symbian-qemu-0.9.1-12/python-2.6.1/Lib/bsddb/dbtables.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     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 
       
    20 import re
       
    21 import sys
       
    22 import copy
       
    23 import random
       
    24 import struct
       
    25 import cPickle as pickle
       
    26 
       
    27 try:
       
    28     # For Pythons w/distutils pybsddb
       
    29     from bsddb3 import db
       
    30 except ImportError:
       
    31     # For Python 2.3
       
    32     from bsddb import db
       
    33 
       
    34 # XXX(nnorwitz): is this correct? DBIncompleteError is conditional in _bsddb.c
       
    35 if not hasattr(db,"DBIncompleteError") :
       
    36     class DBIncompleteError(Exception):
       
    37         pass
       
    38     db.DBIncompleteError = DBIncompleteError
       
    39 
       
    40 class TableDBError(StandardError):
       
    41     pass
       
    42 class TableAlreadyExists(TableDBError):
       
    43     pass
       
    44 
       
    45 
       
    46 class Cond:
       
    47     """This condition matches everything"""
       
    48     def __call__(self, s):
       
    49         return 1
       
    50 
       
    51 class 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 
       
    58 class 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 
       
    65 class 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 
       
    72 class 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 
       
    95 def _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 
       
   107 def _data_key(table, col, rowid):
       
   108     return table + _data + col + _data + rowid
       
   109 
       
   110 def _search_col_data_key(table, col):
       
   111     return table + _data + col + _data
       
   112 
       
   113 def _search_all_data_key(table):
       
   114     return table + _data
       
   115 
       
   116 def _rowid_key(table, rowid):
       
   117     return table + _rowid + rowid + _rowid
       
   118 
       
   119 def _search_rowid_key(table):
       
   120     return table + _rowid
       
   121 
       
   122 def 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 
       
   136 class 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]