Extended query protocol: parse, bind, execute, describe FE/BE messages.

Only lightly tested as yet, since libpq doesn't know anything about 'em.
This commit is contained in:
Tom Lane 2003-05-05 00:44:56 +00:00
parent a59793f82c
commit 16503e6fa4
17 changed files with 1336 additions and 268 deletions

View File

@ -1,4 +1,4 @@
<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.33 2003/04/28 05:17:31 tgl Exp $ -->
<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.34 2003/05/05 00:44:55 tgl Exp $ -->
<chapter id="protocol">
<title>Frontend/Backend Protocol</title>
@ -595,7 +595,11 @@
<para>
If successfully created, a named prepared-statement object lasts till
the end of the current session, unless explicitly destroyed. An unnamed
prepared statement lasts only until the next Parse message is issued.
prepared statement lasts only until the next Parse statement specifying
the unnamed statement as destination is issued. (Note that a simple
Query message also destroys the unnamed statement.) Named prepared
statements must be explicitly closed before they can be redefined by
a Parse message, but this is not required for the unnamed statement.
Named prepared statements can also be created and accessed at the SQL
command level, using <command>PREPARE</> and <command>EXECUTE</>.
</para>
@ -611,10 +615,13 @@
</para>
<para>
If successfully created, a named portal object lasts till
the end of the current transaction, unless explicitly destroyed. An
unnamed portal is destroyed at the end of the transaction, or as soon
as the next Parse or Bind message is executed.
If successfully created, a named portal object lasts till the end of the
current transaction, unless explicitly destroyed. An unnamed portal is
destroyed at the end of the transaction, or as soon as the next Bind
statement specifying the unnamed portal as destination is issued. (Note
that a simple Query message also destroys the unnamed portal.) Named
portals must be explicitly closed before they can be redefined by a Bind
message, but this is not required for the unnamed portal.
Named portals can also be created and accessed at the SQL
command level, using <command>DECLARE CURSOR</> and <command>FETCH</>.
</para>
@ -691,17 +698,19 @@
The Describe message (statement variant) specifies the name of an existing
prepared statement (or an empty string for the unnamed prepared
statement). The response is a ParameterDescription message describing the
parameters needed by the statement (if any), followed by a RowDescription
message describing the rows that will be returned when the statement is
eventually executed (or NoData if there is no SELECT-type query in the
prepared statement). ErrorResponse is issued if there is no such prepared
statement. This message may be useful if the client library is
uncertain about the parameters needed by a prepared statement.
parameters needed by the statement. ErrorResponse is issued if there is
no such prepared statement. This message may be useful if the client
library is uncertain about the parameters needed by a prepared statement.
</para>
<para>
The Close message closes an existing prepared statement or portal
and releases resources.
and releases resources. It is not an error to issue Close against
a nonexistent statement or portal name. The response is normally
CloseComplete, but could be ErrorResponse if some difficulty is
encountered while releasing resources. Note that closing a prepared
statement implicitly closes any open portals that were constructed
from that statement.
</para>
<para>
@ -709,19 +718,20 @@
but forces the backend to deliver any data pending in its output
buffers. A Flush must be sent after any extended-query command except
Sync, if the frontend wishes to examine the results of that command before
issuing more commands. Without Flush, returning data will be combined
into the minimum possible number of packets to minimize network overhead.
issuing more commands. Without Flush, messages returned by the backend
will be combined into the minimum possible number of packets to minimize
network overhead.
</para>
<note>
<para>
The simple Query message is approximately equivalent to the series Parse,
Bind, portal Describe, Execute, Sync, using the unnamed prepared statement
and portal objects and no parameters. One difference is that it
will accept multiple SQL statements in the query string, automatically
Bind, portal Describe, Execute, Close, Sync, using the unnamed prepared
statement and portal objects and no parameters. One difference is that
it will accept multiple SQL statements in the query string, automatically
performing the bind/describe/execute sequence for each one in succession.
Another is that it will not return ParseComplete, BindComplete, or
NoData messages.
Another difference is that it will not return ParseComplete, BindComplete,
CloseComplete, or NoData messages.
</para>
</note>
</sect2>
@ -1917,6 +1927,41 @@ Close (F)
</VarListEntry>
<VarListEntry>
<Term>
CloseComplete (B)
</Term>
<ListItem>
<Para>
<VariableList>
<VarListEntry>
<Term>
Byte1('3')
</Term>
<ListItem>
<Para>
Identifies the message as a Close-complete indicator.
</Para>
</ListItem>
</VarListEntry>
<VarListEntry>
<Term>
Int32(4)
</Term>
<ListItem>
<Para>
Length of message contents in bytes, including self.
</Para>
</ListItem>
</VarListEntry>
</VariableList>
</Para>
</ListItem>
</VarListEntry>
<VarListEntry>
<Term>
CommandComplete (B)
@ -3875,6 +3920,15 @@ The ReadyForQuery ('<literal>Z</>') message includes a transaction status
indicator.
</para>
<para>
There is a new <quote>extended query</> sub-protocol, which adds the frontend
message types Parse, Bind, Execute, Describe, Close, Flush, and Sync, and the
backend message types ParseComplete, BindComplete, PortalSuspended,
ParameterDescription, NoData, and CloseComplete. Existing clients do not
have to concern themselves with this sub-protocol, but making use of it
may allow improvements in performance or functionality.
</para>
<para>
COPY data is now encapsulated into CopyData and CopyDone messages. There
is a well-defined way to recover from errors during COPY. The special

