PageRenderTime 46ms CodeModel.GetById 2ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 0ms

/Lib/bsddb/dbtables.py

http://unladen-swallow.googlecode.com/
Python | 827 lines | 709 code | 52 blank | 66 comment | 29 complexity | 4199dc3d8bd99cde449754c75874d041 MD5 | raw file
  1#-----------------------------------------------------------------------
  2#
  3# Copyright (C) 2000, 2001 by Autonomous Zone Industries
  4# Copyright (C) 2002 Gregory P. Smith
  5#
  6# License:      This is free software.  You may use this software for any
  7#               purpose including modification/redistribution, so long as
  8#               this header remains intact and that you do not claim any
  9#               rights of ownership or authorship of this software.  This
 10#               software has been tested, but no warranty is expressed or
 11#               implied.
 12#
 13#   --  Gregory P. Smith <greg@krypto.org>
 14
 15# This provides a simple database table interface built on top of
 16# the Python Berkeley DB 3 interface.
 17#
 18_cvsid = '$Id: dbtables.py 66088 2008-08-31 14:00:51Z jesus.cea $'
 19
 20import re
 21import sys
 22import copy
 23import random
 24import struct
 25import cPickle as pickle
 26
 27try:
 28    # For Pythons w/distutils pybsddb
 29    from bsddb3 import db
 30except ImportError:
 31    # For Python 2.3
 32    from bsddb import db
 33
 34# XXX(nnorwitz): is this correct? DBIncompleteError is conditional in _bsddb.c
 35if not hasattr(db,"DBIncompleteError") :
 36    class DBIncompleteError(Exception):
 37        pass
 38    db.DBIncompleteError = DBIncompleteError
 39
 40class TableDBError(StandardError):
 41    pass
 42class TableAlreadyExists(TableDBError):
 43    pass
 44
 45
 46class Cond:
 47    """This condition matches everything"""
 48    def __call__(self, s):
 49        return 1
 50
 51class ExactCond(Cond):
 52    """Acts as an exact match condition function"""
 53    def __init__(self, strtomatch):
 54        self.strtomatch = strtomatch
 55    def __call__(self, s):
 56        return s == self.strtomatch
 57
 58class PrefixCond(Cond):
 59    """Acts as a condition function for matching a string prefix"""
 60    def __init__(self, prefix):
 61        self.prefix = prefix
 62    def __call__(self, s):
 63        return s[:len(self.prefix)] == self.prefix
 64
 65class PostfixCond(Cond):
 66    """Acts as a condition function for matching a string postfix"""
 67    def __init__(self, postfix):
 68        self.postfix = postfix
 69    def __call__(self, s):
 70        return s[-len(self.postfix):] == self.postfix
 71
 72class LikeCond(Cond):
 73    """
 74    Acts as a function that will match using an SQL 'LIKE' style
 75    string.  Case insensitive and % signs are wild cards.
 76    This isn't perfect but it should work for the simple common cases.
 77    """
 78    def __init__(self, likestr, re_flags=re.IGNORECASE):
 79        # escape python re characters
 80        chars_to_escape = '.*+()[]?'
 81        for char in chars_to_escape :
 82            likestr = likestr.replace(char, '\\'+char)
 83        # convert %s to wildcards
 84        self.likestr = likestr.replace('%', '.*')
 85        self.re = re.compile('^'+self.likestr+'$', re_flags)
 86    def __call__(self, s):
 87        return self.re.match(s)
 88
 89#
 90# keys used to store database metadata
 91#
 92_table_names_key = '__TABLE_NAMES__'  # list of the tables in this db
 93_columns = '._COLUMNS__'  # table_name+this key contains a list of columns
 94
 95def _columns_key(table):
 96    return table + _columns
 97
 98#
 99# these keys are found within table sub databases
