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