diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 79221044c2130f6fdad7479b5752e45197d54738..a923e260edb4f6e5ffde6a3e1d6e45f254dc459f 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -1,5 +1,5 @@ @@ -71,6 +71,7 @@ Complete list of usable sgml source files in this directory. + @@ -93,6 +94,7 @@ Complete list of usable sgml source files in this directory. + @@ -102,6 +104,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/deallocate.sgml b/doc/src/sgml/ref/deallocate.sgml new file mode 100644 index 0000000000000000000000000000000000000000..29925adc4aa0d8348884bf466063ccab19321141 --- /dev/null +++ b/doc/src/sgml/ref/deallocate.sgml @@ -0,0 +1,137 @@ + + + + + DEALLOCATE + SQL - Language Statements + + + + DEALLOCATE + + + remove a prepared query + + + + + 2002-08-12 + + + DEALLOCATE [ PREPARE ] plan_name + + + + + 2002-08-12 + + + Inputs + + + + + + PREPARE + + + This keyword is ignored. + + + + + plan_name + + + The name of the prepared query to remove. + + + + + + + + + 2002-08-12 + + + Outputs + + + + + + + DEALLOCATE + + + + The prepared query was removed successfully. + + + + + + + + + + + 2002-08-12 + + + Description + + + + DEALLOCATE is used to remove a previously + prepared query. If you do not explicitly + DEALLOCATE a prepared query, it is removed when + the session ends. + + + + For more information on prepared queries, see . + + + + + + Compatibility + + + + + 2002-08-12 + + + SQL92 + + + SQL92 includes a DEALLOCATE statement, but it is + only for use in embedded SQL clients. + + + + + + diff --git a/doc/src/sgml/ref/execute.sgml b/doc/src/sgml/ref/execute.sgml new file mode 100644 index 0000000000000000000000000000000000000000..67035572e742373acd55f86af4108f12567b60e8 --- /dev/null +++ b/doc/src/sgml/ref/execute.sgml @@ -0,0 +1,132 @@ + + + + + EXECUTE + SQL - Language Statements + + + + EXECUTE + + + execute a prepared query + + + + + 2002-08-12 + + + EXECUTE plan_name [ (parameter [, ...] ) ] + + + + + 2002-08-12 + + + Inputs + + + + + + plan_name + + + The name of the prepared query to execute. + + + + + parameter + + + The actual value of a parameter to the prepared query. + This must be an expression yielding a value of a type + compatible with + the data-type specified for this parameter position in the + PREPARE statement that created the prepared + query. + + + + + + + + + + + 2002-08-12 + + + Description + + + + EXECUTE is used to execute a previously prepared + query. Since prepared queries only exist for the duration of a + session, the prepared query must have been created by a + PREPARE statement executed earlier in the + current session. + + + + If the PREPARE statement that created the query + specified some parameters, a compatible set of parameters must be + passed to the EXECUTE statement, or else an + error is raised. Note that (unlike functions) prepared queries are + not overloaded based on the type or number of their parameters: the + name of a prepared query must be unique within a database session. + + + + For more information on the creation and usage of prepared queries, + see . + + + + + + Compatibility + + + + + 2002-08-12 + + + SQL92 + + + SQL92 includes an EXECUTE statement, but it is + only for use in embedded SQL clients. The + EXECUTE statement implemented by + PostgreSQL also uses a somewhat + different syntax. + + + + + + diff --git a/doc/src/sgml/ref/prepare.sgml b/doc/src/sgml/ref/prepare.sgml new file mode 100644 index 0000000000000000000000000000000000000000..d9fa86414bf1aabfd07a060db437a0e042b0570f --- /dev/null +++ b/doc/src/sgml/ref/prepare.sgml @@ -0,0 +1,209 @@ + + + + + PREPARE + SQL - Language Statements + + + + PREPARE + + + create a prepared query + + + + + 2002-08-12 + + + PREPARE plan_name [ (datatype [, ...] ) ] AS query + + + + + 2002-08-12 + + + Inputs + + + + + + plan_name + + + An arbitrary name given to this particular prepared query. It + must be unique within a single session, and is used to execute + or remove a previously prepared query. + + + + + datatype + + + The data-type of a parameter to the prepared query. + To refer to the parameters in the prepared query itself, + use $1, $2, etc. + + + + + + + + + + 2002-08-12 + + + Outputs + + + + + + + PREPARE + + + + The query has been prepared successfully. + + + + + + + + + + + + 2002-08-12 + + + Description + + + PREPARE creates a prepared query. A prepared + query is a server-side object that can be used to optimize + performance. When the PREPARE statement is + executed, the specified query is parsed, rewritten, and + planned. When a subsequent EXECUTE statement is + issued, the prepared query need only be executed. Thus, the + parsing, rewriting, and planning stages are only performed once, + instead of every time the query is executed. + + + + Prepared queries can take parameters: values that are + substituted into the query when it is executed. To specify the + parameters to a prepared query, include a list of data-types with + the PREPARE statement. In the query itself, you + can refer to the parameters by position using + $1, $2, etc. When executing + the query, specify the actual values for these parameters in the + EXECUTE statement -- refer to + for more information. + + + + Prepared queries are stored locally (in the current backend), and + only exist for the duration of the current database session. When + the client exits, the prepared query is forgotten, and so it must be + re-created before being used again. This also means that a single + prepared query cannot be used by multiple simultaneous database + clients; however, each client can create their own prepared query + to use. + + + + Prepared queries have the largest performance advantage when a + single backend is being used to execute a large number of similar + queries. The performance difference will be particularly + significant if the queries are complex to plan or rewrite. For + example, if the query involves a join of many tables or requires + the application of several rules. If the query is relatively simple + to plan and rewrite but relatively expensive to execute, the + performance advantage of prepared queries will be less noticeable. + + + + + 2002-08-12 + + + Notes + + + + In some situations, the query plan produced by + PostgreSQL for a prepared query may be + inferior to the plan produced if the query were submitted and + executed normally. This is because when the query is planned (and + the optimizer attempts to determine the optimal query plan), the + actual values of any parameters specified in the query are + unavailable. PostgreSQL collects + statistics on the distribution of data in the table, and can use + constant values in a query to make guesses about the likely + result of executing the query. Since this data is unavailable when + planning prepared queries with parameters, the chosen plan may be + sub-optimal. + + + + For more information on query planning and the statistics + collected by PostgreSQL for query + optimization purposes, see the documentation. + + + + + + + Compatibility + + + + + 2002-08-12 + + + SQL92 + + + SQL92 includes a PREPARE statement, but it is + only for use in embedded SQL clients. The + PREPARE statement implemented by + PostgreSQL also uses a somewhat + different syntax. + + + + + + diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index effe495f1da38c004d76a07c1931926f766793da..22a3c07a3e69eefde9264d92ed50935d50b17eed 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -1,5 +1,5 @@ @@ -80,6 +80,7 @@ PostgreSQL Reference Manual &createType; &createUser; &createView; + &deallocate; &declare; &delete; &dropAggregate; @@ -98,10 +99,11 @@ PostgreSQL Reference Manual &dropSequence; &dropTable; &dropTrigger; - &dropType + &dropType; &dropUser; &dropView; &end; + &execute; &explain; &fetch; &grant; @@ -111,6 +113,7 @@ PostgreSQL Reference Manual &lock; &move; ¬ify; + &prepare; &reindex; &reset; &revoke; diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml index 4be5868512091d62d9e0483937882c82ee68b99f..e457504ebefcf4a5352d3dac79ac6525cf7fa45a 100644 --- a/doc/src/sgml/release.sgml +++ b/doc/src/sgml/release.sgml @@ -1,5 +1,5 @@ @@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without worries about funny characters. --> 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) + { + Query *query = (Query *) lfirst(query_list_item); + Plan *plan; + + /* We can't generate plans for utility statements. */ + if (query->commandType == CMD_UTILITY) + plan = NULL; + else + { + /* Call the query planner to generate a plan. */ + plan = planner(query); + } + + plan_list = lappend(plan_list, plan); + } + + StoreQuery(stmt->name, query_list, plan_list, stmt->argtype_oids); +} + +/* + * Implements the 'EXECUTE' utility statement. + */ +void +ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) +{ + QueryHashEntry *entry; + List *l, + *query_list, + *plan_list; + ParamListInfo paramLI = NULL; + + /* Look it up in the hash table */ + entry = FetchQuery(stmt->name); + + /* Make working copies the executor can safely scribble on */ + query_list = (List *) copyObject(entry->query_list); + plan_list = (List *) copyObject(entry->plan_list); + + Assert(length(query_list) == length(plan_list)); + + /* Evaluate parameters, if any */ + if (entry->argtype_list != NIL) + { + int nargs = length(entry->argtype_list); + int i = 0; + ExprContext *econtext = MakeExprContext(NULL, CurrentMemoryContext); + + /* Parser should have caught this error, but check */ + if (nargs != length(stmt->params)) + elog(ERROR, "ExecuteQuery: wrong number of arguments"); + + paramLI = (ParamListInfo) palloc((nargs + 1) * sizeof(ParamListInfoData)); + MemSet(paramLI, 0, (nargs + 1) * sizeof(ParamListInfoData)); + + foreach (l, stmt->params) + { + Node *n = lfirst(l); + bool isNull; + + paramLI[i].value = ExecEvalExprSwitchContext(n, + econtext, + &isNull, + NULL); + paramLI[i].kind = PARAM_NUM; + paramLI[i].id = i + 1; + paramLI[i].isnull = isNull; + + i++; + } + paramLI[i].kind = PARAM_INVALID; + } + + /* Execute each query */ + foreach(l, query_list) + { + Query *query = lfirst(l); + Plan *plan = lfirst(plan_list); + bool is_last_query; + + plan_list = lnext(plan_list); + is_last_query = (plan_list == NIL); + + if (query->commandType == CMD_UTILITY) + ProcessUtility(query->utilityStmt, outputDest, NULL); + else + { + QueryDesc *qdesc; + EState *state; + + if (Show_executor_stats) + ResetUsage(); + + qdesc = CreateQueryDesc(query, plan, outputDest, NULL); + state = CreateExecutorState(); + + state->es_param_list_info = paramLI; + + 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(); + } + + /* No need to pfree memory, MemoryContext will be reset */ +} + +/* + * Initialize query hash table upon first use. + */ +static void +InitQueryHashTable(void) +{ + HASHCTL hash_ctl; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + + hash_ctl.keysize = HASH_KEY_LEN; + hash_ctl.entrysize = sizeof(QueryHashEntry); + + prepared_queries = hash_create("Prepared Queries", + 32, + &hash_ctl, + HASH_ELEM); + + if (!prepared_queries) + elog(ERROR, "InitQueryHashTable: unable to create hash table"); +} + +/* + * Store all the data pertaining to a query in the hash table using + * the specified key. A copy of the data is made in a memory context belonging + * to the hash entry, so the caller can dispose of their copy. + */ +static void +StoreQuery(const char *stmt_name, List *query_list, List *plan_list, + List *argtype_list) +{ + QueryHashEntry *entry; + MemoryContext oldcxt, + entrycxt; + char key[HASH_KEY_LEN]; + bool found; + + /* Initialize the hash table, if necessary */ + if (!prepared_queries) + InitQueryHashTable(); + + /* Check for pre-existing entry of same name */ + /* See notes in FetchQuery */ + MemSet(key, 0, sizeof(key)); + strncpy(key, stmt_name, sizeof(key)); + + hash_search(prepared_queries, key, HASH_FIND, &found); + + if (found) + elog(ERROR, "Prepared statement with name \"%s\" already exists", + stmt_name); + + /* Okay. Make a permanent memory context for the hashtable entry */ + entrycxt = AllocSetContextCreate(TopMemoryContext, + stmt_name, + 1024, + 1024, + ALLOCSET_DEFAULT_MAXSIZE); + + oldcxt = MemoryContextSwitchTo(entrycxt); + + /* + * We need to copy the data so that it is stored in the correct + * memory context. Do this before making hashtable entry, so that + * an out-of-memory failure only wastes memory and doesn't leave us + * with an incomplete (ie corrupt) hashtable entry. + */ + query_list = (List *) copyObject(query_list); + plan_list = (List *) copyObject(plan_list); + argtype_list = listCopy(argtype_list); + + /* Now we can add entry to hash table */ + entry = (QueryHashEntry *) hash_search(prepared_queries, + key, + HASH_ENTER, + &found); + + /* Shouldn't get a failure, nor duplicate entry */ + if (!entry || found) + elog(ERROR, "Unable to store prepared statement \"%s\"!", + stmt_name); + + /* Fill in the hash table entry with copied data */ + entry->query_list = query_list; + entry->plan_list = plan_list; + entry->argtype_list = argtype_list; + entry->context = entrycxt; + + MemoryContextSwitchTo(oldcxt); +} + +/* + * Lookup an existing query in the hash table. + */ +static QueryHashEntry * +FetchQuery(const char *plan_name) +{ + char key[HASH_KEY_LEN]; + QueryHashEntry *entry; + + /* + * 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); + + /* + * 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. + */ + MemSet(key, 0, sizeof(key)); + strncpy(key, plan_name, sizeof(key)); + + entry = (QueryHashEntry *) hash_search(prepared_queries, + key, + HASH_FIND, + NULL); + + if (!entry) + elog(ERROR, "Prepared statement with name \"%s\" does not exist", + plan_name); + + return entry; +} + +/* + * Given a plan name, look up the stored plan (giving error if not found). + * If found, return the list of argument type OIDs. + */ +List * +FetchQueryParams(const char *plan_name) +{ + QueryHashEntry *entry; + + entry = FetchQuery(plan_name); + + return entry->argtype_list; +} + +/* + * Actually execute a prepared query. + */ +static void +RunQuery(QueryDesc *qdesc, EState *state) +{ + TupleDesc tupdesc; + + tupdesc = ExecutorStart(qdesc, state); + + ExecutorRun(qdesc, state, state->es_direction, 0L); + + ExecutorEnd(qdesc, state); +} + +/* + * Implements the 'DEALLOCATE' utility statement: deletes the + * specified plan from storage. + * + * The initial part of this routine is identical to FetchQuery(), + * but we repeat the coding because we need to use the key twice. + */ +void +DeallocateQuery(DeallocateStmt *stmt) +{ + char key[HASH_KEY_LEN]; + QueryHashEntry *entry; + + /* + * 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", + stmt->name); + + /* + * 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. + */ + MemSet(key, 0, sizeof(key)); + strncpy(key, stmt->name, sizeof(key)); + + /* + * First lookup the entry, so we can release all the subsidiary 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 = (QueryHashEntry *) hash_search(prepared_queries, + key, + HASH_FIND, + NULL); + + if (!entry) + elog(ERROR, "Prepared statement with name \"%s\" does not exist", + stmt->name); + + /* Flush the context holding the subsidiary data */ + if (MemoryContextIsValid(entry->context)) + MemoryContextDelete(entry->context); + + /* Now we can remove the hash table entry */ + hash_search(prepared_queries, key, HASH_REMOVE, NULL); +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 4de9ba5b8dec55d6b7e9f9cbf060c1786afebb38..b3920e38b2dfbb44e73731441de537d25009ddf6 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.206 2002/08/26 17:53:57 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.207 2002/08/27 04:55:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2647,6 +2647,41 @@ _copyDropCastStmt(DropCastStmt *from) return newnode; } +static PrepareStmt * +_copyPrepareStmt(PrepareStmt *from) +{ + PrepareStmt *newnode = makeNode(PrepareStmt); + + newnode->name = pstrdup(from->name); + Node_Copy(from, newnode, argtypes); + newnode->argtype_oids = listCopy(from->argtype_oids); + Node_Copy(from, newnode, query); + + return newnode; +} + +static ExecuteStmt * +_copyExecuteStmt(ExecuteStmt *from) +{ + ExecuteStmt *newnode = makeNode(ExecuteStmt); + + newnode->name = pstrdup(from->name); + Node_Copy(from, newnode, into); + Node_Copy(from, newnode, params); + + return newnode; +} + +static DeallocateStmt * +_copyDeallocateStmt(DeallocateStmt *from) +{ + DeallocateStmt *newnode = makeNode(DeallocateStmt); + + newnode->name = pstrdup(from->name); + + return newnode; +} + /* **************************************************************** * pg_list.h copy functions @@ -3079,6 +3114,15 @@ copyObject(void *from) case T_DropCastStmt: retval = _copyDropCastStmt(from); break; + case T_PrepareStmt: + retval = _copyPrepareStmt(from); + break; + case T_ExecuteStmt: + retval = _copyExecuteStmt(from); + break; + case T_DeallocateStmt: + retval = _copyDeallocateStmt(from); + break; case T_A_Expr: retval = _copyAExpr(from); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index c96389f1e8b28e67324350d9d63eb8d53b7352b6..408a94ff1b3922f272c3e4e15ade3d2eb4a54fcb 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -20,7 +20,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.154 2002/08/26 17:53:57 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.155 2002/08/27 04:55:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1490,6 +1490,43 @@ _equalDropCastStmt(DropCastStmt *a, DropCastStmt *b) return true; } +static bool +_equalPrepareStmt(PrepareStmt *a, PrepareStmt *b) +{ + if (!equalstr(a->name, b->name)) + return false; + if (!equal(a->argtypes, b->argtypes)) + return false; + if (!equali(a->argtype_oids, b->argtype_oids)) + return false; + if (!equal(a->query, b->query)) + return false; + + return true; +} + +static bool +_equalExecuteStmt(ExecuteStmt *a, ExecuteStmt *b) +{ + if (!equalstr(a->name, b->name)) + return false; + if (!equal(a->into, b->into)) + return false; + if (!equal(a->params, b->params)) + return false; + + return true; +} + +static bool +_equalDeallocateStmt(DeallocateStmt *a, DeallocateStmt *b) +{ + if (!equalstr(a->name, b->name)) + return false; + + return true; +} + static bool _equalAExpr(A_Expr *a, A_Expr *b) { @@ -2249,6 +2286,15 @@ equal(void *a, void *b) case T_DropCastStmt: retval = _equalDropCastStmt(a, b); break; + case T_PrepareStmt: + retval = _equalPrepareStmt(a, b); + break; + case T_ExecuteStmt: + retval = _equalExecuteStmt(a, b); + break; + case T_DeallocateStmt: + retval = _equalDeallocateStmt(a, b); + break; case T_A_Expr: retval = _equalAExpr(a, b); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 01e6d867ce477135880443d1e9efa4ffaa025628..ffa371d92605d38ffacbda3b215f2075fa6be1c5 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.243 2002/08/27 03:56:34 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.244 2002/08/27 04:55:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,7 +20,10 @@ #include "catalog/namespace.h" #include "catalog/pg_index.h" #include "catalog/pg_type.h" +#include "commands/prepare.h" #include "nodes/makefuncs.h" +#include "optimizer/clauses.h" +#include "optimizer/planmain.h" #include "parser/analyze.h" #include "parser/gramparse.h" #include "parser/parsetree.h" @@ -94,6 +97,8 @@ static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt); 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, @@ -277,6 +282,14 @@ transformStmt(ParseState *pstate, Node *parseTree, 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 */ @@ -2454,6 +2467,131 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt, return qry; } +static Query * +transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt) +{ + Query *result = makeNode(Query); + List *extras_before = NIL, + *extras_after = NIL; + List *argtype_oids = NIL; /* argtype OIDs in a list */ + Oid *argtoids = NULL; /* as an array for parser_param_set */ + int nargs; + + result->commandType = CMD_UTILITY; + result->utilityStmt = (Node *) stmt; + + /* Transform list of TypeNames to list (and array) of type OIDs */ + nargs = length(stmt->argtypes); + + if (nargs) + { + List *l; + int i = 0; + + argtoids = (Oid *) palloc(nargs * sizeof(Oid)); + + foreach (l, stmt->argtypes) + { + TypeName *tn = lfirst(l); + Oid toid = typenameTypeId(tn); + + argtype_oids = lappendi(argtype_oids, toid); + argtoids[i++] = toid; + } + } + + stmt->argtype_oids = argtype_oids; + + /* + * We need to adjust the parameters expected by the + * rest of the system, so that $1, ... $n are parsed properly. + * + * This is somewhat of a hack; however, the main parser interface + * only allows parameters to be specified when working with a + * raw query string, which is not helpful here. + */ + parser_param_set(argtoids, nargs); + + stmt->query = transformStmt(pstate, (Node *) stmt->query, + &extras_before, &extras_after); + + /* Shouldn't get any extras, since grammar only allows OptimizableStmt */ + if (extras_before || extras_after) + elog(ERROR, "transformPrepareStmt: internal error"); + + /* Remove links to our local parameters */ + parser_param_set(NULL, 0); + + return result; +} + +static Query * +transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt) +{ + Query *result = makeNode(Query); + List *paramtypes; + + result->commandType = CMD_UTILITY; + result->utilityStmt = (Node *) stmt; + + paramtypes = FetchQueryParams(stmt->name); + + if (stmt->params || paramtypes) + { + int nparams = length(stmt->params); + int nexpected = length(paramtypes); + List *l; + int i = 1; + + if (nparams != nexpected) + elog(ERROR, "Wrong number of parameters, expected %d but got %d", + nexpected, nparams); + + foreach (l, stmt->params) + { + Node *expr = lfirst(l); + Oid expected_type_id, + given_type_id; + + expr = transformExpr(pstate, expr); + + /* Cannot contain subselects or aggregates */ + if (contain_subplans(expr)) + elog(ERROR, "Cannot use subselects in EXECUTE parameters"); + if (contain_agg_clause(expr)) + elog(ERROR, "Cannot use aggregates in EXECUTE parameters"); + + given_type_id = exprType(expr); + expected_type_id = (Oid) lfirsti(paramtypes); + + 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\tYou will need to rewrite or cast the expression", + i, + format_type_be(given_type_id), + format_type_be(expected_type_id)); + } + + fix_opids(expr); + lfirst(l) = expr; + + paramtypes = lnext(paramtypes); + i++; + } + } + + return result; +} + /* exported so planner can check again after rewriting, query pullup, etc */ void CheckSelectForUpdate(Query *qry) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 80aa372f9c572f21faf6685010506a10ffddfa54..0f512c7d31b32df62755d13d20ee3273ae1c8738 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.360 2002/08/19 15:08:47 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.361 2002/08/27 04:55:08 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -68,8 +68,6 @@ extern List *parsetree; /* final parse result is delivered here */ static bool QueryIsRule = FALSE; -static Oid *param_type_info; -static int pfunc_num_args; /* * If you need access to certain yacc-generated variables and find that @@ -149,7 +147,8 @@ static void doNegateFloat(Value *v); SelectStmt, TransactionStmt, TruncateStmt, UnlistenStmt, UpdateStmt, VacuumStmt, VariableResetStmt, VariableSetStmt, VariableShowStmt, - ViewStmt, CheckPointStmt, CreateConversionStmt + ViewStmt, CheckPointStmt, CreateConversionStmt, + DeallocateStmt, PrepareStmt, ExecuteStmt %type select_no_parens, select_with_parens, select_clause, simple_select @@ -218,7 +217,8 @@ static void doNegateFloat(Value *v); group_clause, TriggerFuncArgs, select_limit, opt_select_limit, opclass_item_list, trans_options, TableFuncElementList, OptTableFuncElementList, - convert_args + convert_args, prep_type_clause, prep_type_list, + execute_param_clause, execute_param_list %type into_clause, OptTempTableName @@ -335,7 +335,7 @@ static void doNegateFloat(Value *v); CREATEUSER, CROSS, CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, CURSOR, CYCLE, - DATABASE, DAY_P, DEC, DECIMAL, DECLARE, DEFAULT, + DATABASE, DAY_P, DEALLOCATE, DEC, DECIMAL, DECLARE, DEFAULT, DEFERRABLE, DEFERRED, DEFINER, DELETE_P, DELIMITER, DELIMITERS, DESC, DISTINCT, DO, DOMAIN_P, DOUBLE, DROP, @@ -371,7 +371,7 @@ static void doNegateFloat(Value *v); ORDER, OUT_P, OUTER_P, OVERLAPS, OVERLAY, OWNER, PARTIAL, PASSWORD, PATH_P, PENDANT, PLACING, POSITION, - PRECISION, PRIMARY, PRIOR, PRIVILEGES, PROCEDURE, + PRECISION, PREPARE, PRIMARY, PRIOR, PRIVILEGES, PROCEDURE, PROCEDURAL, READ, REAL, RECHECK, REFERENCES, REINDEX, RELATIVE, RENAME, REPLACE, @@ -490,6 +490,7 @@ stmt : | CreateTrigStmt | CreateUserStmt | ClusterStmt + | DeallocateStmt | DefineStmt | DropStmt | TruncateStmt @@ -502,6 +503,7 @@ stmt : | DropTrigStmt | DropRuleStmt | DropUserStmt + | ExecuteStmt | ExplainStmt | FetchStmt | GrantStmt @@ -510,6 +512,7 @@ stmt : | UnlistenStmt | LockStmt | NotifyStmt + | PrepareStmt | ReindexStmt | RemoveAggrStmt | RemoveOperStmt @@ -3875,6 +3878,77 @@ ExplainStmt: } ; +/***************************************************************************** + * + * QUERY: + * PREPARE [(args, ...)] AS + * + *****************************************************************************/ + +PrepareStmt: PREPARE name prep_type_clause AS OptimizableStmt + { + PrepareStmt *n = makeNode(PrepareStmt); + n->name = $2; + n->argtypes = $3; + n->query = (Query *) $5; + $$ = (Node *) n; + } + ; + +prep_type_clause: '(' prep_type_list ')' { $$ = $2; } + | /* EMPTY */ { $$ = NIL; } + ; + +prep_type_list: Typename { $$ = makeList1($1); } + | prep_type_list ',' Typename + { $$ = lappend($1, $3); } + ; + +/***************************************************************************** + * + * QUERY: + * EXECUTE [(params, ...)] [INTO ...] + * + *****************************************************************************/ + +ExecuteStmt: EXECUTE name execute_param_clause into_clause + { + ExecuteStmt *n = makeNode(ExecuteStmt); + n->name = $2; + n->params = $3; + n->into = $4; + $$ = (Node *) n; + } + ; + +execute_param_clause: '(' execute_param_list ')' { $$ = $2; } + | /* EMPTY */ { $$ = NIL; } + ; + +execute_param_list: a_expr { $$ = makeList1($1); } + | execute_param_list ',' a_expr { $$ = lappend($1, $3); } + ; + +/***************************************************************************** + * + * QUERY: + * DEALLOCATE [PREPARE] + * + *****************************************************************************/ + +DeallocateStmt: DEALLOCATE name + { + DeallocateStmt *n = makeNode(DeallocateStmt); + n->name = $2; + $$ = (Node *) n; + } + | DEALLOCATE PREPARE name + { + DeallocateStmt *n = makeNode(DeallocateStmt); + n->name = $3; + $$ = (Node *) n; + } + ; /***************************************************************************** * * @@ -6947,6 +7021,7 @@ unreserved_keyword: | CYCLE | DATABASE | DAY_P + | DEALLOCATE | DECLARE | DEFERRED | DEFINER @@ -7019,6 +7094,7 @@ unreserved_keyword: | PATH_P | PENDANT | PRECISION + | PREPARE | PRIOR | PRIVILEGES | PROCEDURAL @@ -7589,26 +7665,9 @@ SystemTypeName(char *name) * Initialize to parse one query string */ void -parser_init(Oid *typev, int nargs) +parser_init(void) { QueryIsRule = FALSE; - /* - * Keep enough information around to fill out the type of param nodes - * used in postquel functions - */ - param_type_info = typev; - pfunc_num_args = nargs; -} - -/* param_type() - * Fetch a parameter type previously passed to parser_init - */ -Oid -param_type(int t) -{ - if ((t > pfunc_num_args) || (t <= 0)) - return InvalidOid; - return param_type_info[t - 1]; } /* exprIsNullConstant() diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index 2bb6772054ad26d29305bd17d9c244806fe35edb..9a3064ad661596bea9b1ce1287679dc4117f6dab 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.125 2002/08/18 09:36:25 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.126 2002/08/27 04:55:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -96,6 +96,7 @@ static const ScanKeyword ScanKeywords[] = { {"cycle", CYCLE}, {"database", DATABASE}, {"day", DAY_P}, + {"deallocate", DEALLOCATE}, {"dec", DEC}, {"decimal", DECIMAL}, {"declare", DECLARE}, @@ -229,6 +230,7 @@ static const ScanKeyword ScanKeywords[] = { {"placing", PLACING}, {"position", POSITION}, {"precision", PRECISION}, + {"prepare", PREPARE}, {"primary", PRIMARY}, {"prior", PRIOR}, {"privileges", PRIVILEGES}, diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index f4cd24e0c4ff7835a94bbc8b847a0493915bac40..8c129cb9161bd316a1e325c3f8c9b7db69d71239 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -14,7 +14,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.53 2002/06/20 20:29:33 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.54 2002/08/27 04:55:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -30,6 +30,9 @@ List *parsetree; /* result of parsing is left here */ +static Oid *param_type_info; /* state for param_type() */ +static int param_count; + static int lookahead_token; /* one-token lookahead */ static bool have_lookahead; /* lookahead_token set? */ @@ -50,8 +53,9 @@ parser(StringInfo str, Oid *typev, int nargs) have_lookahead = false; scanner_init(str); - parser_init(typev, nargs); + parser_init(); parse_expr_init(); + parser_param_set(typev, nargs); yyresult = yyparse(); @@ -65,6 +69,35 @@ parser(StringInfo str, Oid *typev, int nargs) } +/* + * Save information needed to fill out the type of Param references ($n) + * + * This is used for SQL functions, PREPARE statements, etc. It's split + * out from parser() setup because PREPARE needs to change the info after + * the grammar runs and before parse analysis is done on the preparable + * query. + */ +void +parser_param_set(Oid *typev, int nargs) +{ + param_type_info = typev; + param_count = nargs; +} + +/* + * param_type() + * + * Fetch a parameter type previously passed to parser_param_set + */ +Oid +param_type(int t) +{ + if (t > param_count || t <= 0) + return InvalidOid; + return param_type_info[t - 1]; +} + + /* * Intermediate filter between parser and base lexer (base_yylex in scan.l). * diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 605f44ba70b9f7ac9c5a50f598219f407f4341e8..28576b8fad610864929606cf0d96f9bda3f96777 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.283 2002/08/17 15:12:07 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.284 2002/08/27 04:55:11 tgl Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -1666,7 +1666,7 @@ PostgresMain(int argc, char *argv[], const char *username) if (!IsUnderPostmaster) { puts("\nPOSTGRES backend interactive interface "); - puts("$Revision: 1.283 $ $Date: 2002/08/17 15:12:07 $\n"); + puts("$Revision: 1.284 $ $Date: 2002/08/27 04:55:11 $\n"); } /* @@ -2406,6 +2406,18 @@ CreateCommandTag(Node *parsetree) tag = "DROP OPERATOR CLASS"; break; + case T_PrepareStmt: + tag = "PREPARE"; + break; + + case T_ExecuteStmt: + tag = "EXECUTE"; + break; + + case T_DeallocateStmt: + tag = "DEALLOCATE"; + break; + default: elog(LOG, "CreateCommandTag: unknown parse node type %d", nodeTag(parsetree)); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 1ae0a89fd6be669ff456774de8abcee36d92a5c2..b16adef54dbce3e68463c23891c3f929c4b8053f 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.172 2002/08/17 13:04:15 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.173 2002/08/27 04:55:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -30,6 +30,7 @@ #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" @@ -379,6 +380,18 @@ ProcessUtility(Node *parsetree, } break; + case T_PrepareStmt: + PrepareQuery((PrepareStmt *) parsetree); + break; + + case T_ExecuteStmt: + ExecuteQuery((ExecuteStmt *) parsetree, dest); + break; + + case T_DeallocateStmt: + DeallocateQuery((DeallocateStmt *) parsetree); + break; + /* * schema */ @@ -541,11 +554,7 @@ ProcessUtility(Node *parsetree, case T_GrantStmt: - { - GrantStmt *stmt = (GrantStmt *) parsetree; - - ExecuteGrantStmt(stmt); - } + ExecuteGrantStmt((GrantStmt *) parsetree); break; /* @@ -841,9 +850,7 @@ ProcessUtility(Node *parsetree, break; case T_CreateConversionStmt: - { - CreateConversionCommand((CreateConversionStmt *) parsetree); - } + CreateConversionCommand((CreateConversionStmt *) parsetree); break; case T_CreateCastStmt: diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h new file mode 100644 index 0000000000000000000000000000000000000000..6af60feeae6d3d7176cc39fae8edd6c67e3d6dac --- /dev/null +++ b/src/include/commands/prepare.h @@ -0,0 +1,29 @@ +/*------------------------------------------------------------------------- + * + * prepare.h + * PREPARE, EXECUTE and DEALLOCATE command prototypes + * + * + * Copyright (c) 2002, PostgreSQL Global Development Group + * + * $Id: prepare.h,v 1.1 2002/08/27 04:55:11 tgl Exp $ + * + *------------------------------------------------------------------------- + */ + +#ifndef PREPARE_H +#define PREPARE_H + +#include "nodes/parsenodes.h" +#include "tcop/dest.h" + + +extern void PrepareQuery(PrepareStmt *stmt); + +extern void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest); + +extern void DeallocateQuery(DeallocateStmt *stmt); + +extern List *FetchQueryParams(const char *plan_name); + +#endif /* PREPARE_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index f3437ce4cbf66adf1df958b2e9799aac0217292f..3f5f6d744998d40c868a95d727132d14c8e159b2 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: nodes.h,v 1.116 2002/08/19 15:08:47 tgl Exp $ + * $Id: nodes.h,v 1.117 2002/08/27 04:55:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -200,6 +200,9 @@ typedef enum NodeTag T_DropCastStmt, T_CreateOpClassStmt, T_RemoveOpClassStmt, + T_PrepareStmt, + T_ExecuteStmt, + T_DeallocateStmt, T_A_Expr = 700, T_ColumnRef, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ecf59f30c10536dd695c737e50bffd08b52d8c7b..25ec8a3542b9dd0710417cb05cb64bcd6b4150d0 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.201 2002/08/19 15:08:47 tgl Exp $ + * $Id: parsenodes.h,v 1.202 2002/08/27 04:55:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1620,4 +1620,42 @@ typedef struct DropCastStmt } DropCastStmt; +/* ---------------------- + * PREPARE Statement + * ---------------------- + */ +typedef struct PrepareStmt +{ + NodeTag type; + char *name; /* Name of plan, arbitrary */ + List *argtypes; /* Types of parameters (TypeNames) */ + List *argtype_oids; /* Types of parameters (OIDs) */ + Query *query; /* The query itself */ +} PrepareStmt; + + +/* ---------------------- + * EXECUTE Statement + * ---------------------- + */ + +typedef struct ExecuteStmt +{ + NodeTag type; + char *name; /* The name of the plan to execute */ + RangeVar *into; /* Optional table to store results in */ + List *params; /* Values to assign to parameters */ +} ExecuteStmt; + + +/* ---------------------- + * DEALLOCATE Statement + * ---------------------- + */ +typedef struct DeallocateStmt +{ + NodeTag type; + char *name; /* The name of the plan to remove */ +} DeallocateStmt; + #endif /* PARSENODES_H */ diff --git a/src/include/parser/gramparse.h b/src/include/parser/gramparse.h index 0bd00cb1b0cac29891e1fc7c99491459d1eaf29e..6af3bafbfb3d796379e3def4c7a9d69098f2f856 100644 --- a/src/include/parser/gramparse.h +++ b/src/include/parser/gramparse.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: gramparse.h,v 1.23 2002/06/20 20:29:51 momjian Exp $ + * $Id: gramparse.h,v 1.24 2002/08/27 04:55:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,6 +19,8 @@ #include "nodes/parsenodes.h" /* from parser.c */ +extern void parser_param_set(Oid *typev, int nargs); +extern Oid param_type(int t); extern int yylex(void); /* from scan.l */ @@ -28,8 +30,7 @@ extern int base_yylex(void); extern void yyerror(const char *message); /* from gram.y */ -extern void parser_init(Oid *typev, int nargs); -extern Oid param_type(int t); +extern void parser_init(void); extern int yyparse(void); extern List *SystemFuncName(char *name); extern TypeName *SystemTypeName(char *name); diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out new file mode 100644 index 0000000000000000000000000000000000000000..166be86eefd4a40562bbb494f7e645866ff5889e --- /dev/null +++ b/src/test/regress/expected/prepare.out @@ -0,0 +1,107 @@ +-- Regression tests for prepareable statements +PREPARE q1 AS SELECT 1; +EXECUTE q1; + ?column? +---------- + 1 +(1 row) + +-- should fail +PREPARE q1 AS SELECT 2; +ERROR: Prepared statement with name "q1" already exists +-- should succeed +DEALLOCATE q1; +PREPARE q1 AS SELECT 2; +EXECUTE q1; + ?column? +---------- + 2 +(1 row) + +-- sql92 syntax +DEALLOCATE PREPARE q1; +-- parameterized queries +PREPARE q2(text) AS + SELECT datname, datistemplate, datallowconn + FROM pg_database WHERE datname = $1; +EXECUTE q2('regression'); + datname | datistemplate | datallowconn +------------+---------------+-------------- + regression | f | t +(1 row) + +PREPARE q3(text, int, float, boolean, oid, smallint) AS + 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('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('bool'); +ERROR: Wrong number of parameters, expected 6 but got 1 +-- too many params +EXECUTE q3('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(5::smallint, 10.5::float, false, 500::oid, 4::bigint, 'bytea'); +ERROR: Parameter $2 of type double precision cannot be coerced into the expected type integer + You will need to rewrite or cast the expression +-- invalid type +PREPARE q4(nonexistenttype) AS SELECT $1; +ERROR: Type "nonexistenttype" does not exist +-- execute into +PREPARE q5(int, text) AS + SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2; +EXECUTE q5(200, 'DTAAAA') INTO TEMPORARY q5_prep_results; +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) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index e82d9421679cfb5d1e7045702790853ea91d89e4..94dedb2b4917d6589b5a0be7a1e196ec774b66fb 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -74,4 +74,4 @@ test: select_views alter_table portals_p2 rules foreign_key cluster # The sixth group of parallel test # ---------- # "plpgsql" cannot run concurrently with "rules" -test: limit plpgsql temp domain rangefuncs copy2 conversion without_oid truncate +test: limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 553df2dfe7d6939ae97789eb4dc043e8f85ec5c7..cf00eeb961cd27f3a2b7c829af3d4c6c127e04b3 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -1,4 +1,4 @@ -# $Header: /cvsroot/pgsql/src/test/regress/serial_schedule,v 1.16 2002/08/22 04:51:06 momjian Exp $ +# $Header: /cvsroot/pgsql/src/test/regress/serial_schedule,v 1.17 2002/08/27 04:55:12 tgl Exp $ # This should probably be in an order similar to parallel_schedule. test: boolean test: char @@ -86,6 +86,7 @@ test: copy2 test: temp test: domain test: rangefuncs +test: prepare test: without_oid test: conversion test: truncate diff --git a/src/test/regress/sql/prepare.sql b/src/test/regress/sql/prepare.sql new file mode 100644 index 0000000000000000000000000000000000000000..ee8df42e0e1b74cbd71f895285e72715335afa98 --- /dev/null +++ b/src/test/regress/sql/prepare.sql @@ -0,0 +1,45 @@ +-- Regression tests for prepareable statements + +PREPARE q1 AS SELECT 1; +EXECUTE q1; + +-- should fail +PREPARE q1 AS SELECT 2; + +-- should succeed +DEALLOCATE q1; +PREPARE q1 AS SELECT 2; +EXECUTE q1; + +-- sql92 syntax +DEALLOCATE PREPARE q1; + +-- parameterized queries +PREPARE q2(text) AS + SELECT datname, datistemplate, datallowconn + FROM pg_database WHERE datname = $1; +EXECUTE q2('regression'); + +PREPARE q3(text, int, float, boolean, oid, smallint) AS + 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('AAAAxx', 5::smallint, 10.5::float, false, 500::oid, 4::bigint); + +-- too few params +EXECUTE q3('bool'); + +-- too many params +EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 500::oid, 4::bigint, true); + +-- wrong param types +EXECUTE q3(5::smallint, 10.5::float, false, 500::oid, 4::bigint, 'bytea'); + +-- invalid type +PREPARE q4(nonexistenttype) AS SELECT $1; + +-- execute into +PREPARE q5(int, text) AS + SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2; +EXECUTE q5(200, 'DTAAAA') INTO TEMPORARY q5_prep_results; +SELECT * FROM q5_prep_results;