Re: Use generators instead of lists for resultsets?

Jacob Smullyan <[email protected]> Tue, 28 Jun 2005 18:47:20 -0400
Newsgroups gmane.comp.web.skunkweb
Message-ID <[email protected]>
Before my horrendously long response to this, which no one in their
right mind will read, I should say again that my release plan is to
worry about this and other api enhancements for 2.1; I'm still a step
behind, concerned with assuring that the simple things work correctly.
If anyone has time to run the test suite in their environment, I'd be
very glad to get the results.

On Tue, Jun 28, 2005 at 08:27:14PM +0100, [email protected] wrote:

> I agree that potential network latency probably makes it undesirable
> to switch over completely to using 'fetchone' rather than
> 'fetchall', and so we will probably need to have various fetching
> strategies. You expressed misgivings about 'getSome' sometimes
> returning a generator and other times a list. Is there any reason
> why it (and the functions it makes use of) can't always return an
> iterator, even when 'fetchall' has been used? If this iterator [1]
> were to implement a '__len__' method (returning the cursor's
> rowcount), then it would be an almost seamless replacement for a
> list as far as other code was concerned.

There are operations that you could perform on a list that don't
involve iterating on it:

   cPickle.dump(obj.getSome(), fp)

or

   x=obj.getSome()
   x.append('hoho')

You'd need either to implement a lot of smarts in your ResultSet
iterator, or simply break backwards compatibility and make it clear
that it was now an iterator and you had to call list(x) to get a list.

I'd still prefer to provide just such an iterator method, but call it
something different, such as iterSome(), and having getSome() continue
to return a list -- it might be implemented as list(self.iterSome()).
Not only did I promise not to break backwards API compatibility with
the beta release, but I personally would like better a separate
method, if only because I have going on five years' history with
getSome(), and converting my old and crufty code to PyDO2 will be
difficult enough.

As for whether iterSome(), if you accept that name, should be
configurable to fetch iteratively on a per-class basis, my impression
was that per-query configurability would be more useful, but per-class
configurability would be better than none.

In summary, here is a start at a set of requirements and unsettled
issues:

 1. we need a way to get large result sets from the db in a stream.

 2. Some drivers may provide a means for doing this using dbapi methods like
    fetchone/fetchmany; others may require directly manipulating a sql
    cursor, because they prefetch the entire result set anyway.  It is
    possible that the first set of drivers is empty.

 3. we need an iterator that returns the same data as getSome() (whether 
    it is the same or a different method).

 4. It must be possible for the user to configure when the result sets are
    streamed and when they are fetched en bloc, either per query or
    per class, and preferably both.

Am I leaving out anything we've covered?

Here is a counter-proposal inspired by your implementation ideas:

A thorn is the fact that the getSome() api is rather rigid, in that it
can't be extended with keyword arguments without treading on potential
fields.  One solution would be to introduce an intermediate Query
object that has a getSome() method; the Query could have other
attributes, and PyDO.getSome() would simply use a default Query
object.  I can't say I'm really a fan of using that design pattern
just to get around a limitation of preexisting function signatures,
but this wouldn't be too bad; most of the time you'd never create a
Query object, doing so only when you have a particularly brutal result
set and need to solve a performance problem.  Using such an object
would also enable you to set a per-class default as well as override
it on a per-query basis.  As for how to deal with joins, if they are
being done as PyDO methods that only return one table, as in
joinTable, the table being returned is the one whose query settings
would be applied; in the case of fetch, with multiple tables, you
could get either use a default query object by calling fetch(), or
create a custom one and call .fetch() (or .iterfetch()) on it.
(Implicitly creating a lowest common denominator query obj may be OK,
unless it turns out that such an object can have other properties that
can't be reconciled in that way.)

Cheers,

js  
-- 
Jacob Smullyan
signature.asc (application/pgp-signature, 189 B)
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)

iD8DBQFCwdN4uqamFyFXXLIRAnhLAJ9v1YEabS197JD9Nu6Fc/yigzUbFQCfZXWj
Wg+SoZUJAuNiy5Gy+L6QgcU=
=koZe
-----END PGP SIGNATURE-----