Postgres
Database Access
These
are wrapper objects around PyGreSQL-3.5.
|
PgCursorInSubthread
- handling a Postgres named cursor in a subthread, which the
application periodically polls until the subthread has received
results from Postgres PgQueryInSubthread - handling
a single query to Postgres in a subthread, which the application
periodically polls until the subthread has received results from
Postgres PgQueryInSubthreadWithQtTimer - just
like the last one, except that the polling is done by a Qt timer
build into the query object PgQueryInForeground -
simple wrapper of PyGreSQL that causes the application to block
until it receives results from Postgres
|
PgCursorInSubthread
Problem: The libpq
interface will load into RAM the entire result set from a select
statement for any client application that is linked with libpq,
so you can't use this for scrolling through very large data sets (why
use a database at all if you can fit it into RAM?).
Solution: The
PgCursorInSubthread creates a Postgres named
cursor for your SQL
string, and then issues the FETCH and MOVE SQL commands
against this named cursor
to pull back only the very limited rows that you are interested in.
Because these FETCH and MOVE SQL commands could take a long time when
running against a very large result set, they are issued in a
subthread
so that the main thread of program execution does not hang.
PgCursorInSubthread starts off a
Python subthread
from the thread module's start_new_thread() function.
This subthread
calls the main()
method, which spins in an infinite loop, calling internal methods
(like __fetch(), __move() and __close()) to talk
to the database using PyGreSQL when needed, and polling several
internal state variables (like __new_rows_requested, __refresh
and __do_close) to determine what to do when it's not waiting
on the database. It does a tiny time.sleep() during each loop
so that when there is no work to do it does not spin through the loop
out of control and take up tons of CPU time.
The main
thread of execution in a program that uses
PgCursorInSubthread will tell a PgCursorInSubthread
object what to do through an API that sets and queries the internal
state variables. If there are rows of data back from the database,
they can be retrieved.
Specifically, the main program
in the main
thread will call
get_or_request_rows(starting_row_number,
ending_row_number).
This will lock the lock, and search through the row
cache, which is implemented as a dict indexed by
row number. If the rows from starting_row_number
to ending_row_number can be fetched
from the row
cache, they are immediately returned and
the lock is unlocked.
If all the requested rows
cannot be found in the row
cache, get_or_request_rows()
will set some internal variables like __new_rows_requested,
and unlock the lock and return an empty list to the main program.
The main program in the main
thread would need to know that it needs to keep
polling get_or_request_rows()
until it gets back more than an empty list. This could be
done via user intervention (clicking on a refresh button), or by
a timer.
In the subthread,
the main()
method of PgCursorInSubthread will see that the
__new_rows_requested state variable has been changed as
soon as it goes through it's loop again (and as soon as the main
thread unlocks the lock). It will then call the
internal __move()
and/or __fetch()
methods, which in turn call on the PyGreSQL module. Those
__move() and/or
__fetch() calls
could hang for a very long time as the database does its work,
but that's OK because it is happening in a subthread,
so the main thread of
the program will not hang.
The __move()
and/or __fetch()
routines issue sql statements against the named
cursor that was created in the initialization of
the PgCursorInSubthread object. These sql statements
tell the named cursor
to move forward or backward for a certain number of rows, and
to retrieve a very limited result set - just the range from
starting_row_number to
ending_row_number, and a little
more prefetch, based on the direction of movement.
When the rows actually do
return, the subthread
lock the lock, and sprinkles the result rows into the row
cache. If the cache is too full, the subthread
will at this time also clean out the least recently used rows
from row cache.
The subthread then
unlocks the lock and goes back to looping and taking tiny
sleeps.
The
main program in the main thread
would then somehow again call get_or_request_rows()
and it would then get back a list containing the rows from
starting_row_number to
ending_row_number.
|

