blob: 85dbb4aa3b56adc659ba5ae52d619a3139d81ac5 [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#
13# -- Gregory P. Smith <greg@electricrain.com>
14
15# This provides a simple database table interface built on top of
16# the Python BerkeleyDB 3 interface.
17#
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
Barry Warsawf71de3e2003-01-28 17:20:44 +000023import xdrlib
24import whrandom
25from types import ListType, StringType
26import cPickle as pickle
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000027
Barry Warsawf71de3e2003-01-28 17:20:44 +000028try:
29 # For Python 2.3
30 from bsddb.db import *
31except ImportError:
32 # For earlier Pythons w/distutils pybsddb
33 from bsddb3.db import *
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000034
35
Barry Warsawf71de3e2003-01-28 17:20:44 +000036class TableDBError(StandardError):
37 pass
38class TableAlreadyExists(TableDBError):
39 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000040
41
42class Cond:
43 """This condition matches everything"""
44 def __call__(self, s):
45 return 1
46
47class ExactCond(Cond):
48 """Acts as an exact match condition function"""
49 def __init__(self, strtomatch):
50 self.strtomatch = strtomatch
51 def __call__(self, s):
52 return s == self.strtomatch
53
54class PrefixCond(Cond):
55 """Acts as a condition function for matching a string prefix"""
56 def __init__(self, prefix):
57 self.prefix = prefix
58 def __call__(self, s):
59 return s[:len(self.prefix)] == self.prefix
60
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +000061class PostfixCond(Cond):
62 """Acts as a condition function for matching a string postfix"""
63 def __init__(self, postfix):
64 self.postfix = postfix
65 def __call__(self, s):
66 return s[-len(self.postfix):] == self.postfix
67
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000068class LikeCond(Cond):
69 """
70 Acts as a function that will match using an SQL 'LIKE' style
71 string. Case insensitive and % signs are wild cards.
72 This isn't perfect but it should work for the simple common cases.
73 """
74 def __init__(self, likestr, re_flags=re.IGNORECASE):
75 # escape python re characters
76 chars_to_escape = '.*+()[]?'
77 for char in chars_to_escape :
Barry Warsawf71de3e2003-01-28 17:20:44 +000078 likestr = likestr.replace(char, '\\'+char)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000079 # convert %s to wildcards
Barry Warsawf71de3e2003-01-28 17:20:44 +000080 self.likestr = likestr.replace('%', '.*')
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000081 self.re = re.compile('^'+self.likestr+'$', re_flags)
82 def __call__(self, s):
83 return self.re.match(s)
84
85#
86# keys used to store database metadata
87#
88_table_names_key = '__TABLE_NAMES__' # list of the tables in this db
89_columns = '._COLUMNS__' # table_name+this key contains a list of columns
Barry Warsawf71de3e2003-01-28 17:20:44 +000090
91def _columns_key(table):
92 return table + _columns
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000093
94#
95# these keys are found within table sub databases
96#
97_data = '._DATA_.' # this+column+this+rowid key contains table data
98_rowid = '._ROWID_.' # this+rowid+this key contains a unique entry for each
99 # row in the table. (no data is stored)
100_rowid_str_len = 8 # length in bytes of the unique rowid strings
Barry Warsawf71de3e2003-01-28 17:20:44 +0000101
102def _data_key(table, col, rowid):
103 return table + _data + col + _data + rowid
104
105def _search_col_data_key(table, col):
106 return table + _data + col + _data
107
108def _search_all_data_key(table):
109 return table + _data
110
111def _rowid_key(table, rowid):
112 return table + _rowid + rowid + _rowid
113
114def _search_rowid_key(table):
115 return table + _rowid
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000116
117def contains_metastrings(s) :
118 """Verify that the given string does not contain any
119 metadata strings that might interfere with dbtables database operation.
120 """
Barry Warsawf71de3e2003-01-28 17:20:44 +0000121 if (s.find(_table_names_key) >= 0 or
122 s.find(_columns) >= 0 or
123 s.find(_data) >= 0 or
124 s.find(_rowid) >= 0):
125 # Then
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000126 return 1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000127 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000128 return 0
129
130
131class bsdTableDB :
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000132 def __init__(self, filename, dbhome, create=0, truncate=0, mode=0600,
Barry Warsawf71de3e2003-01-28 17:20:44 +0000133 recover=0, dbflags=0):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000134 """bsdTableDB.open(filename, dbhome, create=0, truncate=0, mode=0600)
135 Open database name in the dbhome BerkeleyDB directory.
136 Use keyword arguments when calling this constructor.
137 """
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000138 self.db = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000139 myflags = DB_THREAD
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000140 if create:
141 myflags |= DB_CREATE
142 flagsforenv = (DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG |
143 DB_INIT_TXN | dbflags)
144 # DB_AUTO_COMMIT isn't a valid flag for env.open()
145 try:
146 dbflags |= DB_AUTO_COMMIT
147 except AttributeError:
148 pass
149 if recover:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000150 flagsforenv = flagsforenv | DB_RECOVER
151 self.env = DBEnv()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000152 # enable auto deadlock avoidance
153 self.env.set_lk_detect(DB_LOCK_DEFAULT)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000154 self.env.open(dbhome, myflags | flagsforenv)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000155 if truncate:
156 myflags |= DB_TRUNCATE
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000157 self.db = DB(self.env)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000158 # allow duplicate entries [warning: be careful w/ metadata]
159 self.db.set_flags(DB_DUP)
160 self.db.open(filename, DB_BTREE, dbflags | myflags, mode)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000161 self.dbfilename = filename
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000162 # Initialize the table names list if this is a new database
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000163 txn = self.env.txn_begin()
164 try:
165 if not self.db.has_key(_table_names_key, txn):
166 self.db.put(_table_names_key, pickle.dumps([], 1), txn=txn)
167 # Yes, bare except
168 except:
169 txn.abort()
170 raise
171 else:
172 txn.commit()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000173 # TODO verify more of the database's metadata?
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000174 self.__tablecolumns = {}
175
176 def __del__(self):
177 self.close()
178
179 def close(self):
180 if self.db is not None:
181 self.db.close()
182 self.db = None
183 if self.env is not None:
184 self.env.close()
185 self.env = None
186
187 def checkpoint(self, mins=0):
188 try:
189 self.env.txn_checkpoint(mins)
190 except DBIncompleteError:
191 pass
192
193 def sync(self):
194 try:
195 self.db.sync()
196 except DBIncompleteError:
197 pass
198
199 def _db_print(self) :
200 """Print the database to stdout for debugging"""
201 print "******** Printing raw database for debugging ********"
202 cur = self.db.cursor()
203 try:
204 key, data = cur.first()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000205 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000206 print `{key: data}`
207 next = cur.next()
208 if next:
209 key, data = next
210 else:
211 cur.close()
212 return
213 except DBNotFoundError:
214 cur.close()
215
216
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000217 def CreateTable(self, table, columns):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000218 """CreateTable(table, columns) - Create a new table in the database
219 raises TableDBError if it already exists or for other DB errors.
220 """
Barry Warsawf71de3e2003-01-28 17:20:44 +0000221 assert isinstance(columns, ListType)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000222 txn = None
223 try:
224 # checking sanity of the table and column names here on
225 # table creation will prevent problems elsewhere.
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000226 if contains_metastrings(table):
227 raise ValueError(
228 "bad table name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000229 for column in columns :
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000230 if contains_metastrings(column):
231 raise ValueError(
232 "bad column name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000233
234 columnlist_key = _columns_key(table)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000235 if self.db.has_key(columnlist_key):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000236 raise TableAlreadyExists, "table already exists"
237
238 txn = self.env.txn_begin()
239 # store the table's column info
240 self.db.put(columnlist_key, pickle.dumps(columns, 1), txn=txn)
241
242 # add the table name to the tablelist
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000243 tablelist = pickle.loads(self.db.get(_table_names_key, txn=txn,
244 flags=DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000245 tablelist.append(table)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000246 # delete 1st, in case we opened with DB_DUP
247 self.db.delete(_table_names_key, txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000248 self.db.put(_table_names_key, pickle.dumps(tablelist, 1), txn=txn)
249
250 txn.commit()
251 txn = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000252 except DBError, dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000253 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000254 txn.abort()
255 raise TableDBError, dberror[1]
256
257
258 def ListTableColumns(self, table):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000259 """Return a list of columns in the given table.
260 [] if the table doesn't exist.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000261 """
Barry Warsawf71de3e2003-01-28 17:20:44 +0000262 assert isinstance(table, StringType)
263 if contains_metastrings(table):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000264 raise ValueError, "bad table name: contains reserved metastrings"
265
266 columnlist_key = _columns_key(table)
267 if not self.db.has_key(columnlist_key):
268 return []
269 pickledcolumnlist = self.db.get(columnlist_key)
270 if pickledcolumnlist:
271 return pickle.loads(pickledcolumnlist)
272 else:
273 return []
274
275 def ListTables(self):
276 """Return a list of tables in this database."""
277 pickledtablelist = self.db.get(_table_names_key)
278 if pickledtablelist:
279 return pickle.loads(pickledtablelist)
280 else:
281 return []
282
283 def CreateOrExtendTable(self, table, columns):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000284 """CreateOrExtendTable(table, columns)
285
286 - Create a new table in the database.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000287 If a table of this name already exists, extend it to have any
288 additional columns present in the given list as well as
289 all of its current columns.
290 """
Barry Warsawf71de3e2003-01-28 17:20:44 +0000291 assert isinstance(columns, ListType)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000292 try:
293 self.CreateTable(table, columns)
294 except TableAlreadyExists:
295 # the table already existed, add any new columns
296 txn = None
297 try:
298 columnlist_key = _columns_key(table)
299 txn = self.env.txn_begin()
300
301 # load the current column list
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000302 oldcolumnlist = pickle.loads(
303 self.db.get(columnlist_key, txn=txn, flags=DB_RMW))
304 # create a hash table for fast lookups of column names in the
305 # loop below
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000306 oldcolumnhash = {}
307 for c in oldcolumnlist:
308 oldcolumnhash[c] = c
309
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000310 # create a new column list containing both the old and new
311 # column names
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000312 newcolumnlist = copy.copy(oldcolumnlist)
313 for c in columns:
314 if not oldcolumnhash.has_key(c):
315 newcolumnlist.append(c)
316
317 # store the table's new extended column list
318 if newcolumnlist != oldcolumnlist :
319 # delete the old one first since we opened with DB_DUP
320 self.db.delete(columnlist_key, txn)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000321 self.db.put(columnlist_key,
322 pickle.dumps(newcolumnlist, 1),
323 txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000324
325 txn.commit()
326 txn = None
327
328 self.__load_column_info(table)
329 except DBError, dberror:
330 if txn:
331 txn.abort()
332 raise TableDBError, dberror[1]
333
334
335 def __load_column_info(self, table) :
336 """initialize the self.__tablecolumns dict"""
337 # check the column names
338 try:
339 tcolpickles = self.db.get(_columns_key(table))
340 except DBNotFoundError:
341 raise TableDBError, "unknown table: " + `table`
342 if not tcolpickles:
343 raise TableDBError, "unknown table: " + `table`
344 self.__tablecolumns[table] = pickle.loads(tcolpickles)
345
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000346 def __new_rowid(self, table, txn) :
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000347 """Create a new unique row identifier"""
348 unique = 0
Barry Warsawf71de3e2003-01-28 17:20:44 +0000349 while not unique:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000350 # Generate a random 64-bit row ID string
351 # (note: this code has <64 bits of randomness
352 # but it's plenty for our database id needs!)
353 p = xdrlib.Packer()
354 p.pack_int(int(whrandom.random()*2147483647))
355 p.pack_int(int(whrandom.random()*2147483647))
356 newid = p.get_buffer()
357
358 # Guarantee uniqueness by adding this key to the database
359 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000360 self.db.put(_rowid_key(table, newid), None, txn=txn,
361 flags=DB_NOOVERWRITE)
362 except DBKeyExistError:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000363 pass
364 else:
365 unique = 1
366
367 return newid
368
369
370 def Insert(self, table, rowdict) :
371 """Insert(table, datadict) - Insert a new row into the table
372 using the keys+values from rowdict as the column values.
373 """
374 txn = None
375 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000376 if not self.db.has_key(_columns_key(table)):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000377 raise TableDBError, "unknown table"
378
379 # check the validity of each column name
Barry Warsawf71de3e2003-01-28 17:20:44 +0000380 if not self.__tablecolumns.has_key(table):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000381 self.__load_column_info(table)
382 for column in rowdict.keys() :
Barry Warsawf71de3e2003-01-28 17:20:44 +0000383 if not self.__tablecolumns[table].count(column):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000384 raise TableDBError, "unknown column: "+`column`
385
386 # get a unique row identifier for this row
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000387 txn = self.env.txn_begin()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000388 rowid = self.__new_rowid(table, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000389
390 # insert the row values into the table database
Barry Warsawf71de3e2003-01-28 17:20:44 +0000391 for column, dataitem in rowdict.items():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000392 # store the value
393 self.db.put(_data_key(table, column, rowid), dataitem, txn=txn)
394
395 txn.commit()
396 txn = None
397
398 except DBError, dberror:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000399 # WIBNI we could just abort the txn and re-raise the exception?
400 # But no, because TableDBError is not related to DBError via
401 # inheritance, so it would be backwards incompatible. Do the next
402 # best thing.
403 info = sys.exc_info()
404 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000405 txn.abort()
406 self.db.delete(_rowid_key(table, rowid))
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000407 raise TableDBError, dberror[1], info[2]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000408
409
Barry Warsawf71de3e2003-01-28 17:20:44 +0000410 def Modify(self, table, conditions={}, mappings={}):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000411 """Modify(table, conditions) - Modify in rows matching 'conditions'
412 using mapping functions in 'mappings'
413 * conditions is a dictionary keyed on column names
414 containing condition functions expecting the data string as an
415 argument and returning a boolean.
416 * mappings is a dictionary keyed on column names containint condition
417 functions expecting the data string as an argument and returning the
418 new string for that column.
419 """
420 try:
421 matching_rowids = self.__Select(table, [], conditions)
422
423 # modify only requested columns
424 columns = mappings.keys()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000425 for rowid in matching_rowids.keys():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000426 txn = None
427 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000428 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000429 txn = self.env.txn_begin()
430 # modify the requested column
431 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000432 dataitem = self.db.get(
433 _data_key(table, column, rowid),
434 txn)
435 self.db.delete(
436 _data_key(table, column, rowid),
437 txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000438 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000439 # XXXXXXX row key somehow didn't exist, assume no
440 # error
441 dataitem = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000442 dataitem = mappings[column](dataitem)
443 if dataitem <> None:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000444 self.db.put(
445 _data_key(table, column, rowid),
446 dataitem, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000447 txn.commit()
448 txn = None
449
450 except DBError, dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000451 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000452 txn.abort()
453 raise
454
455 except DBError, dberror:
456 raise TableDBError, dberror[1]
457
Barry Warsawf71de3e2003-01-28 17:20:44 +0000458 def Delete(self, table, conditions={}):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000459 """Delete(table, conditions) - Delete items matching the given
460 conditions from the table.
461 * conditions is a dictionary keyed on column names
462 containing condition functions expecting the data string as an
463 argument and returning a boolean.
464 """
465 try:
466 matching_rowids = self.__Select(table, [], conditions)
467
468 # delete row data from all columns
469 columns = self.__tablecolumns[table]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000470 for rowid in matching_rowids.keys():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000471 txn = None
472 try:
473 txn = self.env.txn_begin()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000474 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000475 # delete the data key
476 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000477 self.db.delete(_data_key(table, column, rowid),
478 txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000479 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000480 # XXXXXXX column may not exist, assume no error
481 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000482
483 try:
484 self.db.delete(_rowid_key(table, rowid), txn)
485 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000486 # XXXXXXX row key somehow didn't exist, assume no error
487 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000488 txn.commit()
489 txn = None
490 except DBError, dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000491 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000492 txn.abort()
493 raise
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000494 except DBError, dberror:
495 raise TableDBError, dberror[1]
496
497
Barry Warsawf71de3e2003-01-28 17:20:44 +0000498 def Select(self, table, columns, conditions={}):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000499 """Select(table, conditions) - retrieve specific row data
500 Returns a list of row column->value mapping dictionaries.
501 * columns is a list of which column data to return. If
502 columns is None, all columns will be returned.
503 * conditions is a dictionary keyed on column names
504 containing callable conditions expecting the data string as an
505 argument and returning a boolean.
506 """
507 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000508 if not self.__tablecolumns.has_key(table):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000509 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000510 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000511 columns = self.__tablecolumns[table]
512 matching_rowids = self.__Select(table, columns, conditions)
513 except DBError, dberror:
514 raise TableDBError, dberror[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000515 # return the matches as a list of dictionaries
516 return matching_rowids.values()
517
518
Barry Warsawf71de3e2003-01-28 17:20:44 +0000519 def __Select(self, table, columns, conditions):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000520 """__Select() - Used to implement Select and Delete (above)
521 Returns a dictionary keyed on rowids containing dicts
522 holding the row data for columns listed in the columns param
523 that match the given conditions.
524 * conditions is a dictionary keyed on column names
525 containing callable conditions expecting the data string as an
526 argument and returning a boolean.
527 """
528 # check the validity of each column name
Barry Warsawf71de3e2003-01-28 17:20:44 +0000529 if not self.__tablecolumns.has_key(table):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000530 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000531 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000532 columns = self.tablecolumns[table]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000533 for column in (columns + conditions.keys()):
534 if not self.__tablecolumns[table].count(column):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000535 raise TableDBError, "unknown column: "+`column`
536
537 # keyed on rows that match so far, containings dicts keyed on
538 # column names containing the data for that row and column.
539 matching_rowids = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000540 # keys are rowids that do not match
541 rejected_rowids = {}
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000542
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000543 # attempt to sort the conditions in such a way as to minimize full
544 # column lookups
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000545 def cmp_conditions(atuple, btuple):
546 a = atuple[1]
547 b = btuple[1]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000548 if type(a) is type(b):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000549 if isinstance(a, PrefixCond) and isinstance(b, PrefixCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000550 # longest prefix first
551 return cmp(len(b.prefix), len(a.prefix))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000552 if isinstance(a, LikeCond) and isinstance(b, LikeCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000553 # longest likestr first
554 return cmp(len(b.likestr), len(a.likestr))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000555 return 0
556 if isinstance(a, ExactCond):
557 return -1
558 if isinstance(b, ExactCond):
559 return 1
560 if isinstance(a, PrefixCond):
561 return -1
562 if isinstance(b, PrefixCond):
563 return 1
564 # leave all unknown condition callables alone as equals
565 return 0
566
567 conditionlist = conditions.items()
568 conditionlist.sort(cmp_conditions)
569
570 # Apply conditions to column data to find what we want
571 cur = self.db.cursor()
572 column_num = -1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000573 for column, condition in conditionlist:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000574 column_num = column_num + 1
575 searchkey = _search_col_data_key(table, column)
576 # speedup: don't linear search columns within loop
Barry Warsawf71de3e2003-01-28 17:20:44 +0000577 if column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000578 savethiscolumndata = 1 # save the data for return
Barry Warsawf71de3e2003-01-28 17:20:44 +0000579 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000580 savethiscolumndata = 0 # data only used for selection
581
582 try:
583 key, data = cur.set_range(searchkey)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000584 while key[:len(searchkey)] == searchkey:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000585 # extract the rowid from the key
586 rowid = key[-_rowid_str_len:]
587
Barry Warsawf71de3e2003-01-28 17:20:44 +0000588 if not rejected_rowids.has_key(rowid):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000589 # if no condition was specified or the condition
590 # succeeds, add row to our match list.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000591 if not condition or condition(data):
592 if not matching_rowids.has_key(rowid):
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000593 matching_rowids[rowid] = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000594 if savethiscolumndata:
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000595 matching_rowids[rowid][column] = data
Barry Warsawf71de3e2003-01-28 17:20:44 +0000596 else:
597 if matching_rowids.has_key(rowid):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000598 del matching_rowids[rowid]
599 rejected_rowids[rowid] = rowid
600
601 key, data = cur.next()
602
603 except DBError, dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000604 if dberror[0] != DB_NOTFOUND:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000605 raise
606 continue
607
608 cur.close()
609
610 # we're done selecting rows, garbage collect the reject list
611 del rejected_rowids
612
613 # extract any remaining desired column data from the
614 # database for the matching rows.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000615 if len(columns) > 0:
616 for rowid, rowdata in matching_rowids.items():
617 for column in columns:
618 if rowdata.has_key(column):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000619 continue
620 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000621 rowdata[column] = self.db.get(
622 _data_key(table, column, rowid))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000623 except DBError, dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000624 if dberror[0] != DB_NOTFOUND:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000625 raise
626 rowdata[column] = None
627
628 # return the matches
629 return matching_rowids
630
631
Barry Warsawf71de3e2003-01-28 17:20:44 +0000632 def Drop(self, table):
633 """Remove an entire table from the database"""
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000634 txn = None
635 try:
636 txn = self.env.txn_begin()
637
638 # delete the column list
639 self.db.delete(_columns_key(table), txn)
640
641 cur = self.db.cursor(txn)
642
643 # delete all keys containing this tables column and row info
644 table_key = _search_all_data_key(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000645 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000646 try:
647 key, data = cur.set_range(table_key)
648 except DBNotFoundError:
649 break
650 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000651 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000652 break
653 cur.delete()
654
655 # delete all rowids used by this table
656 table_key = _search_rowid_key(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000657 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000658 try:
659 key, data = cur.set_range(table_key)
660 except DBNotFoundError:
661 break
662 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000663 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000664 break
665 cur.delete()
666
667 cur.close()
668
669 # delete the tablename from the table name list
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000670 tablelist = pickle.loads(
671 self.db.get(_table_names_key, txn=txn, flags=DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000672 try:
673 tablelist.remove(table)
674 except ValueError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000675 # hmm, it wasn't there, oh well, that's what we want.
676 pass
677 # delete 1st, incase we opened with DB_DUP
678 self.db.delete(_table_names_key, txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000679 self.db.put(_table_names_key, pickle.dumps(tablelist, 1), txn=txn)
680
681 txn.commit()
682 txn = None
683
Barry Warsawf71de3e2003-01-28 17:20:44 +0000684 if self.__tablecolumns.has_key(table):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000685 del self.__tablecolumns[table]
686
687 except DBError, dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000688 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000689 txn.abort()
690 raise TableDBError, dberror[1]