100#
101_data =  '._DATA_.'  # this+column+this+rowid key contains table data
102_rowid = '._ROWID_.' # this+rowid+this key contains a unique entry for each
103                     # row in the table.  (no data is stored)
104_rowid_str_len = 8   # length in bytes of the unique rowid strings
105
106
107def _data_key(table, col, rowid):
108    return table + _data + col + _data + rowid
109
110def _search_col_data_key(table, col):
111    return table + _data + col + _data
112
113def _search_all_data_key(table):
114    return table + _data
115
116def _rowid_key(table, rowid):
117    return table + _rowid + rowid + _rowid
118
119def _search_rowid_key(table):
120    return table + _rowid
121
122def contains_metastrings(s) :
123    """Verify that the given string does not contain any
124    metadata strings that might interfere with dbtables database operation.
125    """
126    if (s.find(_table_names_key) >= 0 or
127        s.find(_columns) >= 0 or
128        s.find(_data) >= 0 or
129        s.find(_rowid) >= 0):
130        # Then
131        return 1
132    else:
133        return 0
134
135
136class bsdTableDB :
137    def __init__(self, filename, dbhome, create=0, truncate=0, mode=0600,
138                 recover=0, dbflags=0):
139        """bsdTableDB(filename, dbhome, create=0, truncate=0, mode=0600)
140
141        Open database name in the dbhome Berkeley DB directory.
142        Use keyword arguments when calling this constructor.
143        """
144        self.db = None
145        myflags = db.DB_THREAD
146        if create:
147            myflags |= db.DB_CREATE
148        flagsforenv = (db.DB_INIT_MPOOL | db.DB_INIT_LOCK | db.DB_INIT_LOG |
149                       db.DB_INIT_TXN | dbflags)
150        # DB_AUTO_COMMIT isn't a valid flag for env.open()
151        try:
152            dbflags |= db.DB_AUTO_COMMIT
153        except AttributeError:
154            pass
155        if recover:
156            flagsforenv = flagsforenv | db.DB_RECOVER
157        self.env = db.DBEnv()
158        # enable auto deadlock avoidance
159        self.env.set_lk_detect(db.DB_LOCK_DEFAULT)
160        self.env.open(dbhome, myflags | flagsforenv)
161        if truncate:
162            myflags |= db.DB_TRUNCATE
163        self.db = db.DB(self.env)
164        # this code relies on DBCursor.set* methods to raise exceptions
165        # rather than returning None
166        self.db.set_get_returns_none(1)
167        # allow duplicate entries [warning: be careful w/ metadata]
168        self.db.set_flags(db.DB_DUP)
169        self.db.open(filename, db.DB_BTREE, dbflags | myflags, mode)
170        self.dbfilename = filename
171
172        if sys.version_info[0] >= 3 :
173            class cursor_py3k(object) :
174                def __init__(self, dbcursor) :
175                    self._dbcursor = dbcursor
176
177                def close(self) :
178                    return self._dbcursor.close()
179
180                def set_range(self, search) :
181                    v = self._dbcursor.set_range(bytes(search, "iso8859-1"))
182                    if v != None :
183                        v = (v[0].decode("iso8859-1"),
184                                v[1].decode("iso8859-1"))
185                    return v
186
187                def __next__(self) :
188                    v = getattr(self._dbcursor, "next")()
189                    if v != None :
190                        v = (v[0].decode("iso8859-1"),
191                                v[1].decode("iso8859-1"))
192                    return v
193
194            class db_py3k(object) :
195                def __init__(self, db) :
196                    self._db = db
197
198                def cursor(self, txn=None) :
199                    return cursor_py3k(self._db.cursor(txn=txn))
200
201                def has_key(self, key, txn=None) :
202                    return getattr(self._db,"has_key")(bytes(key, "iso8859-1"),
203                            txn=txn)
204
205                def put(self, key, value, flags=0, txn=None) :
206                    key = bytes(key, "iso8859-1")
207                    if value != None :
208                        value = bytes(value, "iso8859-1")
209                    return self._db.put(key, value, flags=flags, txn=txn)
210
211                def put_bytes(self, key, value, txn=None) :
212                    key = bytes(key, "iso8859-1")
213                    return self._db.put(key, value, txn=txn)
214
215                def get(self, key, txn=None, flags=0) :
216                    key = bytes(key, "iso8859-1")
217                    v = self._db.get(key, txn=txn, flags=flags)
218                    if v != None :
219                        v = v.decode("iso8859-1")
220                    return v
221
222                def get_bytes(self, key, txn=None, flags=0) :
223                    key = bytes(key, "iso8859-1")
224                    return self._db.get(key, txn=txn, flags=flags)
225
226                def delete(self, key, txn=None) :
227                    key = bytes(key, "iso8859-1")
228                    return self._db.delete(key, txn=txn)
229
230                def close (self) :
231                    return self._db.close()
232
233            self.db = db_py3k(self.db)
234        else :  # Python 2.x
235            pass
236
237        # Initialize the table names list if this is a new database
238        txn = self.env.txn_begin()
239        try:
240            if not getattr(self.db, "has_key")(_table_names_key, txn):
241                getattr(self.db, "put_bytes", self.db.put) \
242                        (_table_names_key, pickle.dumps([], 1), txn=txn)
243        # Yes, bare except
244        except:
245            txn.abort()
246            raise
247        else:
248            txn.commit()
249        # TODO verify more of the database's metadata?
250        self.__tablecolumns = {}
251
252    def __del__(self):
253        self.close()
254
255    def close(self):
256        if self.db is not None:
257            self.db.close()
258            self.db = None
259        if self.env is not None:
260            self.env.close()
261            self.env = None
262
263    def checkpoint(self, mins=0):
264        try:
265            self.env.txn_checkpoint(mins)
266        except db.DBIncompleteError:
267            pass
268
269    def sync(self):
270        try:
271            self.db.sync()
272        except db.DBIncompleteError:
273            pass
274
275    def _db_print(self) :
276        """Print the database to stdout for debugging"""
277        print "******** Printing raw database for debugging ********"
278        cur = self.db.cursor()
279        try:
280            key, data = cur.first()
281            while 1:
282                print repr({key: data})
283                next = cur.next()
284                if next:
285                    key, data = next
286                else:
287                    cur.close()
288                    return
289        except db.DBNotFoundError:
290            cur.close()
291
292
293    def CreateTable(self, table, columns):
294        """CreateTable(table, columns) - Create a new table in the database.
295
296        raises TableDBError if it already exists or for other DB errors.
297        """
298        assert isinstance(columns, list)
299
300        txn = None
301        try:
302            # checking sanity of the table and column names here on
303            # table creation will prevent problems elsewhere.
304            if contains_metastrings(table):
305                raise ValueError(
306                    "bad table name: contains reserved metastrings")
307            for column in columns :
308                if contains_metastrings(column):
309                    raise ValueError(
310                        "bad column name: contains reserved metastrings")
311
312            columnlist_key = _columns_key(table)
313            if getattr(self.db, "has_key")(columnlist_key):
314                raise TableAlreadyExists, "table already exists"
315
316            txn = self.env.txn_begin()
317            # store the table's column info
318            getattr(self.db, "put_bytes", self.db.put)(columnlist_key,
319                    pickle.dumps(columns, 1), txn=txn)
320
321            # add the table name to the tablelist
322            tablelist = pickle.loads(getattr(self.db, "get_bytes",
323                self.db.get) (_table_names_key, txn=txn, flags=db.DB_RMW))
324            tablelist.append(table)
325            # delete 1st, in case we opened with DB_DUP
326            self.db.delete(_table_names_key, txn=txn)
327            getattr(self.db, "put_bytes", self.db.put)(_table_names_key,
328                    pickle.dumps(tablelist, 1), txn=txn)
329
330            txn.commit()
331            txn = None
332        except db.DBError, dberror:
333            if txn:
334                txn.abort()
335            if sys.version_info[0] < 3 :
336                raise TableDBError, dberror[1]
337            else :
338                raise TableDBError, dberror.args[1]
339
340
341    def ListTableColumns(self, table):
342        """Return a list of columns in the given table.
343        [] if the table doesn't exist.
344        """
345        assert isinstance(table, str)
346        if contains_metastrings(table):
347            raise ValueError, "bad table name: contains reserved metastrings"
348
349        columnlist_key = _columns_key(table)
350        if not getattr(self.db, "has_key")(columnlist_key):
351            return []
352        pickledcolumnlist = getattr(self.db, "get_bytes",
353                self.db.get)(columnlist_key)
354        if pickledcolumnlist:
355            return pickle.loads(pickledcolumnlist)
356        else:
357            return []
358
359    def ListTables(self):
360        """Return a list of tables in this database."""
361        pickledtablelist = self.db.get_get(_table_names_key)
362        if pickledtablelist:
363            return pickle.loads(pickledtablelist)
364        else:
365            return []
366
367    def CreateOrExtendTable(self, table, columns):
368        """CreateOrExtendTable(table, columns)
369
370        Create a new table in the database.
371
372        If a table of this name already exists, extend it to have any
373        additional columns present in the given list as well as
374        all of its current columns.
375        """
376        assert isinstance(columns, list)
377
378        try:
379            self.CreateTable(table, columns)
380        except TableAlreadyExists:
381            # the table already existed, add any new columns
382            txn = None
383            try:
384                columnlist_key = _columns_key(table)
385                txn = self.env.txn_begin()
386
387                # load the current column list
388                oldcolumnlist = pickle.loads(
389                    getattr(self.db, "get_bytes",
390                        self.db.get)(columnlist_key, txn=txn, flags=db.DB_RMW))
391                # create a hash table for fast lookups of column names in the
392                # loop below
393                oldcolumnhash = {}
394                for c in oldcolumnlist:
395                    oldcolumnhash[c] = c
396
397                # create a new column list containing both the old and new
398                # column names
399                newcolumnlist = copy.copy(oldcolumnlist)
400                for c in columns:
401                    if not oldcolumnhash.has_key(c):
402                        newcolumnlist.append(c)
403
404                # store the table's new extended column list
405                if newcolumnlist != oldcolumnlist :
406                    # delete the old one first since we opened with DB_DUP
407                    self.db.delete(columnlist_key, txn=txn)
408                    getattr(self.db, "put_bytes", self.db.put)(columnlist_key,
409                                pickle.dumps(newcolumnlist, 1),
410                                txn=txn)
411
412                txn.commit()
413                txn = None
414
415                self.__load_column_info(table)
416            except db.DBError, dberror:
417                if txn:
418                    txn.abort()
419                if sys.version_info[0] < 3 :
420                    raise TableDBError, dberror[1]
421                else :
422                    raise TableDBError, dberror.args[1]
423
424
425    def __load_column_info(self, table) :
426        """initialize the self.__tablecolumns dict"""
427        # check the column names
428        try:
429            tcolpickles = getattr(self.db, "get_bytes",
430                    self.db.get)(_columns_key(table))
431        except db.DBNotFoundError:
432            raise TableDBError, "unknown table: %r" % (table,)
433        if not tcolpickles:
434            raise TableDBError, "unknown table: %r" % (table,)
435        self.__tablecolumns[table] = pickle.loads(tcolpickles)
436
437    def __new_rowid(self, table, txn) :
438        """Create a new unique row identifier"""
439        unique = 0
440        while not unique:
441            # Generate a random 64-bit row ID string
442            # (note: might have <64 bits of true randomness
443            # but it's plenty for our database id needs!)
444            blist = []
445            for x in xrange(_rowid_str_len):
446                blist.append(random.randint(0,255))
447            newid = struct.pack('B'*_rowid_str_len, *blist)
448
449            if sys.version_info[0] >= 3 :
450                newid = newid.decode("iso8859-1")  # 8 bits
451
452            # Guarantee uniqueness by adding this key to the database
453            try:
454                self.db.put(_rowid_key(table, newid), None, txn=txn,
455                            flags=db.DB_NOOVERWRITE)
456            except db.DBKeyExistError:
457                pass
458            else:
459                unique = 1
460
461        return newid
462
463
464    def Insert(self, table, rowdict) :
465        """Insert(table, datadict) - Insert a new row into the table
466        using the keys+values from rowdict as the column values.
467        """
468
469        txn = None
470        try:
471            if not getattr(self.db, "has_key")(_columns_key(table)):
472                raise TableDBError, "unknown table"
473
474            # check the validity of each column name
475            if not self.__tablecolumns.has_key(table):
476                self.__load_column_info(table)
477            for column in rowdict.keys() :
478                if not self.__tablecolumns[table].count(column):
479                    raise TableDBError, "unknown column: %r" % (column,)
480
481            # get a unique row identifier for this row
482            txn = self.env.txn_begin()
483            rowid = self.__new_rowid(table, txn=txn)
484
485            # insert the row values into the table database
486            for column, dataitem in rowdict.items():
487                # store the value
488                self.db.put(_data_key(table, column, rowid), dataitem, txn=txn)
489
490            txn.commit()
491            txn = None
492
493        except db.DBError, dberror:
494            # WIBNI we could just abort the txn and re-raise the exception?
495            # But no, because TableDBError is not related to DBError via
496            # inheritance, so it would be backwards incompatible.  Do the next
497            # best thing.
498            info = sys.exc_info()
499            if txn:
500                txn.abort()
501                self.db.delete(_rowid_key(table, rowid))
502            if sys.version_info[0] < 3 :
503                raise TableDBError, dberror[1], info[2]
504            else :
505                raise TableDBError, dberror.args[1], info[2]
506
507
508    def Modify(self, table, conditions={}, mappings={}):
509        """Modify(table, conditions={}, mappings={}) - Modify items in rows matching 'conditions' using mapping functions in 'mappings'
510
511        * table - the table name
512        * conditions - a dictionary keyed on column names containing
513          a condition callable expecting the data string as an
514          argument and returning a boolean.
515        * mappings - a dictionary keyed on column names containing a
516          condition callable expecting the data string as an argument and
517          returning the new string for that column.
518        """
519
520        try:
521            matching_rowids = self.__Select(table, [], conditions)
522
523            # modify only requested columns
524            columns = mappings.keys()
525            for rowid in matching_rowids.keys():
526                txn = None
527                try:
528                    for column in columns:
529                        txn = self.env.txn_begin()
530                        # modify the requested column
531                        try:
532                            dataitem = self.db.get(
533                                _data_key(table, column, rowid),
534                                txn=txn)
535                            self.db.delete(
536                                _data_key(table, column, rowid),
537                                txn=txn)
538                        except db.DBNotFoundError:
539                             # XXXXXXX row key somehow didn't exist, assume no
540                             # error
541                            dataitem = None
542                        dataitem = mappings[column](dataitem)
543                        if dataitem <> None:
544                            self.db.put(
545                                _data_key(table, column, rowid),
546                                dataitem, txn=txn)
547                        txn.commit()
548                        txn = None
549
550                # catch all exceptions here since we call unknown callables
551                except:
552                    if txn:
553                        txn.abort()
554                    raise
555
556        except db.DBError, dberror:
557            if sys.version_info[0] < 3 :
558                raise TableDBError, dberror[1]
559            else :
560                raise TableDBError, dberror.args[1]
561
562    def Delete(self, table, conditions={}):
563        """Delete(table, conditions) - Delete items matching the given
564        conditions from the table.
565
566        * conditions - a dictionary keyed on column names containing
567          condition functions expecting the data string as an
568          argument and returning a boolean.
569        """
570
571        try:
572            matching_rowids = self.__Select(table, [], conditions)
573
574            # delete row data from all columns
575            columns = self.__tablecolumns[table]
576            for rowid in matching_rowids.keys():
577                txn = None
578                try:
579                    txn = self.env.txn_begin()
580                    for column in columns:
581                        # delete the data key
582                        try:
583                            self.db.delete(_data_key(table, column, rowid),
584                                           txn=txn)
585                        except db.DBNotFoundError:
586                            # XXXXXXX column may not exist, assume no error
587                            pass
588
589                    try:
590                        self.db.delete(_rowid_key(table, rowid), txn=txn)
591                    except db.DBNotFoundError:
592                        # XXXXXXX row key somehow didn't exist, assume no error
593                        pass
594                    txn.commit()
595                    txn = None
596                except db.DBError, dberror:
597                    if txn:
598                        txn.abort()
599                    raise
600        except db.DBError, dberror:
601            if sys.version_info[0] < 3 :
602                raise TableDBError, dberror[1]
603            else :
604                raise TableDBError, dberror.args[1]
605
606
607    def Select(self, table, columns, conditions={}):
608        """Select(table, columns, conditions) - retrieve specific row data
609        Returns a list of row column->value mapping dictionaries.
610
611        * columns - a list of which column data to return.  If
612          columns is None, all columns will be returned.
613        * conditions - a dictionary keyed on column names
614          containing callable conditions expecting the data string as an
615          argument and returning a boolean.
616        """
617        try:
618            if not self.__tablecolumns.has_key(table):
619                self.__load_column_info(table)
620            if columns is None:
621                columns = self.__tablecolumns[table]
622            matching_rowids = self.__Select(table, columns, conditions)
623        except db.DBError, dberror:
624            if sys.version_info[0] < 3 :
625                raise TableDBError, dberror[1]
626            else :
627                raise TableDBError, dberror.args[1]
628        # return the matches as a list of dictionaries
629        return matching_rowids.values()
630
631
632    def __Select(self, table, columns, conditions):
633        """__Select() - Used to implement Select and Delete (above)
634        Returns a dictionary keyed on rowids containing dicts
635        holding the row data for columns listed in the columns param
636        that match the given conditions.
637        * conditions is a dictionary keyed on column names
638        containing callable conditions expecting the data string as an
639        argument and returning a boolean.
640        """
641        # check the validity of each column name
642        if not self.__tablecolumns.has_key(table):
643            self.__load_column_info(table)
644        if columns is None:
645            columns = self.tablecolumns[table]
646        for column in (columns + conditions.keys()):
647            if not self.__tablecolumns[table].count(column):
648                raise TableDBError, "unknown column: %r" % (column,)
649
650        # keyed on rows that match so far, containings dicts keyed on
651        # column names containing the data for that row and column.
652        matching_rowids = {}
653        # keys are rowids that do not match
654        rejected_rowids = {}
655
656        # attempt to sort the conditions in such a way as to minimize full
657        # column lookups
658        def cmp_conditions(atuple, btuple):
659            a = atuple[1]
660            b = btuple[1]
661            if type(a) is type(b):
662                if isinstance(a, PrefixCond) and isinstance(b, PrefixCond):
663                    # longest prefix first
664                    return cmp(len(b.prefix), len(a.prefix))
665                if isinstance(a, LikeCond) and isinstance(b, LikeCond):
666                    # longest likestr first
667                    return cmp(len(b.likestr), len(a.likestr))
668                return 0
669            if isinstance(a, ExactCond):
670                return -1
671            if isinstance(b, ExactCond):
672                return 1
673            if isinstance(a, PrefixCond):
674                return -1
675            if isinstance(b, PrefixCond):
676                return 1
677            # leave all unknown condition callables alone as equals
678            return 0
679
680        if sys.version_info[0] < 3 :
681            conditionlist = conditions.items()
682            conditionlist.sort(cmp_conditions)
683        else :  # Insertion Sort. Please, improve
684            conditionlist = []
685            for i in conditions.items() :
686                for j, k in enumerate(conditionlist) :
687                    r = cmp_conditions(k, i)
688                    if r == 1 :
689                        conditionlist.insert(j, i)
690                        break
691                else :
692                    conditionlist.append(i)
693
694        # Apply conditions to column data to find what we want
695        cur = self.db.cursor()
696        column_num = -1
697        for column, condition in conditionlist:
698            column_num = column_num + 1
699            searchkey = _search_col_data_key(table, column)
700            # speedup: don't linear search columns within loop
701            if column in columns:
702                savethiscolumndata = 1  # save the data for return
703            else:
704                savethiscolumndata = 0  # data only used for selection
705
706            try:
707                key, data = cur.set_range(searchkey)
708                while key[:len(searchkey)] == searchkey:
709                    # extract the rowid from the key
710                    rowid = key[-_rowid_str_len:]
711
712                    if not rejected_rowids.has_key(rowid):
713                        # if no condition was specified or the condition
714                        # succeeds, add row to our match list.
715                        if not condition or condition(data):
716                            if not matching_rowids.has_key(rowid):
717                                matching_rowids[rowid] = {}
718                            if savethiscolumndata:
719                                matching_rowids[rowid][column] = data
720                        else:
721                            if matching_rowids.has_key(rowid):
722                                del matching_rowids[rowid]
723                            rejected_rowids[rowid] = rowid
724
725                    key, data = cur.next()
726
727            except db.DBError, dberror:
728                if sys.version_info[0] < 3 :
729                    if dberror[0] != db.DB_NOTFOUND:
730                        raise
731                else :
732                    if dberror.args[0] != db.DB_NOTFOUND:
733                        raise
734                continue
735
736        cur.close()
737
738        # we're done selecting rows, garbage collect the reject list
739        del rejected_rowids
740
741        # extract any remaining desired column data from the
742        # database for the matching rows.
743        if len(columns) > 0:
744            for rowid, rowdata in matching_rowids.items():
745                for column in columns:
746                    if rowdata.has_key(column):
747                        continue
748                    try:
749                        rowdata[column] = self.db.get(
750                            _data_key(table, column, rowid))
751                    except db.DBError, dberror:
752                        if sys.version_info[0] < 3 :
753                            if dberror[0] != db.DB_NOTFOUND:
754                                raise
755                        else :
756                            if dberror.args[0] != db.DB_NOTFOUND:
757                                raise
758                        rowdata[column] = None
759
760        # return the matches
761        return matching_rowids
762
763
764    def Drop(self, table):
765        """Remove an entire table from the database"""
766        txn = None
767        try:
768            txn = self.env.txn_begin()
769
770            # delete the column list
771            self.db.delete(_columns_key(table), txn=txn)
772
773            cur = self.db.cursor(txn)
774
775            # delete all keys containing this tables column and row info
776            table_key = _search_all_data_key(table)
777            while 1:
778                try:
779                    key, data = cur.set_range(table_key)
780                except db.DBNotFoundError:
781                    break
782                # only delete items in this table
783                if key[:len(table_key)] != table_key:
784                    break
785                cur.delete()
786
787            # delete all rowids used by this table
788            table_key = _search_rowid_key(table)
789            while 1:
790                try:
791                    key, data = cur.set_range(table_key)
792                except db.DBNotFoundError:
793                    break
794                # only delete items in this table
795                if key[:len(table_key)] != table_key:
796                    break
797                cur.delete()
798
799            cur.close()
800
801            # delete the tablename from the table name list
802            tablelist = pickle.loads(
803                getattr(self.db, "get_bytes", self.db.get)(_table_names_key,
804                    txn=txn, flags=db.DB_RMW))
805            try:
806                tablelist.remove(table)
807            except ValueError:
808                # hmm, it wasn't there, oh well, that's what we want.
809                pass
810            # delete 1st, incase we opened with DB_DUP
811            self.db.delete(_table_names_key, txn=txn)
812            getattr(self.db, "put_bytes", self.db.put)(_table_names_key,
813                    pickle.dumps(tablelist, 1), txn=txn)
814
815            txn.commit()
816            txn = None
817
818            if self.__tablecolumns.has_key(table):
819                del self.__tablecolumns[table]
820
821        except db.DBError, dberror:
822            if txn:
823                txn.abort()
824            if sys.version_info[0] < 3 :
825                raise TableDBError, dberror[1]
826            else :
827                raise TableDBError, dberror.args[1]