PgQueryInSubthread
Unlike PgCursorInSubthread, this class
is intended for single-use queries with results that are small enough
to grab in one try. This class will take a single SQL
string (supplied by the application) and wait for the database
to deliver the result, and then switch it's state to "ready".
The application is again responsible for polling, to check
PgQueryInSubthread for readiness. When readiness occurs, the
application can get from PgQueryInSubthread a PyGreSQL "query"
object, which can deliver the result of the query to the application.
There doesn't actually need to be any result from the SQL
string (as in the case of a "CREATE TABLE ..."
statement), so the application might not be interested in anything
but the readiness of the PgQueryInSubthread object. The
application can then refrain from obtaining the "query"
object.
PgQueryInSubthread starts off a
Python subthread
from the thread module's start_new_thread() function.
This subthread
calls the main()
method, which uses the internal __run_sql()
method to send the application-supplied SQL
string to the database using PyGreSQL. When PyGreSQL returns a
result, the subthread sets it's __ready internal variable to
1.
The main
thread of execution in a program that uses
PgQueryInSubthread will supply the PgQueryInSubthread
object with a single SQL string,
and then periodically query the PgQueryInSubthread for
readiness. If there are rows of data back from the database, they can
be retrieved.
Specifically, the main program
in the main
thread will call ready().
This will lock the lock, and check the internal variable __ready,
and then unlock the lock.
If ready() does not
return 1, the main thread should not call query() because
it would have nothing to return.
The main program in the main
thread would need to know that it needs to keep
polling ready() until
it gets back 1. This could be done via user intervention
(clicking on a refresh button), or by a timer.
In the subthread,
the main()
method calls the __run_sql()
method of PgQueryInSubthread, which will eventually
return, and the subthread will store the PyGreSQL module's query
object in the internal variable __query, and it would set
the internal variable __ready to 1.
The main program in the main
thread would then somehow again call ready()
and it would then get back 1. It could, if it desired, then call
query() to get
the PyGreSQL query
object which resulted from executing the SQL
string. The returned rows can be obtained from the query
object using it's getresult() or getdictresult()
methods.
Or, if the main program had
supplied a SQL string
like "CREATE TABLE...", and it was only interested in
the readiness of the PgQueryInSubthread object, the call
to query() by the
main thread could be omitted.
|

PgQueryInSubthreadWithQtTimer
Like PgQueryInSubthread, this class is
intended for single-use queries with results that are small enough to
grab in one try. But this class removes from the main
thread all responsibility for polling. It does this by
assuming that the main thread
is a Qt application, and does it's own polling via a Qt timer. This
class requires that the main thread
supply it with a callback
function that will be called once the results are available.
This class will take as arguments a
callback function,
and a single SQL string
(supplied by the application) and will then wait in the subthread
for the database to deliver the result, and then call the application
supplied callback
function in the main thread,
passing itself as the one and only argument. In the callback
function, the application can get from PgQueryInSubthreadWithQtTimer
a PyGreSQL "query"
object, which can deliver the result of the query to the application.
There doesn't actually need to be any result from the SQL
string (as in the case of a "CREATE TABLE ..."
statement), so the application might not be interested in anything
but the actual invocation of the callback
function. The application can then refrain from obtaining the "query"
object.
PgQueryInSubthreadWithQtTimer
starts off a Python subthread
from the thread module's start_new_thread() function.
This subthread
calls the main()
method, which uses the internal __run_sql()
method to send the application-supplied SQL
string to the database using PyGreSQL. When PyGreSQL returns a
result, the subthread sets it's __ready internal variable to
1.
The main
thread of execution in a program that uses
PgQueryInSubthreadWithQtTimer will supply the
PgQueryInSubthreadWithQtTimer object with a callback
function and a single SQL string,
and then the PgQueryInSubthreadWithQtTimer will poll itself
for readiness in the main thread.
Specifically, the main program
in the main
thread will call the PgQueryInSubthreadWithQtTimer
constructor. This
constructor will create a Qt timer which is stored in the
PgQueryInSubthreadWithQtTimer variable my_timer,
and which will periodically call the
PgQueryInSubthreadWithQtTimer method my_timer_callback()
in the main thread.
The constructor will also start
a subthread.
In the subthread,
the main()
method calls the __run_sql()
method of PgQueryInSubthread, which will eventually
return, and the subthread will store the PyGreSQL module's query
object in the internal variable __query, and it would set
the internal variable __ready to 1.
In the main
thread, the my_timer_callback()
method would eventually find the __ready internal variable
set to 1, indicating that the subthread
had finished it's work. The my_timer_callback()
method would then stop the timer, and call the
application-supplied callback
function, passing the PgQueryInSubthreadWithQtTimer object
as the callback
function's one and only argument.
If the main
thread of the application was interested in the results of
the query, it could, in the callback
function, call the query()
method of the PgQueryInSubthreadWithQtTimer object to get
the PyGreSQL query
object which resulted from executing the SQL
string. The returned rows can be obtained from the query
object using it's getresult() or getdictresult()
methods.
Or, if the main program had
supplied a SQL string
like "CREATE TABLE...", and it was only interested in
the readiness of the PgQueryInSubthreadWithQtTimer object,
the call to query()
by the main thread could be omitted.
|
