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"> <chapter id="protocol">
<title>Frontend/Backend Protocol</title> <title>Frontend/Backend Protocol</title>
...@@ -595,7 +595,11 @@ ...@@ -595,7 +595,11 @@
<para> <para>
If successfully created, a named prepared-statement object lasts till If successfully created, a named prepared-statement object lasts till
the end of the current session, unless explicitly destroyed. An unnamed 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 Named prepared statements can also be created and accessed at the SQL
command level, using <command>PREPARE</> and <command>EXECUTE</>. command level, using <command>PREPARE</> and <command>EXECUTE</>.
</para> </para>
...@@ -611,10 +615,13 @@ ...@@ -611,10 +615,13 @@
</para> </para>
<para> <para>
If successfully created, a named portal object lasts till If successfully created, a named portal object lasts till the end of the
the end of the current transaction, unless explicitly destroyed. An current transaction, unless explicitly destroyed. An unnamed portal is
unnamed portal is destroyed at the end of the transaction, or as soon destroyed at the end of the transaction, or as soon as the next Bind
as the next Parse or Bind message is executed. 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 Named portals can also be created and accessed at the SQL
command level, using <command>DECLARE CURSOR</> and <command>FETCH</>. command level, using <command>DECLARE CURSOR</> and <command>FETCH</>.
</para> </para>
...@@ -691,17 +698,19 @@ ...@@ -691,17 +698,19 @@
The Describe message (statement variant) specifies the name of an existing The Describe message (statement variant) specifies the name of an existing
prepared statement (or an empty string for the unnamed prepared prepared statement (or an empty string for the unnamed prepared
statement). The response is a ParameterDescription message describing the statement). The response is a ParameterDescription message describing the
parameters needed by the statement (if any), followed by a RowDescription parameters needed by the statement. ErrorResponse is issued if there is
message describing the rows that will be returned when the statement is no such prepared statement. This message may be useful if the client
eventually executed (or NoData if there is no SELECT-type query in the library is uncertain about the parameters needed by a prepared statement.
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.
</para> </para>
<para> <para>
The Close message closes an existing prepared statement or portal 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>
<para> <para>
...@@ -709,19 +718,20 @@ ...@@ -709,19 +718,20 @@
but forces the backend to deliver any data pending in its output but forces the backend to deliver any data pending in its output
buffers. A Flush must be sent after any extended-query command except 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 Sync, if the frontend wishes to examine the results of that command before
issuing more commands. Without Flush, returning data will be combined issuing more commands. Without Flush, messages returned by the backend
into the minimum possible number of packets to minimize network overhead. will be combined into the minimum possible number of packets to minimize
network overhead.
</para> </para>
<note> <note>
<para> <para>
The simple Query message is approximately equivalent to the series Parse, The simple Query message is approximately equivalent to the series Parse,
Bind, portal Describe, Execute, Sync, using the unnamed prepared statement Bind, portal Describe, Execute, Close, Sync, using the unnamed prepared
and portal objects and no parameters. One difference is that it statement and portal objects and no parameters. One difference is that
will accept multiple SQL statements in the query string, automatically it will accept multiple SQL statements in the query string, automatically
performing the bind/describe/execute sequence for each one in succession. performing the bind/describe/execute sequence for each one in succession.
Another is that it will not return ParseComplete, BindComplete, or Another difference is that it will not return ParseComplete, BindComplete,
NoData messages. CloseComplete, or NoData messages.
</para> </para>
</note> </note>
</sect2> </sect2>
...@@ -1917,6 +1927,41 @@ Close (F) ...@@ -1917,6 +1927,41 @@ Close (F)
</VarListEntry> </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> <VarListEntry>
<Term> <Term>
CommandComplete (B) CommandComplete (B)
...@@ -3875,6 +3920,15 @@ The ReadyForQuery ('<literal>Z</>') message includes a transaction status ...@@ -3875,6 +3920,15 @@ The ReadyForQuery ('<literal>Z</>') message includes a transaction status
indicator. indicator.
</para> </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> <para>
COPY data is now encapsulated into CopyData and CopyDone messages. There COPY data is now encapsulated into CopyData and CopyDone messages. There
is a well-defined way to recover from errors during COPY. The special is a well-defined way to recover from errors during COPY. The special
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * 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 ...@@ -48,6 +48,7 @@ typedef struct
typedef struct typedef struct
{ {
DestReceiver pub; /* publicly-known function pointers */ DestReceiver pub; /* publicly-known function pointers */
bool sendDescrip; /* send RowDescription at startup? */
TupleDesc attrinfo; /* The attr info we are set up for */ TupleDesc attrinfo; /* The attr info we are set up for */
int nattrs; int nattrs;
PrinttupAttrInfo *myinfo; /* Cached info about each attr */ PrinttupAttrInfo *myinfo; /* Cached info about each attr */
...@@ -58,7 +59,7 @@ typedef struct ...@@ -58,7 +59,7 @@ typedef struct
* ---------------- * ----------------
*/ */
DestReceiver * DestReceiver *
printtup_create_DR(bool isBinary) printtup_create_DR(bool isBinary, bool sendDescrip)
{ {
DR_printtup *self = (DR_printtup *) palloc(sizeof(DR_printtup)); DR_printtup *self = (DR_printtup *) palloc(sizeof(DR_printtup));
...@@ -66,6 +67,8 @@ printtup_create_DR(bool isBinary) ...@@ -66,6 +67,8 @@ printtup_create_DR(bool isBinary)
self->pub.setup = printtup_setup; self->pub.setup = printtup_setup;
self->pub.cleanup = printtup_cleanup; self->pub.cleanup = printtup_cleanup;
self->sendDescrip = sendDescrip;
self->attrinfo = NULL; self->attrinfo = NULL;
self->nattrs = 0; self->nattrs = 0;
self->myinfo = NULL; self->myinfo = NULL;
...@@ -77,6 +80,8 @@ static void ...@@ -77,6 +80,8 @@ static void
printtup_setup(DestReceiver *self, int operation, printtup_setup(DestReceiver *self, int operation,
const char *portalName, TupleDesc typeinfo) const char *portalName, TupleDesc typeinfo)
{ {
DR_printtup *myState = (DR_printtup *) self;
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
{ {
/* /*
...@@ -91,41 +96,11 @@ printtup_setup(DestReceiver *self, int operation, ...@@ -91,41 +96,11 @@ printtup_setup(DestReceiver *self, int operation,
} }
/* /*
* if this is a retrieve, then we send back the tuple descriptor of * If this is a retrieve, and we are supposed to emit row descriptions,
* the tuples. * then we send back the tuple descriptor of the tuples.
*/ */
if (operation == CMD_SELECT) if (operation == CMD_SELECT && myState->sendDescrip)
{ SendRowDescriptionMessage(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);
}
/* ---------------- /* ----------------
* We could set up the derived attr info at this time, but we postpone it * 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, ...@@ -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 static void
printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs) printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
{ {
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -49,7 +49,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
* Disallow empty-string cursor name (conflicts with protocol-level * Disallow empty-string cursor name (conflicts with protocol-level
* unnamed portal). * unnamed portal).
*/ */
if (strlen(stmt->portalname) == 0) if (!stmt->portalname || stmt->portalname[0] == '\0')
elog(ERROR, "Invalid cursor name: must not be empty"); elog(ERROR, "Invalid cursor name: must not be empty");
/* /*
...@@ -148,6 +148,13 @@ PerformPortalFetch(FetchStmt *stmt, ...@@ -148,6 +148,13 @@ PerformPortalFetch(FetchStmt *stmt,
Portal portal; Portal portal;
long nprocessed; 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 */ /* get the portal from the portal name */
portal = GetPortalByName(stmt->portalname); portal = GetPortalByName(stmt->portalname);
if (!PortalIsValid(portal)) if (!PortalIsValid(portal))
...@@ -164,7 +171,9 @@ PerformPortalFetch(FetchStmt *stmt, ...@@ -164,7 +171,9 @@ PerformPortalFetch(FetchStmt *stmt,
* Adjust dest if needed. MOVE wants dest = None. * Adjust dest if needed. MOVE wants dest = None.
* *
* If fetching from a binary cursor and the requested destination is * 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) if (stmt->ismove)
dest = None; dest = None;
...@@ -189,10 +198,17 @@ PerformPortalFetch(FetchStmt *stmt, ...@@ -189,10 +198,17 @@ PerformPortalFetch(FetchStmt *stmt,
* Close a cursor. * Close a cursor.
*/ */
void void
PerformPortalClose(char *name) PerformPortalClose(const char *name)
{ {
Portal portal; 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 * get the portal from the portal name
*/ */
......
...@@ -3,10 +3,14 @@ ...@@ -3,10 +3,14 @@
* prepare.c * prepare.c
* Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE * 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 * 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 @@ ...@@ -25,31 +29,15 @@
#include "utils/memutils.h" #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 * The hash table in which prepared queries are stored. This is
* per-backend: query plans are not shared between backends. * per-backend: query plans are not shared between backends.
* The keys for this hash table are the arguments to PREPARE * The keys for this hash table are the arguments to PREPARE and EXECUTE
* and EXECUTE ("plan names"); the entries are QueryHashEntry structs. * (statement names); the entries are PreparedStatement structs.
*/ */
static HTAB *prepared_queries = NULL; static HTAB *prepared_queries = NULL;
static void InitQueryHashTable(void); 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, static ParamListInfo EvaluateParams(EState *estate,
List *params, List *argtypes); List *params, List *argtypes);
...@@ -59,14 +47,36 @@ static ParamListInfo EvaluateParams(EState *estate, ...@@ -59,14 +47,36 @@ static ParamListInfo EvaluateParams(EState *estate,
void void
PrepareQuery(PrepareStmt *stmt) PrepareQuery(PrepareStmt *stmt)
{ {
const char *commandTag;
List *query_list, List *query_list,
*plan_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) switch (stmt->query->commandType)
elog(ERROR, "Utility statements cannot be prepared"); {
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 * Parse analysis is already done, but we must still rewrite and plan
...@@ -80,7 +90,12 @@ PrepareQuery(PrepareStmt *stmt) ...@@ -80,7 +90,12 @@ PrepareQuery(PrepareStmt *stmt)
plan_list = pg_plan_queries(query_list, false); plan_list = pg_plan_queries(query_list, false);
/* Save the results. */ /* 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) ...@@ -89,7 +104,8 @@ PrepareQuery(PrepareStmt *stmt)
void void
ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
{ {
QueryHashEntry *entry; PreparedStatement *entry;
char *query_string;
List *query_list, List *query_list,
*plan_list; *plan_list;
MemoryContext qcontext; MemoryContext qcontext;
...@@ -98,8 +114,9 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) ...@@ -98,8 +114,9 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
Portal portal; Portal portal;
/* Look it up in the hash table */ /* 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; query_list = entry->query_list;
plan_list = entry->plan_list; plan_list = entry->plan_list;
qcontext = entry->context; qcontext = entry->context;
...@@ -135,6 +152,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) ...@@ -135,6 +152,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
if (query_string)
query_string = pstrdup(query_string);
query_list = copyObject(query_list); query_list = copyObject(query_list);
plan_list = copyObject(plan_list); plan_list = copyObject(plan_list);
qcontext = PortalGetHeapMemory(portal); qcontext = PortalGetHeapMemory(portal);
...@@ -150,8 +169,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) ...@@ -150,8 +169,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
} }
PortalDefineQuery(portal, PortalDefineQuery(portal,
NULL, /* XXX fixme: can we save query text? */ query_string,
NULL, /* no command tag known either */ entry->commandTag,
query_list, query_list,
plan_list, plan_list,
qcontext); qcontext);
...@@ -228,8 +247,8 @@ InitQueryHashTable(void) ...@@ -228,8 +247,8 @@ InitQueryHashTable(void)
MemSet(&hash_ctl, 0, sizeof(hash_ctl)); MemSet(&hash_ctl, 0, sizeof(hash_ctl));
hash_ctl.keysize = HASH_KEY_LEN; hash_ctl.keysize = NAMEDATALEN;
hash_ctl.entrysize = sizeof(QueryHashEntry); hash_ctl.entrysize = sizeof(PreparedStatement);
prepared_queries = hash_create("Prepared Queries", prepared_queries = hash_create("Prepared Queries",
32, 32,
...@@ -244,15 +263,24 @@ InitQueryHashTable(void) ...@@ -244,15 +263,24 @@ InitQueryHashTable(void)
* Store all the data pertaining to a query in the hash table using * 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 * 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. * 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 void
StoreQuery(const char *stmt_name, List *query_list, StorePreparedStatement(const char *stmt_name,
List *plan_list, List *argtype_list) const char *query_string,
const char *commandTag,
List *query_list,
List *plan_list,
List *argtype_list)
{ {
QueryHashEntry *entry; PreparedStatement *entry;
MemoryContext oldcxt, MemoryContext oldcxt,
entrycxt; entrycxt;
char key[HASH_KEY_LEN]; char *qstring;
char key[NAMEDATALEN];
bool found; bool found;
/* Initialize the hash table, if necessary */ /* Initialize the hash table, if necessary */
...@@ -260,7 +288,7 @@ StoreQuery(const char *stmt_name, List *query_list, ...@@ -260,7 +288,7 @@ StoreQuery(const char *stmt_name, List *query_list,
InitQueryHashTable(); InitQueryHashTable();
/* Check for pre-existing entry of same name */ /* Check for pre-existing entry of same name */
/* See notes in FetchQuery */ /* See notes in FetchPreparedStatement */
MemSet(key, 0, sizeof(key)); MemSet(key, 0, sizeof(key));
strncpy(key, stmt_name, sizeof(key)); strncpy(key, stmt_name, sizeof(key));
...@@ -285,15 +313,16 @@ StoreQuery(const char *stmt_name, List *query_list, ...@@ -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 * out-of-memory failure only wastes memory and doesn't leave us with
* an incomplete (ie corrupt) hashtable entry. * an incomplete (ie corrupt) hashtable entry.
*/ */
qstring = query_string ? pstrdup(query_string) : (char *) NULL;
query_list = (List *) copyObject(query_list); query_list = (List *) copyObject(query_list);
plan_list = (List *) copyObject(plan_list); plan_list = (List *) copyObject(plan_list);
argtype_list = listCopy(argtype_list); argtype_list = listCopy(argtype_list);
/* Now we can add entry to hash table */ /* Now we can add entry to hash table */
entry = (QueryHashEntry *) hash_search(prepared_queries, entry = (PreparedStatement *) hash_search(prepared_queries,
key, key,
HASH_ENTER, HASH_ENTER,
&found); &found);
/* Shouldn't get a failure, nor a duplicate entry */ /* Shouldn't get a failure, nor a duplicate entry */
if (!entry || found) if (!entry || found)
...@@ -301,6 +330,8 @@ StoreQuery(const char *stmt_name, List *query_list, ...@@ -301,6 +330,8 @@ StoreQuery(const char *stmt_name, List *query_list,
stmt_name); stmt_name);
/* Fill in the hash table entry with copied data */ /* Fill in the hash table entry with copied data */
entry->query_string = qstring;
entry->commandTag = commandTag;
entry->query_list = query_list; entry->query_list = query_list;
entry->plan_list = plan_list; entry->plan_list = plan_list;
entry->argtype_list = argtype_list; entry->argtype_list = argtype_list;
...@@ -311,52 +342,53 @@ StoreQuery(const char *stmt_name, List *query_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 * 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 * PreparedStatement *
FetchQuery(const char *plan_name) FetchPreparedStatement(const char *stmt_name, bool throwError)
{ {
char key[HASH_KEY_LEN]; char key[NAMEDATALEN];
QueryHashEntry *entry; PreparedStatement *entry;
/* /*
* If the hash table hasn't been initialized, it can't be storing * If the hash table hasn't been initialized, it can't be storing
* anything, therefore it couldn't possibly store our plan. * anything, therefore it couldn't possibly store our plan.
*/ */
if (!prepared_queries) 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
* We can't just use the statement name as supplied by the user: the * the appropriate length to work correctly.
* 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));
MemSet(key, 0, sizeof(key));
strncpy(key, plan_name, sizeof(key));
entry = (QueryHashEntry *) hash_search(prepared_queries, entry = (PreparedStatement *) hash_search(prepared_queries,
key, key,
HASH_FIND, HASH_FIND,
NULL); NULL);
}
else
entry = NULL;
if (!entry) if (!entry && throwError)
elog(ERROR, "Prepared statement with name \"%s\" does not exist", elog(ERROR, "Prepared statement with name \"%s\" does not exist",
plan_name); stmt_name);
return entry; 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. * If found, return the list of argument type OIDs.
*/ */
List * 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; return entry->argtype_list;
} }
...@@ -368,20 +400,34 @@ FetchQueryParams(const char *plan_name) ...@@ -368,20 +400,34 @@ FetchQueryParams(const char *plan_name)
void void
DeallocateQuery(DeallocateStmt *stmt) 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 */ /* Find the query's hash table entry; raise error if wanted */
entry = FetchQuery(stmt->name); entry = FetchPreparedStatement(stmt_name, showError);
/* Drop any open portals that depend on this prepared statement */ if (entry)
Assert(MemoryContextIsValid(entry->context)); {
DropDependentPortals(entry->context); /* Drop any open portals that depend on this prepared statement */
Assert(MemoryContextIsValid(entry->context));
DropDependentPortals(entry->context);
/* Flush the context holding the subsidiary data */ /* Flush the context holding the subsidiary data */
MemoryContextDelete(entry->context); MemoryContextDelete(entry->context);
/* Now we can remove the hash table entry */ /* Now we can remove the hash table entry */
hash_search(prepared_queries, entry->key, HASH_REMOVE, NULL); hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
}
} }
/* /*
...@@ -391,7 +437,7 @@ void ...@@ -391,7 +437,7 @@ void
ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate) ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
{ {
ExecuteStmt *execstmt = (ExecuteStmt *) stmt->query->utilityStmt; ExecuteStmt *execstmt = (ExecuteStmt *) stmt->query->utilityStmt;
QueryHashEntry *entry; PreparedStatement *entry;
List *l, List *l,
*query_list, *query_list,
*plan_list; *plan_list;
...@@ -402,7 +448,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate) ...@@ -402,7 +448,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
Assert(execstmt && IsA(execstmt, ExecuteStmt)); Assert(execstmt && IsA(execstmt, ExecuteStmt));
/* Look it up in the hash table */ /* Look it up in the hash table */
entry = FetchQuery(execstmt->name); entry = FetchPreparedStatement(execstmt->name, true);
query_list = entry->query_list; query_list = entry->query_list;
plan_list = entry->plan_list; plan_list = entry->plan_list;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -83,6 +83,12 @@ typedef struct
IndexStmt *pkey; /* PRIMARY KEY index, if any */ IndexStmt *pkey; /* PRIMARY KEY index, if any */
} CreateStmtContext; } CreateStmtContext;
typedef struct
{
Oid *paramTypes;
int numParams;
} check_parameter_resolution_context;
static List *do_parse_analyze(Node *parseTree, ParseState *pstate); static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
static Query *transformStmt(ParseState *pstate, Node *stmt, static Query *transformStmt(ParseState *pstate, Node *stmt,
...@@ -124,6 +130,8 @@ static void transformColumnType(ParseState *pstate, ColumnDef *column); ...@@ -124,6 +130,8 @@ static void transformColumnType(ParseState *pstate, ColumnDef *column);
static bool relationHasPrimaryKey(Oid relationOid); static bool relationHasPrimaryKey(Oid relationOid);
static void release_pstate_resources(ParseState *pstate); static void release_pstate_resources(ParseState *pstate);
static FromExpr *makeFromExpr(List *fromlist, Node *quals); 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) ...@@ -179,6 +187,16 @@ parse_analyze_varparams(Node *parseTree, Oid **paramTypes, int *numParams)
pfree(pstate); 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; return result;
} }
...@@ -2465,7 +2483,7 @@ transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt) ...@@ -2465,7 +2483,7 @@ transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
result->commandType = CMD_UTILITY; result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *) stmt; result->utilityStmt = (Node *) stmt;
paramtypes = FetchQueryParams(stmt->name); paramtypes = FetchPreparedStatementParams(stmt->name);
if (stmt->params || paramtypes) if (stmt->params || paramtypes)
{ {
...@@ -2879,3 +2897,44 @@ analyzeCreateSchemaStmt(CreateSchemaStmt *stmt) ...@@ -2879,3 +2897,44 @@ analyzeCreateSchemaStmt(CreateSchemaStmt *stmt)
return result; 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 @@ ...@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * 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) ...@@ -91,10 +91,20 @@ DestToFunction(CommandDest dest)
switch (dest) switch (dest)
{ {
case Remote: case Remote:
return printtup_create_DR(false); return printtup_create_DR(false, true);
case RemoteInternal: 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: case Debug:
return &debugtupDR; return &debugtupDR;
...@@ -104,9 +114,6 @@ DestToFunction(CommandDest dest) ...@@ -104,9 +114,6 @@ DestToFunction(CommandDest dest)
case Tuplestore: case Tuplestore:
return tstoreReceiverCreateDR(); return tstoreReceiverCreateDR();
case None:
return &donothingDR;
} }
/* should never get here */ /* should never get here */
...@@ -124,13 +131,15 @@ EndCommand(const char *commandTag, CommandDest dest) ...@@ -124,13 +131,15 @@ EndCommand(const char *commandTag, CommandDest dest)
{ {
case Remote: case Remote:
case RemoteInternal: case RemoteInternal:
case RemoteExecute:
case RemoteExecuteInternal:
pq_puttextmessage('C', commandTag); pq_puttextmessage('C', commandTag);
break; break;
case None: case None:
case Debug: case Debug:
case Tuplestore:
case SPI: case SPI:
case Tuplestore:
break; break;
} }
} }
...@@ -152,8 +161,10 @@ NullCommand(CommandDest dest) ...@@ -152,8 +161,10 @@ NullCommand(CommandDest dest)
{ {
switch (dest) switch (dest)
{ {
case RemoteInternal:
case Remote: case Remote:
case RemoteInternal:
case RemoteExecute:
case RemoteExecuteInternal:
/* /*
* tell the fe that we saw an empty query string. In protocols * tell the fe that we saw an empty query string. In protocols
...@@ -165,10 +176,10 @@ NullCommand(CommandDest dest) ...@@ -165,10 +176,10 @@ NullCommand(CommandDest dest)
pq_puttextmessage('I', ""); pq_puttextmessage('I', "");
break; break;
case None:
case Debug: case Debug:
case SPI:
case Tuplestore: case Tuplestore:
case None:
default:
break; break;
} }
} }
...@@ -189,8 +200,10 @@ ReadyForQuery(CommandDest dest) ...@@ -189,8 +200,10 @@ ReadyForQuery(CommandDest dest)
{ {
switch (dest) switch (dest)
{ {
case RemoteInternal:
case Remote: case Remote:
case RemoteInternal:
case RemoteExecute:
case RemoteExecuteInternal:
if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
{ {
StringInfoData buf; StringInfoData buf;
...@@ -205,10 +218,10 @@ ReadyForQuery(CommandDest dest) ...@@ -205,10 +218,10 @@ ReadyForQuery(CommandDest dest)
pq_flush(); pq_flush();
break; break;
case None:
case Debug: case Debug:
case SPI:
case Tuplestore: case Tuplestore:
case None:
default:
break; break;
} }
} }
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* This cruft is the server side of PQfn. * This cruft is the server side of PQfn.
...@@ -310,6 +310,14 @@ HandleFunctionRequest(StringInfo msgBuf) ...@@ -310,6 +310,14 @@ HandleFunctionRequest(StringInfo msgBuf)
if (aclresult != ACLCHECK_OK) if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_func_name(fid)); 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) if (fip->flinfo.fn_nargs != nargs || nargs > FUNC_MAX_ARGS)
elog(ERROR, "HandleFunctionRequest: actual arguments (%d) != registered arguments (%d)", elog(ERROR, "HandleFunctionRequest: actual arguments (%d) != registered arguments (%d)",
nargs, fip->flinfo.fn_nargs); nargs, fip->flinfo.fn_nargs);
...@@ -359,12 +367,8 @@ HandleFunctionRequest(StringInfo msgBuf) ...@@ -359,12 +367,8 @@ HandleFunctionRequest(StringInfo msgBuf)
} }
} }
/* /* Verify we reached the end of the message where expected. */
* Set up a query snapshot in case function needs one. (It is not safe pq_getmsgend(msgBuf);
* to do this if we are in transaction-abort state, so we have to postpone
* it till now. Ugh.)
*/
SetQuerySnapshot();
#ifdef NO_FASTPATH #ifdef NO_FASTPATH
/* force a NULL return */ /* force a NULL return */
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* this is the "main" module of the postgres backend and * this is the "main" module of the postgres backend and
...@@ -33,8 +33,11 @@ ...@@ -33,8 +33,11 @@
#include <getopt.h> #include <getopt.h>
#endif #endif
#include "access/printtup.h"
#include "access/xlog.h" #include "access/xlog.h"
#include "catalog/pg_type.h"
#include "commands/async.h" #include "commands/async.h"
#include "commands/prepare.h"
#include "commands/trigger.h" #include "commands/trigger.h"
#include "libpq/libpq.h" #include "libpq/libpq.h"
#include "libpq/pqformat.h" #include "libpq/pqformat.h"
...@@ -54,6 +57,7 @@ ...@@ -54,6 +57,7 @@
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
#include "tcop/utility.h" #include "tcop/utility.h"
#include "utils/guc.h" #include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h" #include "utils/memutils.h"
#include "utils/ps_status.h" #include "utils/ps_status.h"
#include "mb/pg_wchar.h" #include "mb/pg_wchar.h"
...@@ -82,7 +86,15 @@ bool InError = false; ...@@ -82,7 +86,15 @@ bool InError = false;
extern bool autocommit; 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 * Flag to mark SIGHUP. Whenever the main loop comes around it
...@@ -91,23 +103,41 @@ static bool EchoQuery = false; /* default don't echo */ ...@@ -91,23 +103,41 @@ static bool EchoQuery = false; /* default don't echo */
*/ */
static volatile bool got_SIGHUP = false; static volatile bool got_SIGHUP = false;
/* ---------------- /*
* people who want to use EOF should #define DONTUSENEWLINE in * Flag to keep track of whether we have started a transaction.
* tcop/tcopdebug.h * 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 #ifndef TCOP_DONTUSENEWLINE
int UseNewLine = 1; /* Use newlines query delimiters (the static int UseNewLine = 1; /* Use newlines query delimiters (the
* default) */ * default) */
#else #else
int UseNewLine = 0; /* Use EOF as query delimiters */ static int UseNewLine = 0; /* Use EOF as query delimiters */
#endif /* TCOP_DONTUSENEWLINE */ #endif /* TCOP_DONTUSENEWLINE */
/*
** Flags for expensive function optimization -- JMH 3/9/92
*/
int XfuncMode = 0;
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* decls for routines only used in this file * decls for routines only used in this file
...@@ -254,10 +284,14 @@ SocketBackend(StringInfo inBuf) ...@@ -254,10 +284,14 @@ SocketBackend(StringInfo inBuf)
* Validate message type code before trying to read body; if we have * 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 * lost sync, better to say "command unknown" than to run out of memory
* because we used garbage as a length word. * 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) switch (qtype)
{ {
case 'Q': /* simple query */ case 'Q': /* simple query */
doing_extended_query_message = false;
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
{ {
/* old style without length word; convert */ /* old style without length word; convert */
...@@ -270,15 +304,43 @@ SocketBackend(StringInfo inBuf) ...@@ -270,15 +304,43 @@ SocketBackend(StringInfo inBuf)
break; break;
case 'F': /* fastpath function call */ case 'F': /* fastpath function call */
/* we let fastpath.c cope with old-style input of this */
doing_extended_query_message = false;
break; break;
case 'X': /* terminate */ 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; break;
case 'd': /* copy data */ case 'd': /* copy data */
case 'c': /* copy done */ case 'c': /* copy done */
case 'f': /* copy fail */ 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; break;
default: default:
...@@ -410,9 +472,6 @@ List * ...@@ -410,9 +472,6 @@ List *
pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams) pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
{ {
List *querytree_list; List *querytree_list;
List *list_item;
Query *querytree;
List *new_list;
/* /*
* (1) Perform parse analysis. * (1) Perform parse analysis.
...@@ -423,21 +482,35 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams) ...@@ -423,21 +482,35 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
querytree_list = parse_analyze(parsetree, paramTypes, numParams); querytree_list = parse_analyze(parsetree, paramTypes, numParams);
if (log_parser_stats) if (log_parser_stats)
{
ShowUsage("PARSE ANALYSIS STATISTICS"); ShowUsage("PARSE ANALYSIS STATISTICS");
ResetUsage();
}
/* /*
* (2) Rewrite the queries, as necessary * (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 * rewritten queries are collected in new_list. Note there may be more
* or fewer than in the original list. * or fewer than in the original list.
*/ */
new_list = NIL;
foreach(list_item, querytree_list) foreach(list_item, querytree_list)
{ {
querytree = (Query *) lfirst(list_item); Query *querytree = (Query *) lfirst(list_item);
if (Debug_print_parse) if (Debug_print_parse)
elog_node_display(LOG, "parse tree", querytree, elog_node_display(LOG, "parse tree", querytree,
...@@ -471,7 +544,7 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams) ...@@ -471,7 +544,7 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
new_list = (List *) copyObject(querytree_list); new_list = (List *) copyObject(querytree_list);
/* This checks both copyObject() and the equal() routines... */ /* This checks both copyObject() and the equal() routines... */
if (!equal(new_list, querytree_list)) 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 else
querytree_list = new_list; querytree_list = new_list;
#endif #endif
...@@ -576,15 +649,13 @@ pg_plan_queries(List *querytrees, bool needSnapshot) ...@@ -576,15 +649,13 @@ pg_plan_queries(List *querytrees, bool needSnapshot)
/* /*
* exec_simple_query() * exec_simple_query
* *
* Execute a "simple Query" protocol message. * Execute a "simple Query" protocol message.
*/ */
static void static void
exec_simple_query(const char *query_string, /* string to execute */ exec_simple_query(const char *query_string)
CommandDest dest) /* where results should go */
{ {
bool xact_started;
MemoryContext oldcontext; MemoryContext oldcontext;
List *parsetree_list, List *parsetree_list,
*parsetree_item; *parsetree_item;
...@@ -619,13 +690,28 @@ exec_simple_query(const char *query_string, /* string to execute */ ...@@ -619,13 +690,28 @@ exec_simple_query(const char *query_string, /* string to execute */
* that this will normally change current memory context.) * that this will normally change current memory context.)
*/ */
start_xact_command(); 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. * Switch to appropriate context for constructing parsetrees.
*/ */
oldcontext = MemoryContextSwitchTo(MessageContext); oldcontext = MemoryContextSwitchTo(MessageContext);
QueryContext = CurrentMemoryContext;
/* /*
* Do basic parsing of the query or queries (this should be safe even * Do basic parsing of the query or queries (this should be safe even
* if we are in aborted transaction state!) * if we are in aborted transaction state!)
...@@ -659,7 +745,7 @@ exec_simple_query(const char *query_string, /* string to execute */ ...@@ -659,7 +745,7 @@ exec_simple_query(const char *query_string, /* string to execute */
set_ps_display(commandTag); set_ps_display(commandTag);
BeginCommand(commandTag, dest); BeginCommand(commandTag, whereToSendOutput);
/* /*
* If we are in an aborted transaction, reject all commands except * 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 */ ...@@ -688,11 +774,7 @@ exec_simple_query(const char *query_string, /* string to execute */
} }
/* Make sure we are in a transaction command */ /* Make sure we are in a transaction command */
if (!xact_started) start_xact_command();
{
start_xact_command();
xact_started = true;
}
/* If we got a cancel signal in parsing or prior command, quit */ /* If we got a cancel signal in parsing or prior command, quit */
CHECK_FOR_INTERRUPTS(); CHECK_FOR_INTERRUPTS();
...@@ -735,37 +817,40 @@ exec_simple_query(const char *query_string, /* string to execute */ ...@@ -735,37 +817,40 @@ exec_simple_query(const char *query_string, /* string to execute */
*/ */
PortalStart(portal, NULL); PortalStart(portal, NULL);
(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag); (void) PortalRun(portal,
FETCH_ALL,
whereToSendOutput,
whereToSendOutput,
completionTag);
PortalDrop(portal, false); 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) || if (IsA(parsetree, TransactionStmt) ||
IsA(parsetree, VariableSetStmt) || IsA(parsetree, VariableSetStmt) ||
IsA(parsetree, VariableShowStmt) || IsA(parsetree, VariableShowStmt) ||
IsA(parsetree, VariableResetStmt)) 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); 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) 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); finish_xact_command(false);
xact_started = false;
} }
else else
{ {
...@@ -783,20 +868,21 @@ exec_simple_query(const char *query_string, /* string to execute */ ...@@ -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 * (But a command aborted by error will not send an EndCommand
* report at all.) * report at all.)
*/ */
EndCommand(completionTag, dest); EndCommand(completionTag, whereToSendOutput);
} /* end loop over parsetrees */ } /* end loop over parsetrees */
/* /*
* If there were no parsetrees, return EmptyQueryResponse message. * If there were no parsetrees, return EmptyQueryResponse message.
*/ */
if (!parsetree_list) if (!parsetree_list)
NullCommand(dest); NullCommand(whereToSendOutput);
QueryContext = NULL;
/* /*
* Close down transaction statement, if one is open. * Close down transaction statement, if one is open.
*/ */
if (xact_started) finish_xact_command(false);
finish_xact_command(false);
/* /*
* Finish up monitoring. * Finish up monitoring.
...@@ -820,39 +906,609 @@ exec_simple_query(const char *query_string, /* string to execute */ ...@@ -820,39 +906,609 @@ exec_simple_query(const char *query_string, /* string to execute */
debug_query_string = NULL; 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. * Convenience routines for starting/committing a single command.
*/ */
static void static void
start_xact_command(void) start_xact_command(void)
{ {
elog(DEBUG1, "StartTransactionCommand"); if (!xact_started)
StartTransactionCommand(false); {
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 */ xact_started = true;
if (StatementTimeout > 0) }
enable_sig_alarm(StatementTimeout, true);
} }
static void static void
finish_xact_command(bool forceCommit) finish_xact_command(bool forceCommit)
{ {
/* Invoke IMMEDIATE constraint triggers */ if (xact_started)
DeferredTriggerEndQuery(); {
/* Invoke IMMEDIATE constraint triggers */
DeferredTriggerEndQuery();
/* Cancel any active statement timeout before committing */ /* Cancel any active statement timeout before committing */
disable_sig_alarm(true); disable_sig_alarm(true);
/* Now commit the command */ /* Now commit the command */
elog(DEBUG1, "CommitTransactionCommand"); elog(DEBUG2, "CommitTransactionCommand");
CommitTransactionCommand(forceCommit); CommitTransactionCommand(forceCommit);
#ifdef SHOW_MEMORY_STATS #ifdef SHOW_MEMORY_STATS
/* Print mem stats at each commit for leak tracking */ /* Print mem stats at each commit for leak tracking */
if (ShowStats) if (ShowStats)
MemoryContextStats(TopMemoryContext); MemoryContextStats(TopMemoryContext);
#endif #endif
xact_started = false;
}
} }
...@@ -1679,7 +2335,7 @@ PostgresMain(int argc, char *argv[], const char *username) ...@@ -1679,7 +2335,7 @@ PostgresMain(int argc, char *argv[], const char *username)
if (!IsUnderPostmaster) if (!IsUnderPostmaster)
{ {
puts("\nPOSTGRES backend interactive interface "); 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) ...@@ -1756,6 +2412,14 @@ PostgresMain(int argc, char *argv[], const char *username)
* successfully. (Flag was set in elog.c before longjmp().) * successfully. (Flag was set in elog.c before longjmp().)
*/ */
InError = false; 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. * Exit interrupt holdoff section we implicitly established above.
...@@ -1775,6 +2439,12 @@ PostgresMain(int argc, char *argv[], const char *username) ...@@ -1775,6 +2439,12 @@ PostgresMain(int argc, char *argv[], const char *username)
for (;;) 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 * Release storage left over from prior query cycle, and create a
* new query input buffer in the cleared MessageContext. * new query input buffer in the cleared MessageContext.
...@@ -1853,20 +2523,74 @@ PostgresMain(int argc, char *argv[], const char *username) ...@@ -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) switch (firstchar)
{ {
case 'Q': /* simple query */ 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; send_rfq = true;
} }
break; 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 */ case 'F': /* fastpath function call */
/* Tell the collector what we're doing */ /* Tell the collector what we're doing */
pgstat_report_activity("<FASTPATH> function call"); pgstat_report_activity("<FASTPATH> function call");
...@@ -1894,6 +2618,89 @@ PostgresMain(int argc, char *argv[], const char *username) ...@@ -1894,6 +2618,89 @@ PostgresMain(int argc, char *argv[], const char *username)
send_rfq = true; send_rfq = true;
break; 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. * 'X' means that the frontend is closing down the socket.
* EOF means unexpected loss of frontend connection. * EOF means unexpected loss of frontend connection.
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * 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) ...@@ -222,11 +222,12 @@ CreateNewPortal(void)
* PortalDefineQuery * PortalDefineQuery
* A simple subroutine to establish a portal's query. * A simple subroutine to establish a portal's query.
* *
* Notes: the passed commandTag must be a pointer to a constant string, * Notes: commandTag shall be NULL if and only if the original query string
* since it is not copied. The caller is responsible for ensuring that * (before rewriting) was an empty string. Also, the passed commandTag must
* the passed sourceText (if any), parse and plan trees have adequate * be a pointer to a constant string, since it is not copied. The caller is
* lifetime. Also, queryContext must accurately describe the location * responsible for ensuring that the passed sourceText (if any), parse and
* of the parse and plan trees. * plan trees have adequate lifetime. Also, queryContext must accurately
* describe the location of the parse and plan trees.
*/ */
void void
PortalDefineQuery(Portal portal, PortalDefineQuery(Portal portal,
...@@ -241,6 +242,8 @@ PortalDefineQuery(Portal portal, ...@@ -241,6 +242,8 @@ PortalDefineQuery(Portal portal,
Assert(length(parseTrees) == length(planTrees)); Assert(length(parseTrees) == length(planTrees));
Assert(commandTag != NULL || parseTrees == NIL);
portal->sourceText = sourceText; portal->sourceText = sourceText;
portal->commandTag = commandTag; portal->commandTag = commandTag;
portal->parseTrees = parseTrees; portal->parseTrees = parseTrees;
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 @@ ...@@ -16,7 +16,9 @@
#include "tcop/dest.h" #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, extern void debugSetup(DestReceiver *self, int operation,
const char *portalName, TupleDesc typeinfo); const char *portalName, TupleDesc typeinfo);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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); ...@@ -22,7 +22,7 @@ extern void PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest);
extern void PerformPortalFetch(FetchStmt *stmt, CommandDest dest, extern void PerformPortalFetch(FetchStmt *stmt, CommandDest dest,
char *completionTag); char *completionTag);
extern void PerformPortalClose(char *name); extern void PerformPortalClose(const char *name);
extern void PortalCleanup(Portal portal, bool isError); extern void PortalCleanup(Portal portal, bool isError);
......
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* *
* prepare.h * 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 @@ ...@@ -18,10 +18,44 @@
#include "tcop/dest.h" #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 PrepareQuery(PrepareStmt *stmt);
extern void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest); extern void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest);
extern void DeallocateQuery(DeallocateStmt *stmt); extern void DeallocateQuery(DeallocateStmt *stmt);
extern List *FetchQueryParams(const char *plan_name);
extern void ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate); 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 */ #endif /* PREPARE_H */
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -106,7 +106,7 @@ typedef union SockAddr
/* The earliest and latest frontend/backend protocol version supported. */ /* The earliest and latest frontend/backend protocol version supported. */
#define PG_PROTOCOL_EARLIEST PG_PROTOCOL(1,0) #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 */ typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 @@ ...@@ -61,6 +61,10 @@
/* ---------------- /* ----------------
* CommandDest is a simplistic means of identifying the desired * CommandDest is a simplistic means of identifying the desired
* destination. Someday this will probably need to be improved. * 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 typedef enum
...@@ -71,7 +75,9 @@ typedef enum ...@@ -71,7 +75,9 @@ typedef enum
RemoteInternal, /* results sent to frontend process in RemoteInternal, /* results sent to frontend process in
* internal (binary) form */ * internal (binary) form */
SPI, /* results sent to SPI manager */ 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; } CommandDest;
/* ---------------- /* ----------------
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 * OLD COMMENTS
* This file was created so that other c files could get the two * This file was created so that other c files could get the two
...@@ -35,11 +35,12 @@ extern DLLIMPORT const char *debug_query_string; ...@@ -35,11 +35,12 @@ extern DLLIMPORT const char *debug_query_string;
#ifndef BOOTSTRAP_INCLUDE #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_parse_query(const char *query_string);
extern List *pg_analyze_and_rewrite(Node *parsetree, extern List *pg_analyze_and_rewrite(Node *parsetree,
Oid *paramTypes, int numParams); Oid *paramTypes, int numParams);
extern List *pg_parse_and_rewrite(const char *query_string, extern List *pg_rewrite_queries(List *querytree_list);
Oid *paramTypes, int numParams);
extern Plan *pg_plan_query(Query *querytree); extern Plan *pg_plan_query(Query *querytree);
extern List *pg_plan_queries(List *querytrees, bool needSnapshot); extern List *pg_plan_queries(List *querytrees, bool needSnapshot);
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -375,6 +375,17 @@ connectOptions1(PGconn *conn, const char *conninfo)
static bool static bool
connectOptions2(PGconn *conn) 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 * Supply default password if none given
*/ */
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -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. * 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. * 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