Commit 16503e6f authored by Tom Lane's avatar Tom Lane

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

Only lightly tested as yet, since libpq doesn't know anything about 'em.
parent a59793f8
<!-- $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
......
......@@ -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)
{
......
......@@ -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
*/
......
......@@ -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)
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));
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 = (QueryHashEntry *) hash_search(prepared_queries,
key,
HASH_FIND,
NULL);
entry = (PreparedStatement *) hash_search(prepared_queries,
key,
HASH_FIND,
NULL);
}
else
entry = NULL;
if (!entry)
if (!entry && throwError)
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);
}
/*
* 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;
/* Find the query's hash table entry */
entry = FetchQuery(stmt->name);
/* Find the query's hash table entry; raise error if wanted */
entry = FetchPreparedStatement(stmt_name, showError);
/* Drop any open portals that depend on this prepared statement */
Assert(MemoryContextIsValid(entry->context));
DropDependentPortals(entry->context);
if (entry)
{
/* Drop any open portals that depend on this prepared statement */
Assert(MemoryContextIsValid(entry->context));
DropDependentPortals(entry->context);
/* Flush the context holding the subsidiary data */
MemoryContextDelete(entry->context);
/* Flush the context holding the subsidiary data */
MemoryContextDelete(entry->context);
/* Now we can remove the hash table entry */
hash_search(prepared_queries, entry->key, HASH_REMOVE, NULL);
/* 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;
......
......@@ -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);
}
......@@ -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 None:
case Debug:
case SPI:
case Tuplestore:
case None:
default:
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 None:
case Debug:
case SPI:
case Tuplestore:
case None:
default:
break;
}
}
......@@ -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 */
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.331 2003/05/03 05:13:20 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.332 2003/05/05 00:44:56 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
......@@ -33,8 +33,11 @@
#include <getopt.h>
#endif
#include "access/printtup.h"
#include "access/xlog.h"
#include "catalog/pg_type.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "commands/trigger.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
......@@ -54,6 +57,7 @@
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
#include "mb/pg_wchar.h"
......@@ -82,7 +86,15 @@ bool InError = false;
extern bool autocommit;
static bool EchoQuery = false; /* default don't echo */
/*
* Flags for expensive function optimization -- JMH 3/9/92
*/
int XfuncMode = 0;
/* ----------------
* private variables
* ----------------
*/
/*
* Flag to mark SIGHUP. Whenever the main loop comes around it
......@@ -91,23 +103,41 @@ static bool EchoQuery = false; /* default don't echo */
*/
static volatile bool got_SIGHUP = false;
/* ----------------
* people who want to use EOF should #define DONTUSENEWLINE in
* tcop/tcopdebug.h
* ----------------
/*
* Flag to keep track of whether we have started a transaction.
* For extended query protocol this has to be remembered across messages.
*/
static bool xact_started = false;
/*
* Flags to implement skip-till-Sync-after-error behavior for messages of
* the extended query protocol.
*/
static bool doing_extended_query_message = false;
static bool ignore_till_sync = false;
/*
* If an unnamed prepared statement exists, it's stored here.
* We keep it separate from the hashtable kept by commands/prepare.c
* in order to reduce overhead for short-lived queries.
*/
static MemoryContext unnamed_stmt_context = NULL;
static PreparedStatement *unnamed_stmt_pstmt = NULL;
static bool EchoQuery = false; /* default don't echo */
/*
* people who want to use EOF should #define DONTUSENEWLINE in
* tcop/tcopdebug.h
*/
#ifndef TCOP_DONTUSENEWLINE
int UseNewLine = 1; /* Use newlines query delimiters (the
static int UseNewLine = 1; /* Use newlines query delimiters (the
* default) */
#else
int UseNewLine = 0; /* Use EOF as query delimiters */
static int UseNewLine = 0; /* Use EOF as query delimiters */
#endif /* TCOP_DONTUSENEWLINE */
/*
** Flags for expensive function optimization -- JMH 3/9/92
*/
int XfuncMode = 0;
/* ----------------------------------------------------------------
* decls for routines only used in this file
......@@ -254,10 +284,14 @@ SocketBackend(StringInfo inBuf)
* Validate message type code before trying to read body; if we have
* lost sync, better to say "command unknown" than to run out of memory
* because we used garbage as a length word.
*
* This also gives us a place to set the doing_extended_query_message
* flag as soon as possible.
*/
switch (qtype)
{
case 'Q': /* simple query */
doing_extended_query_message = false;
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
{
/* old style without length word; convert */
......@@ -270,15 +304,43 @@ SocketBackend(StringInfo inBuf)
break;
case 'F': /* fastpath function call */
/* we let fastpath.c cope with old-style input of this */
doing_extended_query_message = false;
break;
case 'X': /* terminate */
doing_extended_query_message = false;
break;
case 'B': /* bind */
case 'C': /* close */
case 'D': /* describe */
case 'E': /* execute */
case 'H': /* flush */
case 'P': /* parse */
doing_extended_query_message = true;
/* these are only legal in protocol 3 */
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
elog(FATAL, "Socket command type %c unknown", qtype);
break;
case 'S': /* sync */
/* stop any active skip-till-Sync */
ignore_till_sync = false;
/* mark not-extended, so that a new error doesn't begin skip */
doing_extended_query_message = false;
/* only legal in protocol 3 */
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
elog(FATAL, "Socket command type %c unknown", qtype);
break;
case 'd': /* copy data */
case 'c': /* copy done */
case 'f': /* copy fail */
/* Accept but ignore these messages, per protocol spec */
doing_extended_query_message = false;
/* these are only legal in protocol 3 */
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
elog(FATAL, "Socket command type %c unknown", qtype);
break;
default:
......@@ -410,9 +472,6 @@ List *
pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
{
List *querytree_list;
List *list_item;
Query *querytree;
List *new_list;
/*
* (1) Perform parse analysis.
......@@ -423,21 +482,35 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
querytree_list = parse_analyze(parsetree, paramTypes, numParams);
if (log_parser_stats)
{
ShowUsage("PARSE ANALYSIS STATISTICS");
ResetUsage();
}
/*
* (2) Rewrite the queries, as necessary
*
*/
querytree_list = pg_rewrite_queries(querytree_list);
return querytree_list;
}
/*
* Perform rewriting of a list of queries produced by parse analysis.
*/
List *
pg_rewrite_queries(List *querytree_list)
{
List *new_list = NIL;
List *list_item;
if (log_parser_stats)
ResetUsage();
/*
* rewritten queries are collected in new_list. Note there may be more
* or fewer than in the original list.
*/
new_list = NIL;
foreach(list_item, querytree_list)
{
querytree = (Query *) lfirst(list_item);
Query *querytree = (Query *) lfirst(list_item);
if (Debug_print_parse)
elog_node_display(LOG, "parse tree", querytree,
......@@ -471,7 +544,7 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
new_list = (List *) copyObject(querytree_list);
/* This checks both copyObject() and the equal() routines... */
if (!equal(new_list, querytree_list))
elog(WARNING, "pg_analyze_and_rewrite: copyObject failed on parse tree");
elog(WARNING, "pg_rewrite_queries: copyObject failed on parse tree");
else
querytree_list = new_list;
#endif
......@@ -576,15 +649,13 @@ pg_plan_queries(List *querytrees, bool needSnapshot)
/*
* exec_simple_query()
* exec_simple_query
*
* Execute a "simple Query" protocol message.
*/
static void
exec_simple_query(const char *query_string, /* string to execute */
CommandDest dest) /* where results should go */
exec_simple_query(const char *query_string)
{
bool xact_started;
MemoryContext oldcontext;
List *parsetree_list,
*parsetree_item;
......@@ -619,13 +690,28 @@ exec_simple_query(const char *query_string, /* string to execute */
* that this will normally change current memory context.)
*/
start_xact_command();
xact_started = true;
/*
* Zap any pre-existing unnamed statement. (While not strictly
* necessary, it seems best to define simple-Query mode as if it
* used the unnamed statement and portal; this ensures we recover
* any storage used by prior unnamed operations.)
*/
unnamed_stmt_pstmt = NULL;
if (unnamed_stmt_context)
{
DropDependentPortals(unnamed_stmt_context);
MemoryContextDelete(unnamed_stmt_context);
}
unnamed_stmt_context = NULL;
/*
* Switch to appropriate context for constructing parsetrees.
*/
oldcontext = MemoryContextSwitchTo(MessageContext);
QueryContext = CurrentMemoryContext;
/*
* Do basic parsing of the query or queries (this should be safe even
* if we are in aborted transaction state!)
......@@ -659,7 +745,7 @@ exec_simple_query(const char *query_string, /* string to execute */
set_ps_display(commandTag);
BeginCommand(commandTag, dest);
BeginCommand(commandTag, whereToSendOutput);
/*
* If we are in an aborted transaction, reject all commands except
......@@ -688,11 +774,7 @@ exec_simple_query(const char *query_string, /* string to execute */
}
/* Make sure we are in a transaction command */
if (!xact_started)
{
start_xact_command();
xact_started = true;
}
start_xact_command();
/* If we got a cancel signal in parsing or prior command, quit */
CHECK_FOR_INTERRUPTS();
......@@ -735,37 +817,40 @@ exec_simple_query(const char *query_string, /* string to execute */
*/
PortalStart(portal, NULL);
(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
(void) PortalRun(portal,
FETCH_ALL,
whereToSendOutput,
whereToSendOutput,
completionTag);
PortalDrop(portal, false);
/*
* If this was a transaction control statement or a variable
* set/show/reset statement, commit it and arrange to start a
* new xact command for the next command (if any).
*/
if (IsA(parsetree, TransactionStmt) ||
IsA(parsetree, VariableSetStmt) ||
IsA(parsetree, VariableShowStmt) ||
IsA(parsetree, VariableResetStmt))
{
/*
* If this was a transaction control statement or a variable
* set/show/reset statement, commit it. We will start a
* new xact command for the next command (if any).
*/
finish_xact_command(true);
xact_started = false;
}
/*
* If this is the last parsetree of the query string, close down
* transaction statement before reporting command-complete. This
* is so that any end-of-transaction errors are reported before
* the command-complete message is issued, to avoid confusing
* clients who will expect either a command-complete message or an
* error, not one and then the other. But for compatibility with
* historical Postgres behavior, we do not force a transaction
* boundary between queries appearing in a single query string.
*/
else if (lnext(parsetree_item) == NIL || !autocommit)
{
/*
* If this is the last parsetree of the query string, close down
* transaction statement before reporting command-complete. This
* is so that any end-of-transaction errors are reported before
* the command-complete message is issued, to avoid confusing
* clients who will expect either a command-complete message or an
* error, not one and then the other. But for compatibility with
* historical Postgres behavior, we do not force a transaction
* boundary between queries appearing in a single query string.
*/
finish_xact_command(false);
xact_started = false;
}
else
{
......@@ -783,20 +868,21 @@ exec_simple_query(const char *query_string, /* string to execute */
* (But a command aborted by error will not send an EndCommand
* report at all.)
*/
EndCommand(completionTag, dest);
EndCommand(completionTag, whereToSendOutput);
} /* end loop over parsetrees */
/*
* If there were no parsetrees, return EmptyQueryResponse message.
*/
if (!parsetree_list)
NullCommand(dest);
NullCommand(whereToSendOutput);
QueryContext = NULL;
/*
* Close down transaction statement, if one is open.
*/
if (xact_started)
finish_xact_command(false);
finish_xact_command(false);
/*
* Finish up monitoring.
......@@ -820,39 +906,609 @@ exec_simple_query(const char *query_string, /* string to execute */
debug_query_string = NULL;
}
/*
* exec_parse_message
*
* Execute a "Parse" protocol message.
*/
static void
exec_parse_message(const char *query_string, /* string to execute */
const char *stmt_name, /* name for prepared stmt */
Oid *paramTypes, /* parameter types */
int numParams) /* number of parameters */
{
MemoryContext oldcontext;
List *parsetree_list;
const char *commandTag;
List *querytree_list,
*plantree_list,
*param_list;
bool is_named;
bool save_log_statement_stats = log_statement_stats;
/*
* Report query to various monitoring facilities.
*/
debug_query_string = query_string;
pgstat_report_activity(query_string);
set_ps_display("PARSE");
if (save_log_statement_stats)
ResetUsage();
/*
* Start up a transaction command so we can run parse analysis etc.
* (Note that this will normally change current memory context.)
* Nothing happens if we are already in one.
*/
start_xact_command();
/*
* Switch to appropriate context for constructing parsetrees.
*
* We have two strategies depending on whether the prepared statement
* is named or not. For a named prepared statement, we do parsing
* in MessageContext and copy the finished trees into the prepared
* statement's private context; then the reset of MessageContext releases
* temporary space used by parsing and planning. For an unnamed prepared
* statement, we assume the statement isn't going to hang around long,
* so getting rid of temp space quickly is probably not worth the costs
* of copying parse/plan trees. So in this case, we set up a special
* context for the unnamed statement, and do all the parsing/planning
* therein.
*/
is_named = (stmt_name[0] != '\0');
if (is_named)
{
/* Named prepared statement --- parse in MessageContext */
oldcontext = MemoryContextSwitchTo(MessageContext);
}
else
{
/* Unnamed prepared statement --- release any prior unnamed stmt */
unnamed_stmt_pstmt = NULL;
if (unnamed_stmt_context)
{
DropDependentPortals(unnamed_stmt_context);
MemoryContextDelete(unnamed_stmt_context);
}
unnamed_stmt_context = NULL;
/* create context for parsing/planning */
unnamed_stmt_context =
AllocSetContextCreate(TopMemoryContext,
"unnamed prepared statement",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
oldcontext = MemoryContextSwitchTo(unnamed_stmt_context);
}
QueryContext = CurrentMemoryContext;
/*
* Do basic parsing of the query or queries (this should be safe even
* if we are in aborted transaction state!)
*/
parsetree_list = pg_parse_query(query_string);
/*
* We only allow a single user statement in a prepared statement.
* This is mainly to keep the protocol simple --- otherwise we'd need
* to worry about multiple result tupdescs and things like that.
*/
if (length(parsetree_list) > 1)
elog(ERROR, "Cannot insert multiple commands into a prepared statement");
if (parsetree_list != NIL)
{
Node *parsetree = (Node *) lfirst(parsetree_list);
int i;
/*
* Get the command name for possible use in status display.
*/
commandTag = CreateCommandTag(parsetree);
/*
* If we are in an aborted transaction, reject all commands except
* COMMIT/ROLLBACK. It is important that this test occur before we
* try to do parse analysis, rewrite, or planning, since all those
* phases try to do database accesses, which may fail in abort
* state. (It might be safe to allow some additional utility
* commands in this state, but not many...)
*/
if (IsAbortedTransactionBlockState())
{
bool allowit = false;
if (IsA(parsetree, TransactionStmt))
{
TransactionStmt *stmt = (TransactionStmt *) parsetree;
if (stmt->kind == TRANS_STMT_COMMIT ||
stmt->kind == TRANS_STMT_ROLLBACK)
allowit = true;
}
if (!allowit)
elog(ERROR, "current transaction is aborted, "
"queries ignored until end of transaction block");
}
/*
* OK to analyze, rewrite, and plan this query. Note that the
* originally specified parameter set is not required to be
* complete, so we have to use parse_analyze_varparams().
*/
if (log_parser_stats)
ResetUsage();
querytree_list = parse_analyze_varparams(parsetree,
&paramTypes,
&numParams);
/*
* Check all parameter types got determined, and convert array
* representation to a list for storage.
*/
param_list = NIL;
for (i = 0; i < numParams; i++)
{
Oid ptype = paramTypes[i];
if (ptype == InvalidOid || ptype == UNKNOWNOID)
elog(ERROR, "Could not determine datatype of parameter $%d",
i + 1);
param_list = lappendo(param_list, ptype);
}
if (log_parser_stats)
ShowUsage("PARSE ANALYSIS STATISTICS");
querytree_list = pg_rewrite_queries(querytree_list);
plantree_list = pg_plan_queries(querytree_list, true);
}
else
{
/* Empty input string. This is legal. */
commandTag = NULL;
querytree_list = NIL;
plantree_list = NIL;
param_list = NIL;
}
/* If we got a cancel signal in analysis or planning, quit */
CHECK_FOR_INTERRUPTS();
/*
* Store the query as a prepared statement. See above comments.
*/
if (is_named)
{
StorePreparedStatement(stmt_name,
query_string,
commandTag,
querytree_list,
plantree_list,
param_list);
}
else
{
PreparedStatement *pstmt;
pstmt = (PreparedStatement *) palloc0(sizeof(PreparedStatement));
/* query_string needs to be copied into unnamed_stmt_context */
pstmt->query_string = pstrdup(query_string);
/* the rest is there already */
pstmt->commandTag = commandTag;
pstmt->query_list = querytree_list;
pstmt->plan_list = plantree_list;
pstmt->argtype_list = param_list;
pstmt->context = unnamed_stmt_context;
/* Now the unnamed statement is complete and valid */
unnamed_stmt_pstmt = pstmt;
}
MemoryContextSwitchTo(oldcontext);
QueryContext = NULL;
/*
* We do NOT close the open transaction command here; that only happens
* when the client sends Sync. Instead, do CommandCounterIncrement just
* in case something happened during parse/plan.
*/
CommandCounterIncrement();
/*
* Send ParseComplete.
*/
if (whereToSendOutput == Remote)
pq_putemptymessage('1');
if (save_log_statement_stats)
ShowUsage("PARSE MESSAGE STATISTICS");
debug_query_string = NULL;
}
/*
* exec_bind_message
*
* Process a "Bind" message to create a portal from a prepared statement
*/
static void
exec_bind_message(StringInfo input_message)
{
const char *portal_name;
const char *stmt_name;
int is_binary;
int numParams;
PreparedStatement *pstmt;
Portal portal;
ParamListInfo params;
pgstat_report_activity("<BIND>");
set_ps_display("BIND");
/*
* Start up a transaction command so we can call functions etc.
* (Note that this will normally change current memory context.)
* Nothing happens if we are already in one.
*/
start_xact_command();
/* Get the fixed part of the message */
portal_name = pq_getmsgstring(input_message);
stmt_name = pq_getmsgstring(input_message);
is_binary = pq_getmsgbyte(input_message);
numParams = pq_getmsgint(input_message, 4);
if (is_binary)
elog(ERROR, "Binary BIND not implemented yet");
/* Find prepared statement */
if (stmt_name[0] != '\0')
pstmt = FetchPreparedStatement(stmt_name, true);
else
{
/* special-case the unnamed statement */
pstmt = unnamed_stmt_pstmt;
if (!pstmt)
elog(ERROR, "Unnamed prepared statement does not exist");
}
if (numParams != length(pstmt->argtype_list))
elog(ERROR, "Bind message supplies %d parameters, but prepared statement \"%s\" requires %d",
numParams, stmt_name, length(pstmt->argtype_list));
/*
* Create the portal. Allow silent replacement of an existing portal
* only if the unnamed portal is specified.
*/
if (portal_name[0] == '\0')
portal = CreatePortal(portal_name, true, true);
else
portal = CreatePortal(portal_name, false, false);
PortalDefineQuery(portal,
pstmt->query_string,
pstmt->commandTag,
pstmt->query_list,
pstmt->plan_list,
pstmt->context);
/*
* Fetch parameters, if any, and store in the portal's memory context.
*
* In an aborted transaction, we can't risk calling user-defined functions,
* so bind all parameters to null values.
*/
if (numParams > 0)
{
bool isaborted = IsAbortedTransactionBlockState();
int i = 0;
List *l;
MemoryContext oldContext;
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
params = (ParamListInfo)
palloc0((numParams + 1) * sizeof(ParamListInfoData));
foreach(l, pstmt->argtype_list)
{
Oid ptype = lfirsto(l);
bool isNull;
isNull = (pq_getmsgbyte(input_message) != 0) ? false : true;
if (!isNull)
{
const char *ptext = pq_getmsgstring(input_message);
if (isaborted)
isNull = true;
else
{
Oid typInput;
Oid typElem;
getTypeInputInfo(ptype, &typInput, &typElem);
params[i].value =
OidFunctionCall3(typInput,
CStringGetDatum(ptext),
ObjectIdGetDatum(typElem),
Int32GetDatum(-1));
}
}
params[i].kind = PARAM_NUM;
params[i].id = i + 1;
params[i].isnull = isNull;
i++;
}
params[i].kind = PARAM_INVALID;
MemoryContextSwitchTo(oldContext);
}
else
params = NULL;
pq_getmsgend(input_message);
/*
* Start portal execution.
*/
PortalStart(portal, params);
/*
* Send BindComplete.
*/
if (whereToSendOutput == Remote)
pq_putemptymessage('2');
}
/*
* exec_execute_message
*
* Process an "Execute" message for a portal
*/
static void
exec_execute_message(const char *portal_name, int is_binary, long max_rows)
{
CommandDest dest;
Portal portal;
bool is_trans_stmt = false;
bool is_trans_exit = false;
bool completed;
char completionTag[COMPLETION_TAG_BUFSIZE];
/* Adjust destination to tell printtup.c what to do */
dest = whereToSendOutput;
if (dest == Remote)
dest = is_binary ? RemoteExecuteInternal : RemoteExecute;
portal = GetPortalByName(portal_name);
if (!PortalIsValid(portal))
elog(ERROR, "Portal \"%s\" not found", portal_name);
/*
* If the original query was a null string, just return EmptyQueryResponse.
*/
if (portal->commandTag == NULL)
{
Assert(portal->parseTrees == NIL);
NullCommand(dest);
return;
}
if (portal->sourceText)
{
debug_query_string = portal->sourceText;
pgstat_report_activity(portal->sourceText);
}
else
{
debug_query_string = "execute message";
pgstat_report_activity("<EXECUTE>");
}
set_ps_display(portal->commandTag);
BeginCommand(portal->commandTag, dest);
/* Check for transaction-control commands */
if (length(portal->parseTrees) == 1)
{
Query *query = (Query *) lfirst(portal->parseTrees);
if (query->commandType == CMD_UTILITY &&
query->utilityStmt != NULL &&
IsA(query->utilityStmt, TransactionStmt))
{
TransactionStmt *stmt = (TransactionStmt *) query->utilityStmt;
is_trans_stmt = true;
if (stmt->kind == TRANS_STMT_COMMIT ||
stmt->kind == TRANS_STMT_ROLLBACK)
is_trans_exit = true;
}
}
/*
* Ensure we are in a transaction command (this should normally be
* the case already due to prior BIND).
*/
start_xact_command();
/*
* If we are in aborted transaction state, the only portals we can
* actually run are those containing COMMIT or ROLLBACK commands.
*/
if (IsAbortedTransactionBlockState())
{
if (!is_trans_exit)
elog(ERROR, "current transaction is aborted, "
"queries ignored until end of transaction block");
}
/* Check for cancel signal before we start execution */
CHECK_FOR_INTERRUPTS();
/*
* Okay to run the portal.
*/
if (max_rows <= 0)
max_rows = FETCH_ALL;
completed = PortalRun(portal,
max_rows,
dest,
dest,
completionTag);
if (completed)
{
if (is_trans_stmt)
{
/*
* If this was a transaction control statement, commit it. We will
* start a new xact command for the next command (if any).
*/
finish_xact_command(true);
}
else
{
/*
* We need a CommandCounterIncrement after every query,
* except those that start or end a transaction block.
*/
CommandCounterIncrement();
}
/* Send appropriate CommandComplete to client */
EndCommand(completionTag, dest);
}
else
{
/* Portal run not complete, so send PortalSuspended */
if (whereToSendOutput == Remote)
pq_putemptymessage('s');
}
debug_query_string = NULL;
}
/*
* exec_describe_statement_message
*
* Process a "Describe" message for a prepared statement
*/
static void
exec_describe_statement_message(const char *stmt_name)
{
PreparedStatement *pstmt;
List *l;
StringInfoData buf;
/* Find prepared statement */
if (stmt_name[0] != '\0')
pstmt = FetchPreparedStatement(stmt_name, true);
else
{
/* special-case the unnamed statement */
pstmt = unnamed_stmt_pstmt;
if (!pstmt)
elog(ERROR, "Unnamed prepared statement does not exist");
}
if (whereToSendOutput != Remote)
return; /* can't actually do anything... */
pq_beginmessage(&buf, 't'); /* parameter description message type */
pq_sendint(&buf, length(pstmt->argtype_list), 4);
foreach(l, pstmt->argtype_list)
{
Oid ptype = lfirsto(l);
pq_sendint(&buf, (int) ptype, 4);
}
pq_endmessage(&buf);
}
/*
* exec_describe_portal_message
*
* Process a "Describe" message for a portal
*/
static void
exec_describe_portal_message(const char *portal_name)
{
Portal portal;
portal = GetPortalByName(portal_name);
if (!PortalIsValid(portal))
elog(ERROR, "Portal \"%s\" not found", portal_name);
if (whereToSendOutput != Remote)
return; /* can't actually do anything... */
if (portal->tupDesc)
SendRowDescriptionMessage(portal->tupDesc);
else
pq_putemptymessage('n'); /* NoData */
}
/*
* Convenience routines for starting/committing a single command.
*/
static void
start_xact_command(void)
{
elog(DEBUG1, "StartTransactionCommand");
StartTransactionCommand(false);
if (!xact_started)
{
elog(DEBUG2, "StartTransactionCommand");
StartTransactionCommand(false);
/* Set statement timeout running, if any */
if (StatementTimeout > 0)
enable_sig_alarm(StatementTimeout, true);
/* Set statement timeout running, if any */
if (StatementTimeout > 0)
enable_sig_alarm(StatementTimeout, true);
xact_started = true;
}
}
static void
finish_xact_command(bool forceCommit)
{
/* Invoke IMMEDIATE constraint triggers */
DeferredTriggerEndQuery();
if (xact_started)
{
/* Invoke IMMEDIATE constraint triggers */
DeferredTriggerEndQuery();
/* Cancel any active statement timeout before committing */
disable_sig_alarm(true);
/* Cancel any active statement timeout before committing */
disable_sig_alarm(true);
/* Now commit the command */
elog(DEBUG1, "CommitTransactionCommand");
/* Now commit the command */
elog(DEBUG2, "CommitTransactionCommand");
CommitTransactionCommand(forceCommit);
CommitTransactionCommand(forceCommit);
#ifdef SHOW_MEMORY_STATS
/* Print mem stats at each commit for leak tracking */
if (ShowStats)
MemoryContextStats(TopMemoryContext);
/* Print mem stats at each commit for leak tracking */
if (ShowStats)
MemoryContextStats(TopMemoryContext);
#endif
xact_started = false;
}
}
......@@ -1679,7 +2335,7 @@ PostgresMain(int argc, char *argv[], const char *username)
if (!IsUnderPostmaster)
{
puts("\nPOSTGRES backend interactive interface ");
puts("$Revision: 1.331 $ $Date: 2003/05/03 05:13:20 $\n");
puts("$Revision: 1.332 $ $Date: 2003/05/05 00:44:56 $\n");
}
/*
......@@ -1756,6 +2412,14 @@ PostgresMain(int argc, char *argv[], const char *username)
* successfully. (Flag was set in elog.c before longjmp().)
*/
InError = false;
xact_started = false;
/*
* If we were handling an extended-query-protocol message,
* initiate skip till next Sync.
*/
if (doing_extended_query_message)
ignore_till_sync = true;
/*
* Exit interrupt holdoff section we implicitly established above.
......@@ -1775,6 +2439,12 @@ PostgresMain(int argc, char *argv[], const char *username)
for (;;)
{
/*
* At top of loop, reset extended-query-message flag, so that
* any errors encountered in "idle" state don't provoke skip.
*/
doing_extended_query_message = false;
/*
* Release storage left over from prior query cycle, and create a
* new query input buffer in the cleared MessageContext.
......@@ -1853,20 +2523,74 @@ PostgresMain(int argc, char *argv[], const char *username)
}
/*
* (6) process the command.
* (6) process the command. But ignore it if we're skipping till Sync.
*/
if (ignore_till_sync)
continue;
switch (firstchar)
{
case 'Q': /* simple query */
{
const char *query_string = pq_getmsgstring(input_message);
const char *query_string;
query_string = pq_getmsgstring(input_message);
pq_getmsgend(input_message);
exec_simple_query(query_string, whereToSendOutput);
exec_simple_query(query_string);
send_rfq = true;
}
break;
case 'P': /* parse */
{
const char *stmt_name;
const char *query_string;
int numParams;
Oid *paramTypes = NULL;
stmt_name = pq_getmsgstring(input_message);
query_string = pq_getmsgstring(input_message);
numParams = pq_getmsgint(input_message, 4);
if (numParams > 0)
{
int i;
paramTypes = (Oid *) palloc(numParams * sizeof(Oid));
for (i = 0; i < numParams; i++)
paramTypes[i] = pq_getmsgint(input_message, 4);
}
pq_getmsgend(input_message);
exec_parse_message(query_string, stmt_name,
paramTypes, numParams);
}
break;
case 'B': /* bind */
/*
* this message is complex enough that it seems best to put
* the field extraction out-of-line
*/
exec_bind_message(input_message);
break;
case 'E': /* execute */
{
const char *portal_name;
int is_binary;
int max_rows;
portal_name = pq_getmsgstring(input_message);
is_binary = pq_getmsgbyte(input_message);
max_rows = pq_getmsgint(input_message, 4);
pq_getmsgend(input_message);
exec_execute_message(portal_name, is_binary, max_rows);
}
break;
case 'F': /* fastpath function call */
/* Tell the collector what we're doing */
pgstat_report_activity("<FASTPATH> function call");
......@@ -1894,6 +2618,89 @@ PostgresMain(int argc, char *argv[], const char *username)
send_rfq = true;
break;
case 'C': /* close */
{
int close_type;
const char *close_target;
close_type = pq_getmsgbyte(input_message);
close_target = pq_getmsgstring(input_message);
pq_getmsgend(input_message);
switch (close_type)
{
case 'S':
if (close_target[0] != '\0')
DropPreparedStatement(close_target, false);
else
{
/* special-case the unnamed statement */
unnamed_stmt_pstmt = NULL;
if (unnamed_stmt_context)
{
DropDependentPortals(unnamed_stmt_context);
MemoryContextDelete(unnamed_stmt_context);
}
unnamed_stmt_context = NULL;
}
break;
case 'P':
{
Portal portal;
portal = GetPortalByName(close_target);
if (PortalIsValid(portal))
PortalDrop(portal, false);
}
break;
default:
elog(ERROR, "Invalid Close message subtype %d",
close_type);
break;
}
if (whereToSendOutput == Remote)
pq_putemptymessage('3'); /* CloseComplete */
}
break;
case 'D': /* describe */
{
int describe_type;
const char *describe_target;
describe_type = pq_getmsgbyte(input_message);
describe_target = pq_getmsgstring(input_message);
pq_getmsgend(input_message);
switch (describe_type)
{
case 'S':
exec_describe_statement_message(describe_target);
break;
case 'P':
exec_describe_portal_message(describe_target);
break;
default:
elog(ERROR, "Invalid Describe message subtype %d",
describe_type);
break;
}
}
break;
case 'H': /* flush */
pq_getmsgend(input_message);
if (whereToSendOutput == Remote)
pq_flush();
break;
case 'S': /* sync */
pq_getmsgend(input_message);
finish_xact_command(false);
send_rfq = true;
break;
/*
* 'X' means that the frontend is closing down the socket.
* EOF means unexpected loss of frontend connection.
......
......@@ -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;
......
......@@ -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);
......
......@@ -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);
......
/*-------------------------------------------------------------------------
*
* 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 */
......@@ -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 */
......
......@@ -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;
/* ----------------
......
......@@ -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);
......
......@@ -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
*/
......
......@@ -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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment