Application Note: Integrating Posgresql queries into an event loop. - Mailing list pgsql-docs

From Mark Harrison
Subject Application Note: Integrating Posgresql queries into an event loop.
Date
Msg-id 40C4E2D9.9000108@pixar.com
Whole thread Raw
List pgsql-docs
Here's a note on using the async API.  It's not runnable code (due to
the variances in event loop packages), but I'm happy to add this
to whatever documentation package is appropriate...

Advice and feedback welcomed...
Mark



Application Note: Integrating Posgresql queries into an event loop.

Mark Harrison
mh@pixar.com
May 27, 2004

Problem
-------

The commonly used postgresql APIs will block until completed.
If you are in a GUI event loop, this will block your GUI
responsiveness until a query is completed.

If the queries are small and finish quickly, there is probably
OK.  Hanging for a few milliseconds will not be a problem.

However, if you have a large or slow query,this can be significant.
For example, one of my tables (call it "big") has about
14 million rows.  It takes about two minutes for

     res = PQexec(conn, "select * from big");

to return.  An additional unpleasant side effect is that
the process then requires about 1.2 gig of memory to buffer
the returned data.

Solution, part 1:
-----------------

First, we need to break up the returned data into more
manageable chunks.  For this, we use an SQL cursor.  Here
are the relevant bits of code (error checking elided in
order to make the flow more clear):


     res = PQexec(conn, "BEGIN");
     res = PQexec(conn, "DECLARE cur CURSOR FOR select * from big");

     while (1) {
         res = PQexec(conn, "FETCH 1000 in cur");
         if (PQntuples(res) == 0)
             break
         else
            //process rows of data
     }

     res = PQexec(conn, "CLOSE cur");
     res = PQexec(conn, "END");


This has two immediate benefits:

1.  There is not a two minute pause while the data is being
     transferred and buffered from the server to the client.

2.  The memory requirements for the client program are much
     lower.

Solution, part 2
----------------

Now that we have broken  our data retrieval into managable
chunks, we need to integrate this logic into the event loop.

As is typical for event loop programming, there are two
main bits of code:

1.  Set up query and callback.

     // establish the database connection.  Set the connection to
     // non-blocking mode, and create a cursor for fetching our data.
     // Flush the request to the server.
     conn = PQconnectdb("...");
     rc = PQsetnonblocking(conn, 1);
     res = PQexec(conn, "BEGIN");
     res = PQexec(conn, "DECLARE cur CURSOR FOR select * from big");
     rc = PQsendQuery(conn, "FETCH 1000 in cur");
     PQflush(conn);

     // Get the postgresql socket and add it to our event loop
     sock = PQsocket(conn);
     add_to_event_loop(READABLE, sock, myhandler);


2.  The callback which processes the returned data.  This is referred
     to as myhandler() in the previous step.

     // Consume the input from the server.  This will clear the file
     // descriptor "readable" flag.  Process as much data as we can
     // in the while loop.  When we have retrieved all the data for
     // this FETCH, issue another FETCH command.  When there are no
     // more tuples returned, the query is finished and you can clean
     // up the request.
     rc = PQconsumeInput(conn);
     while (!PQisBusy(conn)) {
         rc = PQconsumeInput(conn); // (is this necessary?)
         res = PQgetResult(conn);
         if (res == NULL) {
             // we have finished all the rows for this FETCH.  We need
             // to send another FETCH to the server.
             rc = PQsendQuery(conn, "FETCH 1000 in cur");
             PQflush(conn);
             return;
         }
         else {
            if (PQntuples(res) == 0)
                // finished processing all rows.  Clean up the
                // result and remove your callback from the
                // event loop.
            else
                //process rows of data
         }
     }

If you wish to cancel a query midway through processing (e.g.,
if the user presses "cancel"), call

     PQrequestCancel(conn);

Copying and Acknowledgements
----------------------------

This document is released under the same license as the Postgresql
documentation.  Thanks to Tom Lane for his help in understanding
this API.

pgsql-docs by date:

Previous
From: Halley Pacheco de Oliveira
Date:
Subject: Re: to_ascii function
Next
From: Halley Pacheco de Oliveira
Date:
Subject: PostgreSQL 7.4 Documentation - PL/pgSQL