From 16503e6fa4a13051debe09698b6db9ce0d509af8 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 5 May 2003 00:44:56 +0000 Subject: [PATCH] Extended query protocol: parse, bind, execute, describe FE/BE messages. Only lightly tested as yet, since libpq doesn't know anything about 'em. --- doc/src/sgml/protocol.sgml | 94 ++- src/backend/access/common/printtup.c | 84 ++- src/backend/commands/portalcmds.c | 24 +- src/backend/commands/prepare.c | 208 +++--- src/backend/parser/analyze.c | 63 +- src/backend/tcop/dest.c | 43 +- src/backend/tcop/fastpath.c | 18 +- src/backend/tcop/postgres.c | 965 ++++++++++++++++++++++++--- src/backend/utils/mmgr/portalmem.c | 15 +- src/include/access/printtup.h | 6 +- src/include/commands/portalcmds.h | 4 +- src/include/commands/prepare.h | 42 +- src/include/libpq/pqcomm.h | 4 +- src/include/tcop/dest.h | 10 +- src/include/tcop/tcopprot.h | 7 +- src/interfaces/libpq/fe-connect.c | 13 +- src/interfaces/libpq/libpq-int.h | 4 +- 17 files changed, 1336 insertions(+), 268 deletions(-) diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 4a01e310b3..48ba74edb5 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1,4 +1,4 @@ - + Frontend/Backend Protocol @@ -595,7 +595,11 @@ 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 PREPARE and EXECUTE. @@ -611,10 +615,13 @@ - 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 DECLARE CURSOR and FETCH. @@ -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. 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. @@ -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. 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. @@ -1917,6 +1927,41 @@ Close (F) + + +CloseComplete (B) + + + + + + + + Byte1('3') + + + + Identifies the message as a Close-complete indicator. + + + + + + Int32(4) + + + + Length of message contents in bytes, including self. + + + + + + + + + + CommandComplete (B) @@ -3875,6 +3920,15 @@ The ReadyForQuery ('Z') message includes a transaction status indicator. + +There is a new 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. + + COPY data is now encapsulated into CopyData and CopyDone messages. There is a well-defined way to recover from errors during COPY. The special diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c index 160b703223..584233c187 100644 --- a/src/backend/access/common/printtup.c +++ b/src/backend/access/common/printtup.c @@ -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) { diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 35ed8a270b..82058ff5d1 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -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 */ diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 5a3e3f589d..3f8beac53c 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -3,10 +3,14 @@ * prepare.c * Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE * - * Copyright (c) 2002, PostgreSQL Global Development Group + * This module also implements storage of prepared statements that are + * accessed via the extended FE/BE query protocol. + * + * + * Copyright (c) 2002-2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.14 2003/05/02 20:54:33 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.15 2003/05/05 00:44:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,31 +29,15 @@ #include "utils/memutils.h" -#define HASH_KEY_LEN NAMEDATALEN - -/* All the data we need to remember about a stored query */ -typedef struct -{ - /* dynahash.c requires key to be first field */ - char key[HASH_KEY_LEN]; - List *query_list; /* list of queries */ - List *plan_list; /* list of plans */ - List *argtype_list; /* list of parameter type OIDs */ - MemoryContext context; /* context containing this query */ -} QueryHashEntry; - /* * The hash table in which prepared queries are stored. This is * per-backend: query plans are not shared between backends. - * The keys for this hash table are the arguments to PREPARE - * and EXECUTE ("plan names"); the entries are QueryHashEntry structs. + * The keys for this hash table are the arguments to PREPARE and EXECUTE + * (statement names); the entries are PreparedStatement structs. */ static HTAB *prepared_queries = NULL; static void InitQueryHashTable(void); -static void StoreQuery(const char *stmt_name, List *query_list, - List *plan_list, List *argtype_list); -static QueryHashEntry *FetchQuery(const char *plan_name); static ParamListInfo EvaluateParams(EState *estate, List *params, List *argtypes); @@ -59,14 +47,36 @@ static ParamListInfo EvaluateParams(EState *estate, void PrepareQuery(PrepareStmt *stmt) { + const char *commandTag; List *query_list, *plan_list; - if (!stmt->name) - elog(ERROR, "No statement name given"); + /* + * Disallow empty-string statement name (conflicts with protocol-level + * unnamed statement). + */ + if (!stmt->name || stmt->name[0] == '\0') + elog(ERROR, "Invalid statement name: must not be empty"); - if (stmt->query->commandType == CMD_UTILITY) - elog(ERROR, "Utility statements cannot be prepared"); + switch (stmt->query->commandType) + { + case CMD_SELECT: + commandTag = "SELECT"; + break; + case CMD_INSERT: + commandTag = "INSERT"; + break; + case CMD_UPDATE: + commandTag = "UPDATE"; + break; + case CMD_DELETE: + commandTag = "DELETE"; + break; + default: + elog(ERROR, "Utility statements cannot be prepared"); + commandTag = NULL; /* keep compiler quiet */ + break; + } /* * Parse analysis is already done, but we must still rewrite and plan @@ -80,7 +90,12 @@ PrepareQuery(PrepareStmt *stmt) plan_list = pg_plan_queries(query_list, false); /* Save the results. */ - StoreQuery(stmt->name, query_list, plan_list, stmt->argtype_oids); + StorePreparedStatement(stmt->name, + NULL, /* text form not available */ + commandTag, + query_list, + plan_list, + stmt->argtype_oids); } /* @@ -89,7 +104,8 @@ PrepareQuery(PrepareStmt *stmt) void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) { - QueryHashEntry *entry; + PreparedStatement *entry; + char *query_string; List *query_list, *plan_list; MemoryContext qcontext; @@ -98,8 +114,9 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) Portal portal; /* Look it up in the hash table */ - entry = FetchQuery(stmt->name); + entry = FetchPreparedStatement(stmt->name, true); + query_string = entry->query_string; query_list = entry->query_list; plan_list = entry->plan_list; qcontext = entry->context; @@ -135,6 +152,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); + if (query_string) + query_string = pstrdup(query_string); query_list = copyObject(query_list); plan_list = copyObject(plan_list); qcontext = PortalGetHeapMemory(portal); @@ -150,8 +169,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) } PortalDefineQuery(portal, - NULL, /* XXX fixme: can we save query text? */ - NULL, /* no command tag known either */ + query_string, + entry->commandTag, query_list, plan_list, qcontext); @@ -228,8 +247,8 @@ InitQueryHashTable(void) MemSet(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = HASH_KEY_LEN; - hash_ctl.entrysize = sizeof(QueryHashEntry); + hash_ctl.keysize = NAMEDATALEN; + hash_ctl.entrysize = sizeof(PreparedStatement); prepared_queries = hash_create("Prepared Queries", 32, @@ -244,15 +263,24 @@ InitQueryHashTable(void) * Store all the data pertaining to a query in the hash table using * the specified key. A copy of the data is made in a memory context belonging * to the hash entry, so the caller can dispose of their copy. + * + * Exception: commandTag is presumed to be a pointer to a constant string, + * or possibly NULL, so it need not be copied. Note that commandTag should + * be NULL only if the original query (before rewriting) was empty. */ -static void -StoreQuery(const char *stmt_name, List *query_list, - List *plan_list, List *argtype_list) +void +StorePreparedStatement(const char *stmt_name, + const char *query_string, + const char *commandTag, + List *query_list, + List *plan_list, + List *argtype_list) { - QueryHashEntry *entry; + PreparedStatement *entry; MemoryContext oldcxt, entrycxt; - char key[HASH_KEY_LEN]; + char *qstring; + char key[NAMEDATALEN]; bool found; /* Initialize the hash table, if necessary */ @@ -260,7 +288,7 @@ StoreQuery(const char *stmt_name, List *query_list, InitQueryHashTable(); /* Check for pre-existing entry of same name */ - /* See notes in FetchQuery */ + /* See notes in FetchPreparedStatement */ MemSet(key, 0, sizeof(key)); strncpy(key, stmt_name, sizeof(key)); @@ -285,15 +313,16 @@ StoreQuery(const char *stmt_name, List *query_list, * out-of-memory failure only wastes memory and doesn't leave us with * an incomplete (ie corrupt) hashtable entry. */ + qstring = query_string ? pstrdup(query_string) : (char *) NULL; query_list = (List *) copyObject(query_list); plan_list = (List *) copyObject(plan_list); argtype_list = listCopy(argtype_list); /* Now we can add entry to hash table */ - entry = (QueryHashEntry *) hash_search(prepared_queries, - key, - HASH_ENTER, - &found); + entry = (PreparedStatement *) hash_search(prepared_queries, + key, + HASH_ENTER, + &found); /* Shouldn't get a failure, nor a duplicate entry */ if (!entry || found) @@ -301,6 +330,8 @@ StoreQuery(const char *stmt_name, List *query_list, stmt_name); /* Fill in the hash table entry with copied data */ + entry->query_string = qstring; + entry->commandTag = commandTag; entry->query_list = query_list; entry->plan_list = plan_list; entry->argtype_list = argtype_list; @@ -311,52 +342,53 @@ StoreQuery(const char *stmt_name, List *query_list, /* * Lookup an existing query in the hash table. If the query does not - * actually exist, an elog(ERROR) is thrown. + * actually exist, throw elog(ERROR) or return NULL per second parameter. */ -static QueryHashEntry * -FetchQuery(const char *plan_name) +PreparedStatement * +FetchPreparedStatement(const char *stmt_name, bool throwError) { - char key[HASH_KEY_LEN]; - QueryHashEntry *entry; + char key[NAMEDATALEN]; + PreparedStatement *entry; /* * If the hash table hasn't been initialized, it can't be storing * anything, therefore it couldn't possibly store our plan. */ - if (!prepared_queries) + if (prepared_queries) + { + /* + * We can't just use the statement name as supplied by the user: the + * hash package is picky enough that it needs to be NULL-padded out to + * the appropriate length to work correctly. + */ + MemSet(key, 0, sizeof(key)); + strncpy(key, stmt_name, sizeof(key)); + + entry = (PreparedStatement *) hash_search(prepared_queries, + key, + HASH_FIND, + NULL); + } + else + entry = NULL; + + if (!entry && throwError) elog(ERROR, "Prepared statement with name \"%s\" does not exist", - plan_name); - - /* - * We can't just use the statement name as supplied by the user: the - * hash package is picky enough that it needs to be NULL-padded out to - * the appropriate length to work correctly. - */ - MemSet(key, 0, sizeof(key)); - strncpy(key, plan_name, sizeof(key)); - - entry = (QueryHashEntry *) hash_search(prepared_queries, - key, - HASH_FIND, - NULL); - - if (!entry) - elog(ERROR, "Prepared statement with name \"%s\" does not exist", - plan_name); + stmt_name); return entry; } /* - * Given a plan name, look up the stored plan (giving error if not found). + * Look up a prepared statement given the name (giving error if not found). * If found, return the list of argument type OIDs. */ List * -FetchQueryParams(const char *plan_name) +FetchPreparedStatementParams(const char *stmt_name) { - QueryHashEntry *entry; + PreparedStatement *entry; - entry = FetchQuery(plan_name); + entry = FetchPreparedStatement(stmt_name, true); return entry->argtype_list; } @@ -368,20 +400,34 @@ FetchQueryParams(const char *plan_name) void DeallocateQuery(DeallocateStmt *stmt) { - QueryHashEntry *entry; + DropPreparedStatement(stmt->name, true); +} - /* Find the query's hash table entry */ - entry = FetchQuery(stmt->name); +/* + * Internal version of DEALLOCATE + * + * If showError is false, dropping a nonexistent statement is a no-op. + */ +void +DropPreparedStatement(const char *stmt_name, bool showError) +{ + PreparedStatement *entry; - /* Drop any open portals that depend on this prepared statement */ - Assert(MemoryContextIsValid(entry->context)); - DropDependentPortals(entry->context); + /* Find the query's hash table entry; raise error if wanted */ + entry = FetchPreparedStatement(stmt_name, showError); - /* Flush the context holding the subsidiary data */ - MemoryContextDelete(entry->context); + if (entry) + { + /* Drop any open portals that depend on this prepared statement */ + Assert(MemoryContextIsValid(entry->context)); + DropDependentPortals(entry->context); - /* Now we can remove the hash table entry */ - hash_search(prepared_queries, entry->key, HASH_REMOVE, NULL); + /* Flush the context holding the subsidiary data */ + MemoryContextDelete(entry->context); + + /* Now we can remove the hash table entry */ + hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL); + } } /* @@ -391,7 +437,7 @@ void ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate) { ExecuteStmt *execstmt = (ExecuteStmt *) stmt->query->utilityStmt; - QueryHashEntry *entry; + PreparedStatement *entry; List *l, *query_list, *plan_list; @@ -402,7 +448,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate) Assert(execstmt && IsA(execstmt, ExecuteStmt)); /* Look it up in the hash table */ - entry = FetchQuery(execstmt->name); + entry = FetchPreparedStatement(execstmt->name, true); query_list = entry->query_list; plan_list = entry->plan_list; diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index a488d1d91e..ad2d5ab568 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.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); +} diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index 41906a348a..a5905dedc7 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.54 2003/04/26 20:22:59 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.55 2003/05/05 00:44:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -91,10 +91,20 @@ DestToFunction(CommandDest dest) switch (dest) { case Remote: - return printtup_create_DR(false); + return printtup_create_DR(false, true); case RemoteInternal: - return printtup_create_DR(true); + return printtup_create_DR(true, true); + + case RemoteExecute: + /* like Remote, but suppress output of T message */ + return printtup_create_DR(false, false); + + case RemoteExecuteInternal: + return printtup_create_DR(true, false); + + case None: + return &donothingDR; case Debug: return &debugtupDR; @@ -104,9 +114,6 @@ DestToFunction(CommandDest dest) case Tuplestore: return tstoreReceiverCreateDR(); - - case None: - return &donothingDR; } /* should never get here */ @@ -124,13 +131,15 @@ EndCommand(const char *commandTag, CommandDest dest) { case Remote: case RemoteInternal: + case RemoteExecute: + case RemoteExecuteInternal: pq_puttextmessage('C', commandTag); break; case None: case Debug: - case Tuplestore: case SPI: + case Tuplestore: break; } } @@ -152,8 +161,10 @@ NullCommand(CommandDest dest) { switch (dest) { - case RemoteInternal: case Remote: + case RemoteInternal: + case RemoteExecute: + case RemoteExecuteInternal: /* * tell the fe that we saw an empty query string. In protocols @@ -165,10 +176,10 @@ NullCommand(CommandDest dest) pq_puttextmessage('I', ""); break; - case Debug: - case Tuplestore: case None: - default: + case Debug: + case SPI: + case Tuplestore: break; } } @@ -189,8 +200,10 @@ ReadyForQuery(CommandDest dest) { switch (dest) { - case RemoteInternal: case Remote: + case RemoteInternal: + case RemoteExecute: + case RemoteExecuteInternal: if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) { StringInfoData buf; @@ -205,10 +218,10 @@ ReadyForQuery(CommandDest dest) pq_flush(); break; - case Debug: - case Tuplestore: case None: - default: + case Debug: + case SPI: + case Tuplestore: break; } } diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c index 78fcfdb7e0..65161c54ff 100644 --- a/src/backend/tcop/fastpath.c +++ b/src/backend/tcop/fastpath.c @@ -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 */ diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index b60898270a..d57ccd973b 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.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 #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. */ -#ifndef TCOP_DONTUSENEWLINE -int UseNewLine = 1; /* Use newlines query delimiters (the - * default) */ - -#else -int UseNewLine = 0; /* Use EOF as query delimiters */ -#endif /* TCOP_DONTUSENEWLINE */ +static bool xact_started = false; /* -** Flags for expensive function optimization -- JMH 3/9/92 -*/ -int XfuncMode = 0; + * 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 +static int UseNewLine = 1; /* Use newlines query delimiters (the + * default) */ +#else +static int UseNewLine = 0; /* Use EOF as query delimiters */ +#endif /* TCOP_DONTUSENEWLINE */ + /* ---------------------------------------------------------------- * 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, + ¶mTypes, + &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(""); + + 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(""); + } + + 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; - exec_simple_query(query_string, whereToSendOutput); + query_string = pq_getmsgstring(input_message); + pq_getmsgend(input_message); + + 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(" 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. diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 974e69a2f1..2fb62f4aba 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -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; diff --git a/src/include/access/printtup.h b/src/include/access/printtup.h index 728b0c8990..688a75cd2d 100644 --- a/src/include/access/printtup.h +++ b/src/include/access/printtup.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: 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); diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h index efa60869fa..f89ac36fa5 100644 --- a/src/include/commands/portalcmds.h +++ b/src/include/commands/portalcmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: 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); diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index aad6416675..5d24c579a4 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * * prepare.h - * PREPARE, EXECUTE and DEALLOCATE command prototypes + * PREPARE, EXECUTE and DEALLOCATE commands, and prepared-stmt storage * * - * Copyright (c) 2002, PostgreSQL Global Development Group + * Copyright (c) 2002-2003, PostgreSQL Global Development Group * - * $Id: prepare.h,v 1.3 2003/02/02 23:46:38 tgl Exp $ + * $Id: prepare.h,v 1.4 2003/05/05 00:44:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,10 +18,44 @@ #include "tcop/dest.h" +/* + * The data structure representing a prepared statement + * + * Note: all subsidiary storage lives in the context denoted by the context + * field. However, the string referenced by commandTag is not subsidiary + * storage; it is assumed to be a compile-time-constant string. As with + * portals, commandTag shall be NULL if and only if the original query string + * (before rewriting) was an empty string. + */ +typedef struct +{ + /* dynahash.c requires key to be first field */ + char stmt_name[NAMEDATALEN]; + char *query_string; /* text of query, or NULL */ + const char *commandTag; /* command tag (a constant!), or NULL */ + List *query_list; /* list of queries */ + List *plan_list; /* list of plans */ + List *argtype_list; /* list of parameter type OIDs */ + MemoryContext context; /* context containing this query */ +} PreparedStatement; + + +/* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */ extern void PrepareQuery(PrepareStmt *stmt); extern void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest); extern void DeallocateQuery(DeallocateStmt *stmt); -extern List *FetchQueryParams(const char *plan_name); extern void ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate); +/* Low-level access to stored prepared statements */ +extern void StorePreparedStatement(const char *stmt_name, + const char *query_string, + const char *commandTag, + List *query_list, + List *plan_list, + List *argtype_list); +extern PreparedStatement *FetchPreparedStatement(const char *stmt_name, + bool throwError); +extern void DropPreparedStatement(const char *stmt_name, bool showError); +extern List *FetchPreparedStatementParams(const char *stmt_name); + #endif /* PREPARE_H */ diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index d5374a68fe..b6961e824c 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.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 */ diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h index 39063af6e1..5fbe9d33af 100644 --- a/src/include/tcop/dest.h +++ b/src/include/tcop/dest.h @@ -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; /* ---------------- diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index c1fa9c1a6d..b5e171e1d3 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: 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); diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 3bf27da427..75bcb43433 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -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 */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index a5e6bceef4..3fcecd63e2 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -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.