blob: 3ebc68d5b0a4b8fffe350027e593b9859b729eeb [file] [log] [blame]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +00001#-----------------------------------------------------------------------
2#
3# Copyright (C) 2000, 2001 by Autonomous Zone Industries
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +00004# Copyright (C) 2002 Gregory P. Smith
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +00005#
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#
Gregory P. Smithf8057852007-09-09 20:25:00 +000013# -- Gregory P. Smith <greg@krypto.org>
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000014
15# This provides a simple database table interface built on top of
Jesus Ceaca3939c2008-05-22 15:27:38 +000016# the Python Berkeley DB 3 interface.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000017#
18_cvsid = '$Id$'
19
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000020import re
Barry Warsawf71de3e2003-01-28 17:20:44 +000021import sys
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000022import copy
Tim Peters95334a52004-08-08 00:54:21 +000023import random
Gregory P. Smithafed3a42007-10-18 07:56:54 +000024import struct
Jesus Cea6557aac2010-03-22 14:22:26 +000025
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.3 and 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.3, 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
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000054
Barry Warsawf71de3e2003-01-28 17:20:44 +000055try:
Gregory P. Smith41631e82003-09-21 00:08:14 +000056 # For Pythons w/distutils pybsddb
Jesus Cea4907d272008-08-31 14:00:51 +000057 from bsddb3 import db
Gregory P. Smith41631e82003-09-21 00:08:14 +000058except ImportError:
Barry Warsawf71de3e2003-01-28 17:20:44 +000059 # For Python 2.3
Jesus Cea4907d272008-08-31 14:00:51 +000060 from bsddb import db
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000061
Barry Warsawf71de3e2003-01-28 17:20:44 +000062class TableDBError(StandardError):
63 pass
64class TableAlreadyExists(TableDBError):
65 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000066
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
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +000087class 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
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000094class 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 :
Barry Warsawf71de3e2003-01-28 17:20:44 +0000104 likestr = likestr.replace(char, '\\'+char)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000105 # convert %s to wildcards
Barry Warsawf71de3e2003-01-28 17:20:44 +0000106 self.likestr = likestr.replace('%', '.*')
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000107 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
Barry Warsawf71de3e2003-01-28 17:20:44 +0000116
117def _columns_key(table):
118 return table + _columns
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000119
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
Barry Warsawf71de3e2003-01-28 17:20:44 +0000127
Jesus Cea4907d272008-08-31 14:00:51 +0000128
Barry Warsawf71de3e2003-01-28 17:20:44 +0000129def _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
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000143
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 """
Barry Warsawf71de3e2003-01-28 17:20:44 +0000148 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
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000153 return 1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000154 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000155 return 0
156
157
158class bsdTableDB :
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000159 def __init__(self, filename, dbhome, create=0, truncate=0, mode=0600,
Barry Warsawf71de3e2003-01-28 17:20:44 +0000160 recover=0, dbflags=0):
Gregory P. Smithff7d9912006-06-08 05:17:08 +0000161 """bsdTableDB(filename, dbhome, create=0, truncate=0, mode=0600)
162
Jesus Ceaca3939c2008-05-22 15:27:38 +0000163 Open database name in the dbhome Berkeley DB directory.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000164 Use keyword arguments when calling this constructor.
165 """
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000166 self.db = None
Jesus Cea4907d272008-08-31 14:00:51 +0000167 myflags = db.DB_THREAD
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000168 if create:
Jesus Cea4907d272008-08-31 14:00:51 +0000169 myflags |= db.DB_CREATE
170 flagsforenv = (db.DB_INIT_MPOOL | db.DB_INIT_LOCK | db.DB_INIT_LOG |
171 db.DB_INIT_TXN | dbflags)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000172 # DB_AUTO_COMMIT isn't a valid flag for env.open()
173 try:
Jesus Cea4907d272008-08-31 14:00:51 +0000174 dbflags |= db.DB_AUTO_COMMIT
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000175 except AttributeError:
176 pass
177 if recover:
Jesus Cea4907d272008-08-31 14:00:51 +0000178 flagsforenv = flagsforenv | db.DB_RECOVER
179 self.env = db.DBEnv()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000180 # enable auto deadlock avoidance
Jesus Cea4907d272008-08-31 14:00:51 +0000181 self.env.set_lk_detect(db.DB_LOCK_DEFAULT)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000182 self.env.open(dbhome, myflags | flagsforenv)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000183 if truncate:
Jesus Cea4907d272008-08-31 14:00:51 +0000184 myflags |= db.DB_TRUNCATE
185 self.db = db.DB(self.env)
Gregory P. Smith455d46f2003-07-09 04:45:59 +0000186 # this code relies on DBCursor.set* methods to raise exceptions
187 # rather than returning None
188 self.db.set_get_returns_none(1)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000189 # allow duplicate entries [warning: be careful w/ metadata]
Jesus Cea4907d272008-08-31 14:00:51 +0000190 self.db.set_flags(db.DB_DUP)
191 self.db.open(filename, db.DB_BTREE, dbflags | myflags, mode)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000192 self.dbfilename = filename
Jesus Cea4907d272008-08-31 14:00:51 +0000193
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"))
Ezio Melotti8d3f1302010-02-02 15:57:45 +0000204 if v is not None :
Jesus Cea4907d272008-08-31 14:00:51 +0000205 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")()
Ezio Melotti8d3f1302010-02-02 15:57:45 +0000211 if v is not None :
Jesus Cea4907d272008-08-31 14:00:51 +0000212 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")
Ezio Melotti8d3f1302010-02-02 15:57:45 +0000229 if value is not None :
Jesus Cea4907d272008-08-31 14:00:51 +0000230 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)
Ezio Melotti8d3f1302010-02-02 15:57:45 +0000240 if v is not None :
Jesus Cea4907d272008-08-31 14:00:51 +0000241 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
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000259 # Initialize the table names list if this is a new database
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000260 txn = self.env.txn_begin()
261 try:
Jesus Cea4907d272008-08-31 14:00:51 +0000262 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)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000265 # Yes, bare except
266 except:
267 txn.abort()
268 raise
269 else:
270 txn.commit()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000271 # TODO verify more of the database's metadata?
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000272 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):
Jesus Cea6557aac2010-03-22 14:22:26 +0000286 self.env.txn_checkpoint(mins)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000287
288 def sync(self):
Jesus Cea6557aac2010-03-22 14:22:26 +0000289 self.db.sync()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000290
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()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000297 while 1:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000298 print repr({key: data})
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000299 next = cur.next()
300 if next:
301 key, data = next
302 else:
303 cur.close()
304 return
Jesus Cea4907d272008-08-31 14:00:51 +0000305 except db.DBNotFoundError:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000306 cur.close()
307
308
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000309 def CreateTable(self, table, columns):
Gregory P. Smithff7d9912006-06-08 05:17:08 +0000310 """CreateTable(table, columns) - Create a new table in the database.
311
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000312 raises TableDBError if it already exists or for other DB errors.
313 """
Jesus Ceac5a11fa2008-07-23 11:38:42 +0000314 assert isinstance(columns, list)
Jesus Cea4907d272008-08-31 14:00:51 +0000315
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000316 txn = None
317 try:
318 # checking sanity of the table and column names here on
319 # table creation will prevent problems elsewhere.
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000320 if contains_metastrings(table):
321 raise ValueError(
322 "bad table name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000323 for column in columns :
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000324 if contains_metastrings(column):
325 raise ValueError(
326 "bad column name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000327
328 columnlist_key = _columns_key(table)
Jesus Cea4907d272008-08-31 14:00:51 +0000329 if getattr(self.db, "has_key")(columnlist_key):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000330 raise TableAlreadyExists, "table already exists"
331
332 txn = self.env.txn_begin()
333 # store the table's column info
Jesus Cea4907d272008-08-31 14:00:51 +0000334 getattr(self.db, "put_bytes", self.db.put)(columnlist_key,
335 pickle.dumps(columns, 1), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000336
337 # add the table name to the tablelist
Jesus Cea4907d272008-08-31 14:00:51 +0000338 tablelist = pickle.loads(getattr(self.db, "get_bytes",
339 self.db.get) (_table_names_key, txn=txn, flags=db.DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000340 tablelist.append(table)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000341 # delete 1st, in case we opened with DB_DUP
Gregory P. Smithafed3a42007-10-18 07:56:54 +0000342 self.db.delete(_table_names_key, txn=txn)
Jesus Cea4907d272008-08-31 14:00:51 +0000343 getattr(self.db, "put_bytes", self.db.put)(_table_names_key,
344 pickle.dumps(tablelist, 1), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000345
346 txn.commit()
347 txn = None
Jesus Cea4907d272008-08-31 14:00:51 +0000348 except db.DBError, dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000349 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000350 txn.abort()
Ezio Melotti5d62cfe2010-02-02 08:37:35 +0000351 if sys.version_info < (2, 6) :
Jesus Cea4907d272008-08-31 14:00:51 +0000352 raise TableDBError, dberror[1]
353 else :
354 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000355
356
357 def ListTableColumns(self, table):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000358 """Return a list of columns in the given table.
359 [] if the table doesn't exist.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000360 """
Jesus Ceac5a11fa2008-07-23 11:38:42 +0000361 assert isinstance(table, str)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000362 if contains_metastrings(table):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000363 raise ValueError, "bad table name: contains reserved metastrings"
364
365 columnlist_key = _columns_key(table)
Jesus Cea4907d272008-08-31 14:00:51 +0000366 if not getattr(self.db, "has_key")(columnlist_key):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000367 return []
Jesus Cea4907d272008-08-31 14:00:51 +0000368 pickledcolumnlist = getattr(self.db, "get_bytes",
369 self.db.get)(columnlist_key)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000370 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."""
Jesus Cea4907d272008-08-31 14:00:51 +0000377 pickledtablelist = self.db.get_get(_table_names_key)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000378 if pickledtablelist:
379 return pickle.loads(pickledtablelist)
380 else:
381 return []
382
383 def CreateOrExtendTable(self, table, columns):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000384 """CreateOrExtendTable(table, columns)
385
Gregory P. Smithff7d9912006-06-08 05:17:08 +0000386 Create a new table in the database.
387
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000388 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 """
Jesus Ceac5a11fa2008-07-23 11:38:42 +0000392 assert isinstance(columns, list)
Jesus Cea4907d272008-08-31 14:00:51 +0000393
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000394 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
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000404 oldcolumnlist = pickle.loads(
Jesus Cea4907d272008-08-31 14:00:51 +0000405 getattr(self.db, "get_bytes",
406 self.db.get)(columnlist_key, txn=txn, flags=db.DB_RMW))
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000407 # create a hash table for fast lookups of column names in the
408 # loop below
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000409 oldcolumnhash = {}
410 for c in oldcolumnlist:
411 oldcolumnhash[c] = c
412
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000413 # create a new column list containing both the old and new
414 # column names
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000415 newcolumnlist = copy.copy(oldcolumnlist)
416 for c in columns:
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000417 if not c in oldcolumnhash:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000418 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
Gregory P. Smithafed3a42007-10-18 07:56:54 +0000423 self.db.delete(columnlist_key, txn=txn)
Jesus Cea4907d272008-08-31 14:00:51 +0000424 getattr(self.db, "put_bytes", self.db.put)(columnlist_key,
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000425 pickle.dumps(newcolumnlist, 1),
426 txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000427
428 txn.commit()
429 txn = None
430
431 self.__load_column_info(table)
Jesus Cea4907d272008-08-31 14:00:51 +0000432 except db.DBError, dberror:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000433 if txn:
434 txn.abort()
Ezio Melotti5d62cfe2010-02-02 08:37:35 +0000435 if sys.version_info < (2, 6) :
Jesus Cea4907d272008-08-31 14:00:51 +0000436 raise TableDBError, dberror[1]
437 else :
438 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000439
440
441 def __load_column_info(self, table) :
442 """initialize the self.__tablecolumns dict"""
443 # check the column names
444 try:
Jesus Cea4907d272008-08-31 14:00:51 +0000445 tcolpickles = getattr(self.db, "get_bytes",
446 self.db.get)(_columns_key(table))
447 except db.DBNotFoundError:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000448 raise TableDBError, "unknown table: %r" % (table,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000449 if not tcolpickles:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000450 raise TableDBError, "unknown table: %r" % (table,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000451 self.__tablecolumns[table] = pickle.loads(tcolpickles)
452
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000453 def __new_rowid(self, table, txn) :
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000454 """Create a new unique row identifier"""
455 unique = 0
Barry Warsawf71de3e2003-01-28 17:20:44 +0000456 while not unique:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000457 # Generate a random 64-bit row ID string
Gregory P. Smith6d331ca2007-11-01 21:15:36 +0000458 # (note: might have <64 bits of true randomness
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000459 # but it's plenty for our database id needs!)
Gregory P. Smith3ef21cb2007-10-18 16:32:02 +0000460 blist = []
461 for x in xrange(_rowid_str_len):
Gregory P. Smith6d331ca2007-11-01 21:15:36 +0000462 blist.append(random.randint(0,255))
Gregory P. Smith3ef21cb2007-10-18 16:32:02 +0000463 newid = struct.pack('B'*_rowid_str_len, *blist)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000464
Jesus Cea4907d272008-08-31 14:00:51 +0000465 if sys.version_info[0] >= 3 :
466 newid = newid.decode("iso8859-1") # 8 bits
467
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000468 # Guarantee uniqueness by adding this key to the database
469 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000470 self.db.put(_rowid_key(table, newid), None, txn=txn,
Jesus Cea4907d272008-08-31 14:00:51 +0000471 flags=db.DB_NOOVERWRITE)
472 except db.DBKeyExistError:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000473 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 """
Jesus Cea4907d272008-08-31 14:00:51 +0000484
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000485 txn = None
486 try:
Jesus Cea4907d272008-08-31 14:00:51 +0000487 if not getattr(self.db, "has_key")(_columns_key(table)):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000488 raise TableDBError, "unknown table"
489
490 # check the validity of each column name
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000491 if not table in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000492 self.__load_column_info(table)
493 for column in rowdict.keys() :
Barry Warsawf71de3e2003-01-28 17:20:44 +0000494 if not self.__tablecolumns[table].count(column):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000495 raise TableDBError, "unknown column: %r" % (column,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000496
497 # get a unique row identifier for this row
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000498 txn = self.env.txn_begin()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000499 rowid = self.__new_rowid(table, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000500
501 # insert the row values into the table database
Barry Warsawf71de3e2003-01-28 17:20:44 +0000502 for column, dataitem in rowdict.items():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000503 # store the value
504 self.db.put(_data_key(table, column, rowid), dataitem, txn=txn)
505
506 txn.commit()
507 txn = None
508
Jesus Cea4907d272008-08-31 14:00:51 +0000509 except db.DBError, dberror:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000510 # 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:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000516 txn.abort()
517 self.db.delete(_rowid_key(table, rowid))
Ezio Melotti5d62cfe2010-02-02 08:37:35 +0000518 if sys.version_info < (2, 6) :
Jesus Cea4907d272008-08-31 14:00:51 +0000519 raise TableDBError, dberror[1], info[2]
520 else :
521 raise TableDBError, dberror.args[1], info[2]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000522
523
Barry Warsawf71de3e2003-01-28 17:20:44 +0000524 def Modify(self, table, conditions={}, mappings={}):
Gregory P. Smithff7d9912006-06-08 05:17:08 +0000525 """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.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000534 """
Jesus Cea4907d272008-08-31 14:00:51 +0000535
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000536 try:
537 matching_rowids = self.__Select(table, [], conditions)
538
539 # modify only requested columns
540 columns = mappings.keys()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000541 for rowid in matching_rowids.keys():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000542 txn = None
543 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000544 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000545 txn = self.env.txn_begin()
546 # modify the requested column
547 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000548 dataitem = self.db.get(
549 _data_key(table, column, rowid),
Gregory P. Smithafed3a42007-10-18 07:56:54 +0000550 txn=txn)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000551 self.db.delete(
552 _data_key(table, column, rowid),
Gregory P. Smithafed3a42007-10-18 07:56:54 +0000553 txn=txn)
Jesus Cea4907d272008-08-31 14:00:51 +0000554 except db.DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000555 # XXXXXXX row key somehow didn't exist, assume no
556 # error
557 dataitem = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000558 dataitem = mappings[column](dataitem)
Ezio Melotti8d3f1302010-02-02 15:57:45 +0000559 if dataitem is not None:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000560 self.db.put(
561 _data_key(table, column, rowid),
562 dataitem, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000563 txn.commit()
564 txn = None
565
Gregory P. Smithff7d9912006-06-08 05:17:08 +0000566 # catch all exceptions here since we call unknown callables
567 except:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000568 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000569 txn.abort()
570 raise
571
Jesus Cea4907d272008-08-31 14:00:51 +0000572 except db.DBError, dberror:
Ezio Melotti5d62cfe2010-02-02 08:37:35 +0000573 if sys.version_info < (2, 6) :
Jesus Cea4907d272008-08-31 14:00:51 +0000574 raise TableDBError, dberror[1]
575 else :
576 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000577
Barry Warsawf71de3e2003-01-28 17:20:44 +0000578 def Delete(self, table, conditions={}):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000579 """Delete(table, conditions) - Delete items matching the given
580 conditions from the table.
Gregory P. Smithff7d9912006-06-08 05:17:08 +0000581
582 * conditions - a dictionary keyed on column names containing
583 condition functions expecting the data string as an
584 argument and returning a boolean.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000585 """
Jesus Cea4907d272008-08-31 14:00:51 +0000586
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000587 try:
588 matching_rowids = self.__Select(table, [], conditions)
589
590 # delete row data from all columns
591 columns = self.__tablecolumns[table]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000592 for rowid in matching_rowids.keys():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000593 txn = None
594 try:
595 txn = self.env.txn_begin()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000596 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000597 # delete the data key
598 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000599 self.db.delete(_data_key(table, column, rowid),
Gregory P. Smithafed3a42007-10-18 07:56:54 +0000600 txn=txn)
Jesus Cea4907d272008-08-31 14:00:51 +0000601 except db.DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000602 # XXXXXXX column may not exist, assume no error
603 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000604
605 try:
Gregory P. Smithafed3a42007-10-18 07:56:54 +0000606 self.db.delete(_rowid_key(table, rowid), txn=txn)
Jesus Cea4907d272008-08-31 14:00:51 +0000607 except db.DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000608 # XXXXXXX row key somehow didn't exist, assume no error
609 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000610 txn.commit()
611 txn = None
Jesus Cea4907d272008-08-31 14:00:51 +0000612 except db.DBError, dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000613 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000614 txn.abort()
615 raise
Jesus Cea4907d272008-08-31 14:00:51 +0000616 except db.DBError, dberror:
Ezio Melotti5d62cfe2010-02-02 08:37:35 +0000617 if sys.version_info < (2, 6) :
Jesus Cea4907d272008-08-31 14:00:51 +0000618 raise TableDBError, dberror[1]
619 else :
620 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000621
622
Barry Warsawf71de3e2003-01-28 17:20:44 +0000623 def Select(self, table, columns, conditions={}):
Gregory P. Smithff7d9912006-06-08 05:17:08 +0000624 """Select(table, columns, conditions) - retrieve specific row data
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000625 Returns a list of row column->value mapping dictionaries.
Gregory P. Smithff7d9912006-06-08 05:17:08 +0000626
627 * columns - a list of which column data to return. If
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000628 columns is None, all columns will be returned.
Gregory P. Smithff7d9912006-06-08 05:17:08 +0000629 * conditions - a dictionary keyed on column names
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000630 containing callable conditions expecting the data string as an
631 argument and returning a boolean.
632 """
633 try:
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000634 if not table in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000635 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000636 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000637 columns = self.__tablecolumns[table]
638 matching_rowids = self.__Select(table, columns, conditions)
Jesus Cea4907d272008-08-31 14:00:51 +0000639 except db.DBError, dberror:
Ezio Melotti5d62cfe2010-02-02 08:37:35 +0000640 if sys.version_info < (2, 6) :
Jesus Cea4907d272008-08-31 14:00:51 +0000641 raise TableDBError, dberror[1]
642 else :
643 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000644 # return the matches as a list of dictionaries
645 return matching_rowids.values()
646
647
Barry Warsawf71de3e2003-01-28 17:20:44 +0000648 def __Select(self, table, columns, conditions):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000649 """__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
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000658 if not table in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000659 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000660 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000661 columns = self.tablecolumns[table]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000662 for column in (columns + conditions.keys()):
663 if not self.__tablecolumns[table].count(column):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000664 raise TableDBError, "unknown column: %r" % (column,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000665
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 = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000669 # keys are rowids that do not match
670 rejected_rowids = {}
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000671
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000672 # attempt to sort the conditions in such a way as to minimize full
673 # column lookups
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000674 def cmp_conditions(atuple, btuple):
675 a = atuple[1]
676 b = btuple[1]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000677 if type(a) is type(b):
Jesus Cea6557aac2010-03-22 14:22:26 +0000678
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
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000685 if isinstance(a, PrefixCond) and isinstance(b, PrefixCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000686 # longest prefix first
687 return cmp(len(b.prefix), len(a.prefix))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000688 if isinstance(a, LikeCond) and isinstance(b, LikeCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000689 # longest likestr first
690 return cmp(len(b.likestr), len(a.likestr))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000691 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
Ezio Melotti5d62cfe2010-02-02 08:37:35 +0000703 if sys.version_info < (2, 6) :
Jesus Cea4907d272008-08-31 14:00:51 +0000704 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)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000716
717 # Apply conditions to column data to find what we want
718 cur = self.db.cursor()
719 column_num = -1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000720 for column, condition in conditionlist:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000721 column_num = column_num + 1
722 searchkey = _search_col_data_key(table, column)
723 # speedup: don't linear search columns within loop
Barry Warsawf71de3e2003-01-28 17:20:44 +0000724 if column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000725 savethiscolumndata = 1 # save the data for return
Barry Warsawf71de3e2003-01-28 17:20:44 +0000726 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000727 savethiscolumndata = 0 # data only used for selection
728
729 try:
730 key, data = cur.set_range(searchkey)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000731 while key[:len(searchkey)] == searchkey:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000732 # extract the rowid from the key
733 rowid = key[-_rowid_str_len:]
734
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000735 if not rowid in rejected_rowids:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000736 # if no condition was specified or the condition
737 # succeeds, add row to our match list.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000738 if not condition or condition(data):
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000739 if not rowid in matching_rowids:
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000740 matching_rowids[rowid] = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000741 if savethiscolumndata:
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000742 matching_rowids[rowid][column] = data
Barry Warsawf71de3e2003-01-28 17:20:44 +0000743 else:
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000744 if rowid in matching_rowids:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000745 del matching_rowids[rowid]
746 rejected_rowids[rowid] = rowid
747
748 key, data = cur.next()
749
Jesus Cea4907d272008-08-31 14:00:51 +0000750 except db.DBError, dberror:
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000751 if dberror.args[0] != db.DB_NOTFOUND:
752 raise
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000753 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.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000762 if len(columns) > 0:
763 for rowid, rowdata in matching_rowids.items():
764 for column in columns:
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000765 if column in rowdata:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000766 continue
767 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000768 rowdata[column] = self.db.get(
769 _data_key(table, column, rowid))
Jesus Cea4907d272008-08-31 14:00:51 +0000770 except db.DBError, dberror:
Ezio Melotti5d62cfe2010-02-02 08:37:35 +0000771 if sys.version_info < (2, 6) :
Jesus Cea4907d272008-08-31 14:00:51 +0000772 if dberror[0] != db.DB_NOTFOUND:
773 raise
774 else :
775 if dberror.args[0] != db.DB_NOTFOUND:
776 raise
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000777 rowdata[column] = None
778
779 # return the matches
780 return matching_rowids
781
782
Barry Warsawf71de3e2003-01-28 17:20:44 +0000783 def Drop(self, table):
784 """Remove an entire table from the database"""
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000785 txn = None
786 try:
787 txn = self.env.txn_begin()
788
789 # delete the column list
Gregory P. Smithafed3a42007-10-18 07:56:54 +0000790 self.db.delete(_columns_key(table), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000791
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)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000796 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000797 try:
798 key, data = cur.set_range(table_key)
Jesus Cea4907d272008-08-31 14:00:51 +0000799 except db.DBNotFoundError:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000800 break
801 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000802 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000803 break
804 cur.delete()
805
806 # delete all rowids used by this table
807 table_key = _search_rowid_key(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000808 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000809 try:
810 key, data = cur.set_range(table_key)
Jesus Cea4907d272008-08-31 14:00:51 +0000811 except db.DBNotFoundError:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000812 break
813 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000814 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000815 break
816 cur.delete()
817
818 cur.close()
819
820 # delete the tablename from the table name list
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000821 tablelist = pickle.loads(
Jesus Cea4907d272008-08-31 14:00:51 +0000822 getattr(self.db, "get_bytes", self.db.get)(_table_names_key,
823 txn=txn, flags=db.DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000824 try:
825 tablelist.remove(table)
826 except ValueError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000827 # hmm, it wasn't there, oh well, that's what we want.
828 pass
829 # delete 1st, incase we opened with DB_DUP
Gregory P. Smithafed3a42007-10-18 07:56:54 +0000830 self.db.delete(_table_names_key, txn=txn)
Jesus Cea4907d272008-08-31 14:00:51 +0000831 getattr(self.db, "put_bytes", self.db.put)(_table_names_key,
832 pickle.dumps(tablelist, 1), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000833
834 txn.commit()
835 txn = None
836
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000837 if table in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000838 del self.__tablecolumns[table]
839
Jesus Cea4907d272008-08-31 14:00:51 +0000840 except db.DBError, dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000841 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000842 txn.abort()
Antoine Pitrou63b0cb22009-10-14 18:01:33 +0000843 raise TableDBError(dberror.args[1])