db_row: A better way of dealing with database result sets and other
        fixed schema structures.

Version 0.8beta

NOTICE: THIS MODULE IS BEING RELEASED AS BETA QUALITY SOFTWARE: This means
        that there may be bugs, missing features, or unexpected behaviors.
        Do not be surprised if your data contains unexpected 'fnords', your
        dishwasher suddenly stops working, your girlfriend unexpectedly gets
        pregnant, or your dog decides to run away.

        YOU HAVE BEEN WARNED.

        On the other hand, this software has been used for over two years in
        a very demanding, high-throughput, and stressful environment.  It is
        constantly run through an application regression testing suite, and
        has exposed several errors in the core Python interpreter (since
        reported and corrected as of Python version 2.2.2).  Still, don't
        whine if it does not work as well for you.  I would, however,
        appreciate comprehensive bug reports, suggestions, and will gladly
        accept patches.

Written by Kevin Jacobs (jacobs@theopalgroup.com).

----------------------------------------------------------------------------

Copyright (c) 2001,2002,2003 The OPAL Group.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

----------------------------------------------------------------------------

The db_row module defines light-weight objects suitable for many
applications, though the primary goal of the implementer is for storage of
database query results.  The primary design criteria for the data-structure
where:

  1) store a sequence of arbitrary Python objects.
  2) the number of items stored in each instance should be constant.
  3) each instance must be as light-weight as possible, since many thousands
     of them are likely to be created.
  4) values must be retrievable by index:
     e.g.: d[3]
  5) values must be retrievable by field name by both Python attribute syntax and
     item syntax:
     e.g.: d.fields.foo == d['foo']
  6) optionally, operations using field names should be case-insensitive
     e.g.: d['FiElD'], d.fields.FiElD
  7) Otherwise drop-in compatible with tuple objects.
  8) Maintains a disjoint namespace for field names so they do not conflict
     with method names.

These criteria were chosen to simplify access to rows that are returned from
database queries.  Lets say that you run this query:

  cursor.execute('SELECT a,b,c FROM blah;')
  results = cursor.fetchall()

The resulting data-structure is typically a list of row tuples. e.g.:

  results = [ (1,2,3), (3,4,5), (6,7,8) ]

While entirely functional, these data types only allow integer indexed
access.  e.g., to query the b attribute of the second row:

  b = results[1][1]

This requires that all query results are accessed by index, which can be
very tedious and the code using this technique tends to be hard to maintain.
The alternative has always been to return a list of native Python
dictionaries, one for each row.  e.g.:

  results = [ {'a':1,'b':2,'c':3}, {'a':3,'b':4,'c':5},
              {'a':6,'b':7,'c':8} ]

This has the advantage of easier access to attributes by name, e.g.:

  foo = results[1]['b']

however, there are several serious disadvantages.

  1) each row requires a heavy-weight dictionary _per instance_.  This can
     damage performance by forcing a loaded system to start swapping virtual
     memory to disk when returning, say, 100,000 rows from a query.

  2) access by index is lost since Python dictionaries are unordered.

  3) attribute-access syntax is somewhat sub-optimal (or at least
     inflexible) since it must use the item-access syntax.

     i.e., x['a'] vs. x.a.

  4) Compatibility with code that expects tuples is lost.

Of course, the second and third problems can be partially addressed by
creating a UserDict (a Python class that looks and acts like a dictionary),
though that only magnifies the performance problems.

HOWEVER, there are some new features in Python 2.2 and newer that can
provide the best of all possible worlds.  Here is an example:

  # Create a new class type to store the results from our query (we'll make
  # field names case-insensitive just to show off)

  R=IMetaRow(['a','b','c'])

  # Create an instance of our new tuple class with values 1,2,3
  r=R( (1,2,3) )

  # Demonstrate all three accessor types
  print r['a'], r[1], r.fields.c
  > 1 2 3

  # Demonstrate case-insensitive operation
  print r['a'], r['A']
  > 1 1

  # Return the keys (column names)
  print r.keys()
  > ('a', 'b', 'c')

  # Return the values
  print r.values()
  > (1, 2, 3)

  # Return a list of keys and values
  print r.items()
  > (('a', 1), ('b', 2), ('c', 3))

  # Return a dictionary of the keys and values
  print r.dict()
  > {'a': 1, 'c': 3, 'b': 2}

  # Demonstrate slicing behavior
  print r[1:3]
  > (2, 3)

This solution uses some new Python 2.2 features and ends up allocating only
one dictionary _per row class_, not per row instance.  i.e., the row
instances do not allocate a dictionary at all!  This is accomplished using
the new-style object 'slots' mechanism.

Here is how you could use these objects:

  cursor.execute('SELECT a,b,c FROM blah;')

  # Make a class to store the resulting rows
  R = IMetaRow(cursor.description)

  # Build the rows from the row class and each tuple returned from the cursor
  results = [ R(row) for row in cursor.fetchall() ]

  print results[1].fields.b, results[2].fields.B, results[3]['b'], results[2][1]

Open implementation issues:

  o Values are currently mutable, so hashing of rows is explicitly
    disallowed.  This does not bother me much, though some may desire both
    mutable and immutable instance types.

  o The current row code returns most slicing, copying, combing operations
    (objects resulting from the '+' and '*' operators), keys(), values(),
    items() as tuples.  This is done to better conform to legacy code which
    assumes that rows are always tuples.  This seems sensible enough, though
    I welcome other opinions on the subject.

   o More documentation and doc-strings are needed.

   o Improve the integrated unit-tests (a la doctest or unittest, most likely)

   o Add some better example code.

Changes from version 0.71 -> 0.8:

  o Ported to win32+Mingw32 and includes pre-compiled Win32 versions for
    Python 2.2 and Python 2.3.

  o Fixed C implmentation to allow fields with zero elements.  The behavior
    now matches the pure-Python version.  (Reported by Anthony Baxter)

  o Fixed C implementation so that accessing unitialized fields raise an
    exception.  The behavior now matches the pure-Python version.

  o Other minor cleanups and tweaks

  o Added more unit tests.

Changes from version 0.7 -> 0.71:

  o Removed an unnecessary call to 'enumerate', which is only available in
    Python 2.3.  Thanks to Ben Golding of Object Craft for noticing this.

Changes from version 0.6 -> 0.7:

  o Removed some cruft from the Python implementation of FieldsBase.

  o Made the behavior of the Python base classes better match the C version
    in a few spots.

  o Cleaned up driver and description storage using properties.

  o Added a dictionary-like .get(key,default=None) method to the Row class.

  o Added new test cases.

Changes from version 0.5 -> 0.6:

  o Added missing slots declaration from Python FieldsBase object.  This
    corrected a major flaw in the pure-Python implementation which caused
    the allocation of per-instance dictionaries, and allowing access to
    undeclared fields.  The C version of FieldsBase was not affected.

  o Fixed exception types so that various accessors raise the appropriate
    exceptions.  e.g., previously __getitem__ would incorrectly raise
    AttributeError exceptions.  These changes were made in both the Python
    and C versions.

  o Added many new test cases to the regression suite, including much more
    rigorous read/write testing.

  o Removed some unnecessary (and slow!) code from the C fields_subscript
    function.  I suspect it was a left-over from past debugging that was
    not completely removed.
