Re: [INTERFACES] Revised proposal for libpq and FE/BE protocol changes - Mailing list pgsql-hackers

From watts@humbug.antnet.com
Subject Re: [INTERFACES] Revised proposal for libpq and FE/BE protocol changes
Date
Msg-id 199804282053.PAA21962@humbug.antnet.com
Whole thread Raw
In response to Revised proposal for libpq and FE/BE protocol changes  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: [HACKERS] Re: [INTERFACES] Revised proposal for libpq and FE/BE protocol changes
List pgsql-hackers
I suggest the application already has fork or fork/exec to
implement an  asynchronous design. Does that also keep the
socket out of the application's domain?

Bob
watts@humbug.antnet.com

Received: from hub.org (hub.org [209.47.148.200])
    by humbug.antnet.com (8.8.5/8.8.5) with ESMTP id LAA21503
    for <watts@humbug.antnet.com>; Tue, 28 Apr 1998 11:28:48 -0500 (CDT)
Received: from localhost (majordom@localhost) by hub.org (8.8.8/8.7.5) with SMTP id MAA01511; Tue, 28 Apr 1998 12:23:18
-0400(EDT) 
Received: by hub.org (TLB v0.10a (1.23 tibbs 1997/01/09 00:29:32)); Tue, 28 Apr 1998 12:23:16 -0400 (EDT)
Received: (from majordom@localhost) by hub.org (8.8.8/8.7.5) id MAA01498 for pgsql-interfaces-outgoing; Tue, 28 Apr
199812:23:09 -0400 (EDT) 
Received: from sss.sss.pgh.pa.us (sss.pgh.pa.us [206.210.65.6]) by hub.org (8.8.8/8.7.5) with ESMTP id MAA01401; Tue,
28Apr 1998 12:22:04 -0400 (EDT) 
Received: from sss.sss.pgh.pa.us (localhost [127.0.0.1])
    by sss.sss.pgh.pa.us (8.8.5/8.8.5) with ESMTP id MAA07043;
    Tue, 28 Apr 1998 12:21:56 -0400 (EDT)
To: pgsql-hackers@postgreSQL.org, pgsql-interfaces@postgreSQL.org
Subject: [INTERFACES] Revised proposal for libpq and FE/BE protocol changes
Date: Tue, 28 Apr 1998 12:21:55 -0400
Message-ID: <7040.893780515@sss.pgh.pa.us>
From: Tom Lane <tgl@sss.pgh.pa.us>
Sender: owner-pgsql-interfaces@hub.org
Precedence: bulk

Here is a revised proposal that takes into account the discussions
of the last few days.  Any comments?


I propose to revise libpq and modify the frontend/backend protocol
to provide the following benefits:
 * Provide a clean way of reading multiple results from a single query
   string.  Among other things, this solves the problem of allowing a
   single query to return several result sets with different descriptors.
 * Allow a frontend to perform other work while awaiting the result of
   a query.
 * Add the ability to cancel queries in progress.
 * Eliminate the need for frontends to issue dummy queries in order
   to detect NOTIFY responses.
 * Eliminate the need for libpq to issue dummy queries internally
   to determine when a query is complete.

We can't break existing code for this, so the behavior of PQexec()
can't change.  Instead, I propose new functions to add to the API.
Internally, PQexec will be reimplemented in terms of these new
functions, but old applications shouldn't notice any difference.


The new functions are:

    bool PQsendQuery (PGconn *conn, const char *query);

Submits a query without waiting for the result.  Returns TRUE if the
query has been successfully dispatched, otherwise FALSE (in the FALSE
case, an error message is left in conn->errorMessage).

    PGresult* PQgetResult (PGconn *conn);

Waits for input from the backend, and consumes input until (a) a result is
available, (b) the current query is over, or (c) a copy in/out operation
is detected.  NULL is returned if the query is over; in all other cases a
suitable PGresult is returned (which the caller must eventually free).
Note that no actual "wait" will occur if the necessary input has already
been consumed; see below.

    bool PQisBusy (PGconn *conn);

Returns TRUE if a query operation is busy (that is, a call to PQgetResult
would block waiting for more input).  Returns FALSE if PQgetResult would
return immediately.

    void PQconsumeInput (PGconn *conn);

This can be called at any time to check for and process new input from
the backend.  It returns no status indication, but after calling it
the application can use PQisBusy() and/or PQnotifies() to see if a query
was completed or a NOTIFY message arrived.  This function will never wait
for more input to arrive.

    int PQsocket (PGconn *conn);

Returns the Unix file descriptor for the socket connection to the backend,
or -1 if there is no open connection.  This is a violation of modularity,
of course, but there is no alternative: an application that needs
asynchronous execution needs to be able to use select() to wait for input
from either the backend or any other input streams it may have.  To use
select() the underlying socket must be made visible.

    PGnotify *PQnotifies (PGconn *conn);

This function doesn't change; we just observe that notifications may
become available as a side effect of executing either PQgetResult() or
PQconsumeInput(), not just PQexec().

    void PQrequestCancel (PGconn *conn);