View File

@ -9,7 +9,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.67 2003/04/26 20:22:58 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.68 2003/05/05 00:44:55 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -48,6 +48,7 @@ typedef struct
typedef struct
{
DestReceiver pub; /* publicly-known function pointers */
bool sendDescrip; /* send RowDescription at startup? */
TupleDesc attrinfo; /* The attr info we are set up for */
int nattrs;
PrinttupAttrInfo *myinfo; /* Cached info about each attr */
@ -58,7 +59,7 @@ typedef struct
* ----------------
*/
DestReceiver *
printtup_create_DR(bool isBinary)
printtup_create_DR(bool isBinary, bool sendDescrip)
{
DR_printtup *self = (DR_printtup *) palloc(sizeof(DR_printtup));
@ -66,6 +67,8 @@ printtup_create_DR(bool isBinary)
self->pub.setup = printtup_setup;
self->pub.cleanup = printtup_cleanup;
self->sendDescrip = sendDescrip;
self->attrinfo = NULL;
self->nattrs = 0;
self->myinfo = NULL;
@ -77,6 +80,8 @@ static void
printtup_setup(DestReceiver *self, int operation,
const char *portalName, TupleDesc typeinfo)
{
DR_printtup *myState = (DR_printtup *) self;
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
{
/*
@ -91,41 +96,11 @@ printtup_setup(DestReceiver *self, int operation,
}
/*
* if this is a retrieve, then we send back the tuple descriptor of
* the tuples.
* If this is a retrieve, and we are supposed to emit row descriptions,
* then we send back the tuple descriptor of the tuples.
*/
if (operation == CMD_SELECT)
{
Form_pg_attribute *attrs = typeinfo->attrs;
int natts = typeinfo->natts;
int proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
int i;
StringInfoData buf;
pq_beginmessage(&buf, 'T'); /* tuple descriptor message type */
pq_sendint(&buf, natts, 2); /* # of attrs in tuples */
for (i = 0; i < natts; ++i)
{
pq_sendstring(&buf, NameStr(attrs[i]->attname));
/* column ID info appears in protocol 3.0 and up */
if (proto >= 3)
{
/* XXX not yet implemented, send zeroes */
pq_sendint(&buf, 0, 4);
pq_sendint(&buf, 0, 2);
}
pq_sendint(&buf, (int) attrs[i]->atttypid,
sizeof(attrs[i]->atttypid));
pq_sendint(&buf, attrs[i]->attlen,
sizeof(attrs[i]->attlen));
/* typmod appears in protocol 2.0 and up */
if (proto >= 2)
pq_sendint(&buf, attrs[i]->atttypmod,
sizeof(attrs[i]->atttypmod));
}
pq_endmessage(&buf);
}
if (operation == CMD_SELECT && myState->sendDescrip)
SendRowDescriptionMessage(typeinfo);
/* ----------------
* We could set up the derived attr info at this time, but we postpone it
@ -139,6 +114,43 @@ printtup_setup(DestReceiver *self, int operation,
*/
}
/*
* SendRowDescriptionMessage --- send a RowDescription message to the frontend
*/
void
SendRowDescriptionMessage(TupleDesc typeinfo)
{
Form_pg_attribute *attrs = typeinfo->attrs;
int natts = typeinfo->natts;
int proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
int i;
StringInfoData buf;
pq_beginmessage(&buf, 'T'); /* tuple descriptor message type */
pq_sendint(&buf, natts, 2); /* # of attrs in tuples */
for (i = 0; i < natts; ++i)
{
pq_sendstring(&buf, NameStr(attrs[i]->attname));
/* column ID info appears in protocol 3.0 and up */
if (proto >= 3)
{
/* XXX not yet implemented, send zeroes */
pq_sendint(&buf, 0, 4);
pq_sendint(&buf, 0, 2);
}
pq_sendint(&buf, (int) attrs[i]->atttypid,
sizeof(attrs[i]->atttypid));
pq_sendint(&buf, attrs[i]->attlen,
sizeof(attrs[i]->attlen));
/* typmod appears in protocol 2.0 and up */
if (proto >= 2)
pq_sendint(&buf, attrs[i]->atttypmod,
sizeof(attrs[i]->atttypmod));
}
pq_endmessage(&buf);
}
static void
printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
{

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.13 2003/05/02 20:54:33 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.14 2003/05/05 00:44:55 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -49,7 +49,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
* Disallow empty-string cursor name (conflicts with protocol-level
* unnamed portal).
*/
if (strlen(stmt->portalname) == 0)
if (!stmt->portalname || stmt->portalname[0] == '\0')
elog(ERROR, "Invalid cursor name: must not be empty");
/*
@ -148,6 +148,13 @@ PerformPortalFetch(FetchStmt *stmt,
Portal portal;
long nprocessed;
/*
* Disallow empty-string cursor name (conflicts with protocol-level
* unnamed portal).
*/
if (!stmt->portalname || stmt->portalname[0] == '\0')
elog(ERROR, "Invalid cursor name: must not be empty");
/* get the portal from the portal name */
portal = GetPortalByName(stmt->portalname);
if (!PortalIsValid(portal))
@ -164,7 +171,9 @@ PerformPortalFetch(FetchStmt *stmt,
* Adjust dest if needed. MOVE wants dest = None.
*
* If fetching from a binary cursor and the requested destination is
* Remote, change it to RemoteInternal.
* Remote, change it to RemoteInternal. Note we do NOT change if the
* destination is RemoteExecute --- so the Execute message's format
* specification wins out over the cursor's type.
*/
if (stmt->ismove)
dest = None;
@ -189,10 +198,17 @@ PerformPortalFetch(FetchStmt *stmt,
* Close a cursor.
*/
void
PerformPortalClose(char *name)
PerformPortalClose(const char *name)
{
Portal portal;
/*
* Disallow empty-string cursor name (conflicts with protocol-level
* unnamed portal).
*/
if (!name || name[0] == '\0')
elog(ERROR, "Invalid cursor name: must not be empty");
/*
* get the portal from the portal name
*/

View File

@ -3,10 +3,14 @@
* prepare.c
* Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE
*
* Copyright (c) 2002, PostgreSQL Global Development Group
* This module also implements storage of prepared statements that are
* accessed via the extended FE/BE query protocol.
*
*
* Copyright (c) 2002-2003, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.14 2003/05/02 20:54:33 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.15 2003/05/05 00:44:55 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -25,31 +29,15 @@
#include "utils/memutils.h"
#define HASH_KEY_LEN NAMEDATALEN
/* All the data we need to remember about a stored query */
typedef struct
{
/* dynahash.c requires key to be first field */
char key[HASH_KEY_LEN];
List *query_list; /* list of queries */
List *plan_list; /* list of plans */
List *argtype_list; /* list of parameter type OIDs */
MemoryContext context; /* context containing this query */
} QueryHashEntry;
/*
* 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 entries are QueryHashEntry structs.
* The keys for this hash table are the arguments to PREPARE and EXECUTE
* (statement names); the entries are PreparedStatement structs.
*/
static HTAB *prepared_queries = NULL;
static void InitQueryHashTable(void);
static void StoreQuery(const char *stmt_name, List *query_list,
List *plan_list, List *argtype_list);
static QueryHashEntry *FetchQuery(const char *plan_name);
static ParamListInfo EvaluateParams(EState *estate,
List *params, List *argtypes);
@ -59,14 +47,36 @@ static ParamListInfo EvaluateParams(EState *estate,
void
PrepareQuery(PrepareStmt *stmt)
{
const char *commandTag;
List *query_list,
*plan_list;
if (!stmt->name)
elog(ERROR, "No statement name given");
/*
* Disallow empty-string statement name (conflicts with protocol-level
* unnamed statement).
*/
if (!stmt->name || stmt->name[0] == '\0')
elog(ERROR, "Invalid statement name: must not be empty");
if (stmt->query->commandType == CMD_UTILITY)
elog(ERROR, "Utility statements cannot be prepared");
switch (stmt->query->commandType)
{
case CMD_SELECT:
commandTag = "SELECT";
break;
case CMD_INSERT:
commandTag = "INSERT";
break;
case CMD_UPDATE:
commandTag = "UPDATE";
break;
case CMD_DELETE:
commandTag = "DELETE";
break;
default:
elog(ERROR, "Utility statements cannot be prepared");
commandTag = NULL; /* keep compiler quiet */
break;
}
/*
* Parse analysis is already done, but we must still rewrite and plan
@ -80,7 +90,12 @@ PrepareQuery(PrepareStmt *stmt)
plan_list = pg_plan_queries(query_list, false);
/* Save the results. */
StoreQuery(stmt->name, query_list, plan_list, stmt->argtype_oids);
StorePreparedStatement(stmt->name,
NULL, /* text form not available */
commandTag,
query_list,
plan_list,
stmt->argtype_oids);
}
/*
@ -89,7 +104,8 @@ PrepareQuery(PrepareStmt *stmt)
void
ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
{
QueryHashEntry *entry;
PreparedStatement *entry;
char *query_string;
List *query_list,
*plan_list;
MemoryContext qcontext;
@ -98,8 +114,9 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
Portal portal;
/* Look it up in the hash table */
entry = FetchQuery(stmt->name);
entry = FetchPreparedStatement(stmt->name, true);
query_string = entry->query_string;
query_list = entry->query_list;
plan_list = entry->plan_list;
qcontext = entry->context;
@ -135,6 +152,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
if (query_string)
query_string = pstrdup(query_string);
query_list = copyObject(query_list);
plan_list = copyObject(plan_list);
qcontext = PortalGetHeapMemory(portal);
@ -150,8 +169,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
}
PortalDefineQuery(portal,
NULL, /* XXX fixme: can we save query text? */
NULL, /* no command tag known either */
query_string,
entry->commandTag,
query_list,
plan_list,
qcontext);
@ -228,8 +247,8 @@ InitQueryHashTable(void)
MemSet(&hash_ctl, 0, sizeof(hash_ctl));
hash_ctl.keysize = HASH_KEY_LEN;
hash_ctl.entrysize = sizeof(QueryHashEntry);
hash_ctl.keysize = NAMEDATALEN;
hash_ctl.entrysize = sizeof(PreparedStatement);
prepared_queries = hash_create("Prepared Queries",
32,
@ -244,15 +263,24 @@ InitQueryHashTable(void)
* 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.
*
* Exception: commandTag is presumed to be a pointer to a constant string,
* or possibly NULL, so it need not be copied. Note that commandTag should
* be NULL only if the original query (before rewriting) was empty.
*/
static void
StoreQuery(const char *stmt_name, List *query_list,
List *plan_list, List *argtype_list)
void
StorePreparedStatement(const char *stmt_name,
const char *query_string,
const char *commandTag,
List *query_list,
List *plan_list,
List *argtype_list)
{
QueryHashEntry *entry;
PreparedStatement *entry;
MemoryContext oldcxt,
entrycxt;
char key[HASH_KEY_LEN];
char *qstring;
char key[NAMEDATALEN];
bool found;
/* Initialize the hash table, if necessary */
@ -260,7 +288,7 @@ StoreQuery(const char *stmt_name, List *query_list,
InitQueryHashTable();
/* Check for pre-existing entry of same name */
/* See notes in FetchQuery */
/* See notes in FetchPreparedStatement */
MemSet(key, 0, sizeof(key));
strncpy(key, stmt_name, sizeof(key));
@ -285,15 +313,16 @@ StoreQuery(const char *stmt_name, List *query_list,
* out-of-memory failure only wastes memory and doesn't leave us with
* an incomplete (ie corrupt) hashtable entry.
*/
qstring = query_string ? pstrdup(query_string) : (char *) NULL;
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);
entry = (PreparedStatement *) hash_search(prepared_queries,
key,
HASH_ENTER,
&found);
/* Shouldn't get a failure, nor a duplicate entry */
if (!entry || found)
@ -301,6 +330,8 @@ StoreQuery(const char *stmt_name, List *query_list,
stmt_name);
/* Fill in the hash table entry with copied data */
entry->query_string = qstring;
entry->commandTag = commandTag;
entry->query_list = query_list;
entry->plan_list = plan_list;
entry->argtype_list = argtype_list;
@ -311,52 +342,53 @@ StoreQuery(const char *stmt_name, List *query_list,
/*
* Lookup an existing query in the hash table. If the query does not
* actually exist, an elog(ERROR) is thrown.
* actually exist, throw elog(ERROR) or return NULL per second parameter.
*/
static QueryHashEntry *
FetchQuery(const char *plan_name)
PreparedStatement *
FetchPreparedStatement(const char *stmt_name, bool throwError)
{
char key[HASH_KEY_LEN];
QueryHashEntry *entry;
char key[NAMEDATALEN];
PreparedStatement *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)
if (prepared_queries)
{
/*
* 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));
entry = (PreparedStatement *) hash_search(prepared_queries,
key,
HASH_FIND,
NULL);
}
else
entry = NULL;
if (!entry && throwError)
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);
stmt_name);
return entry;
}
/*
* Given a plan name, look up the stored plan (giving error if not found).
* Look up a prepared statement given the name (giving error if not found).
* If found, return the list of argument type OIDs.
*/
List *
FetchQueryParams(const char *plan_name)
FetchPreparedStatementParams(const char *stmt_name)
{
QueryHashEntry *entry;
PreparedStatement *entry;
entry = FetchQuery(plan_name);
entry = FetchPreparedStatement(stmt_name, true);
return entry->argtype_list;
}
@ -368,20 +400,34 @@ FetchQueryParams(const char *plan_name)
void
DeallocateQuery(DeallocateStmt *stmt)
{
QueryHashEntry *entry;
DropPreparedStatement(stmt->name, true);
}
/* Find the query's hash table entry */
entry = FetchQuery(stmt->name);
/*
* Internal version of DEALLOCATE
*
* If showError is false, dropping a nonexistent statement is a no-op.
*/
void
DropPreparedStatement(const char *stmt_name, bool showError)
{
PreparedStatement *entry;
/* Drop any open portals that depend on this prepared statement */
Assert(MemoryContextIsValid(entry->context));
DropDependentPortals(entry->context);
/* Find the query's hash table entry; raise error if wanted */
entry = FetchPreparedStatement(stmt_name, showError);
/* Flush the context holding the subsidiary data */
MemoryContextDelete(entry->context);
if (entry)
{
/* Drop any open portals that depend on this prepared statement */
Assert(MemoryContextIsValid(entry->context));
DropDependentPortals(entry->context);
/* Now we can remove the hash table entry */
hash_search(prepared_queries, entry->key, HASH_REMOVE, NULL);
/* Flush the context holding the subsidiary data */
MemoryContextDelete(entry->context);
/* Now we can remove the hash table entry */
hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
}
}
/*
@ -391,7 +437,7 @@ void
ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
{
ExecuteStmt *execstmt = (ExecuteStmt *) stmt->query->utilityStmt;
QueryHashEntry *entry;
PreparedStatement *entry;
List *l,
*query_list,
*plan_list;
@ -402,7 +448,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
Assert(execstmt && IsA(execstmt, ExecuteStmt));
/* Look it up in the hash table */
entry = FetchQuery(execstmt->name);
entry = FetchPreparedStatement(execstmt->name, true);
query_list = entry->query_list;
plan_list = entry->plan_list;

View File

@ -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.269 2003/05/02 20:54:34 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.270 2003/05/05 00:44:55 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -83,6 +83,12 @@ typedef struct
IndexStmt *pkey; /* PRIMARY KEY index, if any */
} CreateStmtContext;
typedef struct
{
Oid *paramTypes;
int numParams;
} check_parameter_resolution_context;
static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
static Query *transformStmt(ParseState *pstate, Node *stmt,
@ -124,6 +130,8 @@ static void transformColumnType(ParseState *pstate, ColumnDef *column);
static bool relationHasPrimaryKey(Oid relationOid);
static void release_pstate_resources(ParseState *pstate);
static FromExpr *makeFromExpr(List *fromlist, Node *quals);
static bool check_parameter_resolution_walker(Node *node,
check_parameter_resolution_context *context);
/*
@ -179,6 +187,16 @@ parse_analyze_varparams(Node *parseTree, Oid **paramTypes, int *numParams)
pfree(pstate);
/* make sure all is well with parameter types */
if (*numParams > 0)
{
check_parameter_resolution_context context;
context.paramTypes = *paramTypes;
context.numParams = *numParams;
check_parameter_resolution_walker((Node *) result, &context);
}
return result;
}
@ -2465,7 +2483,7 @@ transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *) stmt;
paramtypes = FetchQueryParams(stmt->name);
paramtypes = FetchPreparedStatementParams(stmt->name);
if (stmt->params || paramtypes)
{
@ -2879,3 +2897,44 @@ analyzeCreateSchemaStmt(CreateSchemaStmt *stmt)
return result;
}
/*
* Traverse a fully-analyzed tree to verify that parameter symbols
* match their types. We need this because some Params might still
* be UNKNOWN, if there wasn't anything to force their coercion,
* and yet other instances seen later might have gotten coerced.
*/
static bool
check_parameter_resolution_walker(Node *node,
check_parameter_resolution_context *context)
{
if (node == NULL)
return false;
if (IsA(node, Param))
{
Param *param = (Param *) node;
if (param->paramkind == PARAM_NUM)
{
int paramno = param->paramid;
if (paramno <= 0 || /* shouldn't happen, but... */
paramno > context->numParams)
elog(ERROR, "Parameter '$%d' is out of range", paramno);
if (param->paramtype != context->paramTypes[paramno-1])
elog(ERROR, "Could not determine datatype of parameter $%d",
paramno);
}
return false;
}
if (IsA(node, Query))
{
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
return query_tree_walker((Query *) node,
check_parameter_resolution_walker,
(void *) context, 0);
}
return expression_tree_walker(node, check_parameter_resolution_walker,
(void *) context);
}

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.54 2003/04/26 20:22:59 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.55 2003/05/05 00:44:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -91,10 +91,20 @@ DestToFunction(CommandDest dest)
switch (dest)
{
case Remote:
return printtup_create_DR(false);
return printtup_create_DR(false, true);
case RemoteInternal:
return printtup_create_DR(true);
return printtup_create_DR(true, true);
case RemoteExecute:
/* like Remote, but suppress output of T message */
return printtup_create_DR(false, false);
case RemoteExecuteInternal:
return printtup_create_DR(true, false);
case None:
return &donothingDR;
case Debug:
return &debugtupDR;
@ -104,9 +114,6 @@ DestToFunction(CommandDest dest)
case Tuplestore:
return tstoreReceiverCreateDR();
case None:
return &donothingDR;
}
/* should never get here */
@ -124,13 +131,15 @@ EndCommand(const char *commandTag, CommandDest dest)
{
case Remote:
case RemoteInternal:
case RemoteExecute:
case RemoteExecuteInternal:
pq_puttextmessage('C', commandTag);
break;
case None:
case Debug:
case Tuplestore:
case SPI:
case Tuplestore:
break;
}
}
@ -152,8 +161,10 @@ NullCommand(CommandDest dest)
{
switch (dest)
{
case RemoteInternal:
case Remote:
case RemoteInternal:
case RemoteExecute:
case RemoteExecuteInternal:
/*
* tell the fe that we saw an empty query string. In protocols
@ -165,10 +176,10 @@ NullCommand(CommandDest dest)
pq_puttextmessage('I', "");
break;
case Debug:
case Tuplestore:
case None:
default:
case Debug:
case SPI:
case Tuplestore:
break;
}
}
@ -189,8 +200,10 @@ ReadyForQuery(CommandDest dest)
{
switch (dest)
{
case RemoteInternal:
case Remote:
case RemoteInternal:
case RemoteExecute:
case RemoteExecuteInternal:
if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
{
StringInfoData buf;
@ -205,10 +218,10 @@ ReadyForQuery(CommandDest dest)
pq_flush();
break;
case Debug:
case Tuplestore:
case None:
default:
case Debug:
case SPI:
case Tuplestore:
break;
}
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.60 2003/05/02 20:54:35 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.61 2003/05/05 00:44:56 tgl Exp $
*
* NOTES
* This cruft is the server side of PQfn.
@ -310,6 +310,14 @@ HandleFunctionRequest(StringInfo msgBuf)
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_func_name(fid));
/*
* Set up a query snapshot in case function needs one.
*/
SetQuerySnapshot();
/*
* Prepare function call info block.
*/
if (fip->flinfo.fn_nargs != nargs || nargs > FUNC_MAX_ARGS)
elog(ERROR, "HandleFunctionRequest: actual arguments (%d) != registered arguments (%d)",
nargs, fip->flinfo.fn_nargs);
@ -359,12 +367,8 @@ HandleFunctionRequest(StringInfo msgBuf)
}
}
/*
* Set up a query snapshot in case function needs one. (It is not safe
* to do this if we are in transaction-abort state, so we have to postpone
* it till now. Ugh.)
*/
SetQuerySnapshot();
/* Verify we reached the end of the message where expected. */
pq_getmsgend(msgBuf);
#ifdef NO_FASTPATH
/* force a NULL return */

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.56 2003/05/02 20:54:35 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.57 2003/05/05 00:44:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -222,11 +222,12 @@ CreateNewPortal(void)
* PortalDefineQuery
* A simple subroutine to establish a portal's query.
*
* Notes: the passed commandTag must be a pointer to a constant string,
* since it is not copied. The caller is responsible for ensuring that
* the passed sourceText (if any), parse and plan trees have adequate
* lifetime. Also, queryContext must accurately describe the location
* of the parse and plan trees.
* Notes: commandTag shall be NULL if and only if the original query string
* (before rewriting) was an empty string. Also, the passed commandTag must
* be a pointer to a constant string, since it is not copied. The caller is
* responsible for ensuring that the passed sourceText (if any), parse and
* plan trees have adequate lifetime. Also, queryContext must accurately
* describe the location of the parse and plan trees.
*/
void
PortalDefineQuery(Portal portal,
@ -241,6 +242,8 @@ PortalDefineQuery(Portal portal,
Assert(length(parseTrees) == length(planTrees));
Assert(commandTag != NULL || parseTrees == NIL);
portal->sourceText = sourceText;
portal->commandTag = commandTag;
portal->parseTrees = parseTrees;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: printtup.h,v 1.23 2003/01/21 22:06:12 tgl Exp $
* $Id: printtup.h,v 1.24 2003/05/05 00:44:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -16,7 +16,9 @@
#include "tcop/dest.h"
extern DestReceiver *printtup_create_DR(bool isBinary);
extern DestReceiver *printtup_create_DR(bool isBinary, bool sendDescrip);
extern void SendRowDescriptionMessage(TupleDesc typeinfo);
extern void debugSetup(DestReceiver *self, int operation,
const char *portalName, TupleDesc typeinfo);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: portalcmds.h,v 1.8 2003/05/02 20:54:35 tgl Exp $
* $Id: portalcmds.h,v 1.9 2003/05/05 00:44:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,7 +22,7 @@ extern void PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest);
extern void PerformPortalFetch(FetchStmt *stmt, CommandDest dest,
char *completionTag);
extern void PerformPortalClose(char *name);
extern void PerformPortalClose(const char *name);
extern void PortalCleanup(Portal portal, bool isError);

View File

@ -1,12 +1,12 @@
/*-------------------------------------------------------------------------
*
* prepare.h
* PREPARE, EXECUTE and DEALLOCATE command prototypes
* PREPARE, EXECUTE and DEALLOCATE commands, and prepared-stmt storage
*
*
* Copyright (c) 2002, PostgreSQL Global Development Group
* Copyright (c) 2002-2003, PostgreSQL Global Development Group
*
* $Id: prepare.h,v 1.3 2003/02/02 23:46:38 tgl Exp $
* $Id: prepare.h,v 1.4 2003/05/05 00:44:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -18,10 +18,44 @@
#include "tcop/dest.h"
/*
* The data structure representing a prepared statement
*
* Note: all subsidiary storage lives in the context denoted by the context
* field. However, the string referenced by commandTag is not subsidiary
* storage; it is assumed to be a compile-time-constant string. As with
* portals, commandTag shall be NULL if and only if the original query string
* (before rewriting) was an empty string.
*/
typedef struct
{
/* dynahash.c requires key to be first field */
char stmt_name[NAMEDATALEN];
char *query_string; /* text of query, or NULL */
const char *commandTag; /* command tag (a constant!), or NULL */
List *query_list; /* list of queries */
List *plan_list; /* list of plans */
List *argtype_list; /* list of parameter type OIDs */
MemoryContext context; /* context containing this query */
} PreparedStatement;
/* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */
extern void PrepareQuery(PrepareStmt *stmt);
extern void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest);
extern void DeallocateQuery(DeallocateStmt *stmt);
extern List *FetchQueryParams(const char *plan_name);
extern void ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate);
/* Low-level access to stored prepared statements */
extern void StorePreparedStatement(const char *stmt_name,
const char *query_string,
const char *commandTag,
List *query_list,
List *plan_list,
List *argtype_list);
extern PreparedStatement *FetchPreparedStatement(const char *stmt_name,
bool throwError);
extern void DropPreparedStatement(const char *stmt_name, bool showError);
extern List *FetchPreparedStatementParams(const char *stmt_name);
#endif /* PREPARE_H */

View File

@ -9,7 +9,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: pqcomm.h,v 1.81 2003/04/26 20:22:59 tgl Exp $
* $Id: pqcomm.h,v 1.82 2003/05/05 00:44:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -106,7 +106,7 @@ typedef union SockAddr
/* The earliest and latest frontend/backend protocol version supported. */
#define PG_PROTOCOL_EARLIEST PG_PROTOCOL(1,0)
#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,105) /* XXX temporary value */
#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,106) /* XXX temporary value */
typedef uint32 ProtocolVersion; /* FE/BE protocol version number */

View File

@ -44,7 +44,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: dest.h,v 1.34 2003/04/19 00:02:30 tgl Exp $
* $Id: dest.h,v 1.35 2003/05/05 00:44:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -61,6 +61,10 @@
/* ----------------
* CommandDest is a simplistic means of identifying the desired
* destination. Someday this will probably need to be improved.
*
* Note: only the values None, Debug, Remote are legal for the global
* variable whereToSendOutput. The other values may be selected
* as the destination for individual commands.
* ----------------
*/
typedef enum
@ -71,7 +75,9 @@ typedef enum
RemoteInternal, /* results sent to frontend process in
* internal (binary) form */
SPI, /* results sent to SPI manager */
Tuplestore /* results sent to Tuplestore */
Tuplestore, /* results sent to Tuplestore */
RemoteExecute, /* sent to frontend, in Execute command */
RemoteExecuteInternal /* same, but binary format */
} CommandDest;
/* ----------------

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: tcopprot.h,v 1.56 2003/05/02 20:54:36 tgl Exp $
* $Id: tcopprot.h,v 1.57 2003/05/05 00:44:56 tgl Exp $
*
* OLD COMMENTS
* This file was created so that other c files could get the two
@ -35,11 +35,12 @@ extern DLLIMPORT const char *debug_query_string;
#ifndef BOOTSTRAP_INCLUDE
extern List *pg_parse_and_rewrite(const char *query_string,
Oid *paramTypes, int numParams);
extern List *pg_parse_query(const char *query_string);
extern List *pg_analyze_and_rewrite(Node *parsetree,
Oid *paramTypes, int numParams);
extern List *pg_parse_and_rewrite(const char *query_string,
Oid *paramTypes, int numParams);
extern List *pg_rewrite_queries(List *querytree_list);
extern Plan *pg_plan_query(Query *querytree);
extern List *pg_plan_queries(List *querytrees, bool needSnapshot);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.239 2003/04/28 04:52:13 tgl Exp $
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.240 2003/05/05 00:44:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -375,6 +375,17 @@ connectOptions1(PGconn *conn, const char *conninfo)
static bool
connectOptions2(PGconn *conn)
{
/*
* If database name was not given, default it to equal user name
*/
if ((conn->dbName == NULL || conn->dbName[0] == '\0')
&& conn->pguser != NULL)
{
if (conn->dbName)
free(conn->dbName);
conn->dbName = strdup(conn->pguser);
}
/*
* Supply default password if none given
*/

View File

@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: libpq-int.h,v 1.66 2003/04/26 20:23:00 tgl Exp $
* $Id: libpq-int.h,v 1.67 2003/05/05 00:44:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -56,7 +56,7 @@ typedef int ssize_t; /* ssize_t doesn't exist in VC (atleast
* pqcomm.h describe what the backend knows, not what libpq knows.
*/
#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,105) /* XXX temporary value */
#define PG_PROTOCOL_LIBPQ PG_PROTOCOL(3,106) /* XXX temporary value */
/*
* POSTGRES backend dependent Constants.