Issues a cancel request if possible.  There is no direct way to tell whether
this has any effect ... see discussion below.


Discussion:

An application can continue to use PQexec() as before, and notice
very little difference in behavior.

Applications that want to be able to handle multiple results from a
single query should replace PQexec calls with logic like this:

    // Submit the query
    if (! PQsendQuery(conn, query))
        reportTheError();
    // Wait for and process result(s)
    while ((result = PQgetResult(conn)) != NULL) {
        switch (PQresultStatus(result)) {
        ... process result, for example:
        case PGRES_COPY_IN:
            // ... copy data here ...
            if (PQendcopy(conn))
                reportTheError();
            break;
        ...
        }
        PQclear(result);
    }
    // When fall out of loop, we're done and ready for a new query

Note that PQgetResult will always report errors by returning a PGresult
with status PGRES_NONFATAL_ERROR or PGRES_FATAL_ERROR, not by returning
NULL (since NULL implies non-error termination of the processing loop).

PQexec() will be implemented as follows:

    if (! PQsendQuery(conn, query))
        return makeEmptyPGresult(conn, PGRES_FATAL_ERROR);
    lastResult = NULL;
    while ((result = PQgetResult(conn)) != NULL) {
        PQclear(lastResult);
        lastResult = result;
    }
    return lastResult;

This maintains the current behavior that the last result of a series
of commands is returned by PQexec.  (The old implementation is only
capable of doing that correctly in a limited set of cases, but in the
cases where it behaves usefully at all, that's how it behaves.)

There is a small difference in behavior, which is that PQexec will now
return a PGresult with status PGRES_FATAL_ERROR in cases where the old
implementation would just have returned NULL (and set conn->errorMessage).
However, any correctly coded application should handle this the same way.

In the above examples, the frontend application is still synchronous: it
blocks while waiting for the backend to reply to a query.  This is often
undesirable, since the application may have other work to do, such as
responding to user input.  Applications can now handle that by using
PQisBusy and PQconsumeInput along with PQsendQuery and PQgetResult.

The general idea is that the application's main loop will use select()
to wait for input (from either the backend or its other input sources).
When select() indicates that input is pending from the backend, the app
will call PQconsumeInput, followed by checking PQisBusy and/or PQnotifies
to see what has happened.  If PQisBusy returns FALSE then PQgetResult
can safely be called to obtain and process a result without blocking.

Note also that NOTIFY messages can arrive asynchronously from the backend.
They can be detected *without issuing a query* by calling PQconsumeInput
followed by PQnotifies.  I expect a lot of people will build "partially
async" applications that detect notifies this way but still do all their
queries through PQexec (or better, PQsendQuery followed by a synchronous
PQgetResult loop).  This compromise allows notifies to be detected without
wasting time by issuing null queries, yet the basic logic of issuing a
series of queries remains simple.

Finally, since the application can retain control while waiting for a
query response, it becomes meaningful to try to cancel a query in progress.
This is done by calling PQrequestCancel().  Note that PQrequestCancel()
may not have any effect --- if there is no query in progress, or if the
backend has already finished the query, then it *will* have no effect.
The application must continue to follow the result-reading protocol after
issuing a cancel request.  If the cancel is successful, its effect will be
to cause the current query to fail and return an error message.


PROTOCOL CHANGES:

We should change the protocol version number to 2.0.
It would be possible for the backend to continue to support 1.0 clients,
if you think it's worth the trouble to do so.

1. New message type:

Command Done
    Byte1('Z')

The backend will emit this message at completion of processing of every
command string, just before it resumes waiting for frontend input.
This change eliminates libpq's current hack of issuing empty queries to
see whether the backend is done.  Note that 'Z' must be emitted after
*every* query or function invocation, no matter how it terminated.

2. The RowDescription ('T') message is extended by adding a new value
for each field.  Just after the type-size value, there will now be
an int16 "atttypmod" value.  (Would someone provide text specifying
exactly what this value means?)  libpq will store this value in
a new "adtmod" field of PGresAttDesc structs.

3. The "Start Copy In" response message is changed from 'D' to 'G',
and the "Start Copy Out" response message is changed from 'B' to 'H'.
These changes eliminate potential confusion with the data row messages,
which also have message codes 'D' and 'B'.

4. The frontend may request cancellation of the current query by sending
a single byte of OOB (out-of-band) data.  The contents of the data byte
are irrelevant, since the cancellation will be triggered by the associated
signal and not by the data itself.  (But we should probably specify that
the byte be zero, in case we later think of a reason to have different
kinds of OOB messages.)  There is no specific reply to this message.
If the backend does cancel a query, the query terminates with an ordinary
error message indicating that the query was cancelled.


            regards, tom lane


pgsql-hackers by date:

Previous
From: lynch@lscorp.com (Richard Lynch)
Date:
Subject: Re: [QUESTIONS] copy command
Next
From: Ryan Kirkpatrick
Date:
Subject: Re: [PORTS] Patch to remove -Dalpha for Alphas...