blob: b5fa9eb4261cc8d0373ccfa9eb70792f0840c82c [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
Tim Peters95334a52004-08-08 00:54:21 +000024import random
Guido van Rossum99603b02007-07-20 00:22:32 +000025import pickle
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000026
Martin v. Löwiscccc58d2007-08-10 08:36:56 +000027from bsddb.db import *
28
29# All table names, row names etc. must be ASCII strings
30def _E(s):
31 return s.encode("ascii")
32
33# Yet, rowid are arbitrary bytes; if there is a need to hash
34# them, convert them to Latin-1 first
35def _D(s):
36 return s.decode("latin-1")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000037
Thomas Wouters0e3f5912006-08-11 14:57:12 +000038# XXX(nnorwitz): is this correct? DBIncompleteError is conditional in _bsddb.c
39try:
40 DBIncompleteError
41except NameError:
42 class DBIncompleteError(Exception):
43 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000044
Guido van Rossumcd16bf62007-06-13 18:07:49 +000045class TableDBError(Exception):
Barry Warsawf71de3e2003-01-28 17:20:44 +000046 pass
47class TableAlreadyExists(TableDBError):
48 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000049
50
51class Cond:
52 """This condition matches everything"""
53 def __call__(self, s):
54 return 1
55
56class ExactCond(Cond):
57 """Acts as an exact match condition function"""
58 def __init__(self, strtomatch):
59 self.strtomatch = strtomatch
60 def __call__(self, s):
61 return s == self.strtomatch
62
63class PrefixCond(Cond):
64 """Acts as a condition function for matching a string prefix"""
65 def __init__(self, prefix):
66 self.prefix = prefix
67 def __call__(self, s):
68 return s[:len(self.prefix)] == self.prefix
69
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +000070class PostfixCond(Cond):
71 """Acts as a condition function for matching a string postfix"""
72 def __init__(self, postfix):
73 self.postfix = postfix
74 def __call__(self, s):
75 return s[-len(self.postfix):] == self.postfix
76
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000077class LikeCond(Cond):
78 """
79 Acts as a function that will match using an SQL 'LIKE' style
80 string. Case insensitive and % signs are wild cards.
81 This isn't perfect but it should work for the simple common cases.
82 """
83 def __init__(self, likestr, re_flags=re.IGNORECASE):
84 # escape python re characters
85 chars_to_escape = '.*+()[]?'
86 for char in chars_to_escape :
Barry Warsawf71de3e2003-01-28 17:20:44 +000087 likestr = likestr.replace(char, '\\'+char)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000088 # convert %s to wildcards
Barry Warsawf71de3e2003-01-28 17:20:44 +000089 self.likestr = likestr.replace('%', '.*')
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000090 self.re = re.compile('^'+self.likestr+'$', re_flags)
91 def __call__(self, s):
92 return self.re.match(s)
93
94#
95# keys used to store database metadata
96#
Martin v. Löwiscccc58d2007-08-10 08:36:56 +000097_table_names_key = '__TABLE_NAMES__' # list of the tables in this db
98_columns = '._COLUMNS__' # table_name+this key contains a list of columns
Barry Warsawf71de3e2003-01-28 17:20:44 +000099
100def _columns_key(table):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000101 return _E(table + _columns)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000102
103#
104# these keys are found within table sub databases
105#
106_data = '._DATA_.' # this+column+this+rowid key contains table data
107_rowid = '._ROWID_.' # this+rowid+this key contains a unique entry for each
108 # row in the table. (no data is stored)
109_rowid_str_len = 8 # length in bytes of the unique rowid strings
Barry Warsawf71de3e2003-01-28 17:20:44 +0000110
111def _data_key(table, col, rowid):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000112 return _E(table + _data + col + _data) + rowid
Barry Warsawf71de3e2003-01-28 17:20:44 +0000113
114def _search_col_data_key(table, col):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000115 return _E(table + _data + col + _data)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000116
117def _search_all_data_key(table):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000118 return _E(table + _data)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000119
120def _rowid_key(table, rowid):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000121 return _E(table + _rowid) + rowid + _E(_rowid)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000122
123def _search_rowid_key(table):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000124 return _E(table + _rowid)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000125
126def contains_metastrings(s) :
127 """Verify that the given string does not contain any
128 metadata strings that might interfere with dbtables database operation.
129 """
Barry Warsawf71de3e2003-01-28 17:20:44 +0000130 if (s.find(_table_names_key) >= 0 or
131 s.find(_columns) >= 0 or
132 s.find(_data) >= 0 or
133 s.find(_rowid) >= 0):
134 # Then
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000135 return 1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000136 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000137 return 0
138
139
140class bsdTableDB :
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000141 def __init__(self, filename, dbhome, create=0, truncate=0, mode=0o600,
Barry Warsawf71de3e2003-01-28 17:20:44 +0000142 recover=0, dbflags=0):
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000143 """bsdTableDB(filename, dbhome, create=0, truncate=0, mode=0o600)
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000144
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000145 Open database name in the dbhome BerkeleyDB directory.
146 Use keyword arguments when calling this constructor.
147 """
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000148 self.db = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000149 myflags = DB_THREAD
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000150 if create:
151 myflags |= DB_CREATE
152 flagsforenv = (DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG |
153 DB_INIT_TXN | dbflags)
154 # DB_AUTO_COMMIT isn't a valid flag for env.open()
155 try:
156 dbflags |= DB_AUTO_COMMIT
157 except AttributeError:
158 pass
159 if recover:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000160 flagsforenv = flagsforenv | DB_RECOVER
161 self.env = DBEnv()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000162 # enable auto deadlock avoidance
163 self.env.set_lk_detect(DB_LOCK_DEFAULT)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000164 self.env.open(dbhome, myflags | flagsforenv)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000165 if truncate:
166 myflags |= DB_TRUNCATE
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000167 self.db = DB(self.env)
Gregory P. Smith455d46f2003-07-09 04:45:59 +0000168 # this code relies on DBCursor.set* methods to raise exceptions
169 # rather than returning None
170 self.db.set_get_returns_none(1)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000171 # allow duplicate entries [warning: be careful w/ metadata]
172 self.db.set_flags(DB_DUP)
173 self.db.open(filename, DB_BTREE, dbflags | myflags, mode)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000174 self.dbfilename = filename
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000175 # Initialize the table names list if this is a new database
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000176 txn = self.env.txn_begin()
177 try:
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000178 if not self.db.has_key(_E(_table_names_key), txn):
179 self.db.put(_E(_table_names_key), pickle.dumps([], 1), txn=txn)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000180 # Yes, bare except
181 except:
182 txn.abort()
183 raise
184 else:
185 txn.commit()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000186 # TODO verify more of the database's metadata?
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000187 self.__tablecolumns = {}
188
189 def __del__(self):
190 self.close()
191
192 def close(self):
193 if self.db is not None:
194 self.db.close()
195 self.db = None
196 if self.env is not None:
197 self.env.close()
198 self.env = None
199
200 def checkpoint(self, mins=0):
201 try:
202 self.env.txn_checkpoint(mins)
203 except DBIncompleteError:
204 pass
205
206 def sync(self):
207 try:
208 self.db.sync()
209 except DBIncompleteError:
210 pass
211
212 def _db_print(self) :
213 """Print the database to stdout for debugging"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000214 print("******** Printing raw database for debugging ********")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000215 cur = self.db.cursor()
216 try:
217 key, data = cur.first()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000218 while 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000219 print(repr({key: data}))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000220 next = cur.next()
221 if next:
222 key, data = next
223 else:
224 cur.close()
225 return
226 except DBNotFoundError:
227 cur.close()
228
229
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000230 def CreateTable(self, table, columns):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000231 """CreateTable(table, columns) - Create a new table in the database.
232
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000233 raises TableDBError if it already exists or for other DB errors.
234 """
Guido van Rossum13257902007-06-07 23:15:56 +0000235 assert isinstance(columns, list)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000236 txn = None
237 try:
238 # checking sanity of the table and column names here on
239 # table creation will prevent problems elsewhere.
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000240 if contains_metastrings(table):
241 raise ValueError(
242 "bad table name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000243 for column in columns :
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000244 if contains_metastrings(column):
245 raise ValueError(
246 "bad column name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000247
248 columnlist_key = _columns_key(table)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000249 if self.db.has_key(columnlist_key):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000250 raise TableAlreadyExists, "table already exists"
251
252 txn = self.env.txn_begin()
253 # store the table's column info
254 self.db.put(columnlist_key, pickle.dumps(columns, 1), txn=txn)
255
256 # add the table name to the tablelist
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000257 tablelist = pickle.loads(self.db.get(_E(_table_names_key), txn=txn,
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000258 flags=DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000259 tablelist.append(table)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000260 # delete 1st, in case we opened with DB_DUP
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000261 self.db.delete(_E(_table_names_key), txn)
262 self.db.put(_E(_table_names_key), pickle.dumps(tablelist, 1), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000263
264 txn.commit()
265 txn = None
Guido van Rossumb940e112007-01-10 16:19:56 +0000266 except DBError as dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000267 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000268 txn.abort()
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000269 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000270
271
272 def ListTableColumns(self, table):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000273 """Return a list of columns in the given table.
274 [] if the table doesn't exist.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000275 """
Guido van Rossum13257902007-06-07 23:15:56 +0000276 assert isinstance(table, str)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000277 if contains_metastrings(table):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000278 raise ValueError, "bad table name: contains reserved metastrings"
279
280 columnlist_key = _columns_key(table)
281 if not self.db.has_key(columnlist_key):
282 return []
283 pickledcolumnlist = self.db.get(columnlist_key)
284 if pickledcolumnlist:
285 return pickle.loads(pickledcolumnlist)
286 else:
287 return []
288
289 def ListTables(self):
290 """Return a list of tables in this database."""
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000291 pickledtablelist = self.db.get(_E(_table_names_key))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000292 if pickledtablelist:
293 return pickle.loads(pickledtablelist)
294 else:
295 return []
296
297 def CreateOrExtendTable(self, table, columns):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000298 """CreateOrExtendTable(table, columns)
299
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000300 Create a new table in the database.
301
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000302 If a table of this name already exists, extend it to have any
303 additional columns present in the given list as well as
304 all of its current columns.
305 """
Guido van Rossum13257902007-06-07 23:15:56 +0000306 assert isinstance(columns, list)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000307 try:
308 self.CreateTable(table, columns)
309 except TableAlreadyExists:
310 # the table already existed, add any new columns
311 txn = None
312 try:
313 columnlist_key = _columns_key(table)
314 txn = self.env.txn_begin()
315
316 # load the current column list
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000317 oldcolumnlist = pickle.loads(
318 self.db.get(columnlist_key, txn=txn, flags=DB_RMW))
319 # create a hash table for fast lookups of column names in the
320 # loop below
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000321 oldcolumnhash = {}
322 for c in oldcolumnlist:
323 oldcolumnhash[c] = c
324
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000325 # create a new column list containing both the old and new
326 # column names
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000327 newcolumnlist = copy.copy(oldcolumnlist)
328 for c in columns:
Guido van Rossum20435132006-08-21 00:21:47 +0000329 if c not in oldcolumnhash:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000330 newcolumnlist.append(c)
331
332 # store the table's new extended column list
333 if newcolumnlist != oldcolumnlist :
334 # delete the old one first since we opened with DB_DUP
335 self.db.delete(columnlist_key, txn)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000336 self.db.put(columnlist_key,
337 pickle.dumps(newcolumnlist, 1),
338 txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000339
340 txn.commit()
341 txn = None
342
343 self.__load_column_info(table)
Guido van Rossumb940e112007-01-10 16:19:56 +0000344 except DBError as dberror:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000345 if txn:
346 txn.abort()
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000347 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000348
349
350 def __load_column_info(self, table) :
351 """initialize the self.__tablecolumns dict"""
352 # check the column names
353 try:
354 tcolpickles = self.db.get(_columns_key(table))
355 except DBNotFoundError:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000356 raise TableDBError, "unknown table: %r" % (table,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000357 if not tcolpickles:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000358 raise TableDBError, "unknown table: %r" % (table,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000359 self.__tablecolumns[table] = pickle.loads(tcolpickles)
360
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000361 def __new_rowid(self, table, txn) :
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000362 """Create a new unique row identifier"""
363 unique = 0
Barry Warsawf71de3e2003-01-28 17:20:44 +0000364 while not unique:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000365 # Generate a random 64-bit row ID string
366 # (note: this code has <64 bits of randomness
367 # but it's plenty for our database id needs!)
368 p = xdrlib.Packer()
Tim Peters95334a52004-08-08 00:54:21 +0000369 p.pack_int(int(random.random()*2147483647))
370 p.pack_int(int(random.random()*2147483647))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000371 newid = p.get_buffer()
372
373 # Guarantee uniqueness by adding this key to the database
374 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000375 self.db.put(_rowid_key(table, newid), None, txn=txn,
376 flags=DB_NOOVERWRITE)
377 except DBKeyExistError:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000378 pass
379 else:
380 unique = 1
381
382 return newid
383
384
385 def Insert(self, table, rowdict) :
386 """Insert(table, datadict) - Insert a new row into the table
387 using the keys+values from rowdict as the column values.
388 """
389 txn = None
390 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000391 if not self.db.has_key(_columns_key(table)):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000392 raise TableDBError, "unknown table"
393
394 # check the validity of each column name
Guido van Rossum20435132006-08-21 00:21:47 +0000395 if table not in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000396 self.__load_column_info(table)
397 for column in rowdict.keys() :
Barry Warsawf71de3e2003-01-28 17:20:44 +0000398 if not self.__tablecolumns[table].count(column):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000399 raise TableDBError, "unknown column: %r" % (column,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000400
401 # get a unique row identifier for this row
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000402 txn = self.env.txn_begin()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000403 rowid = self.__new_rowid(table, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000404
405 # insert the row values into the table database
Barry Warsawf71de3e2003-01-28 17:20:44 +0000406 for column, dataitem in rowdict.items():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000407 # store the value
408 self.db.put(_data_key(table, column, rowid), dataitem, txn=txn)
409
410 txn.commit()
411 txn = None
412
Guido van Rossumb940e112007-01-10 16:19:56 +0000413 except DBError as dberror:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000414 # WIBNI we could just abort the txn and re-raise the exception?
415 # But no, because TableDBError is not related to DBError via
416 # inheritance, so it would be backwards incompatible. Do the next
417 # best thing.
418 info = sys.exc_info()
419 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000420 txn.abort()
421 self.db.delete(_rowid_key(table, rowid))
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000422 raise TableDBError, dberror.args[1], info[2]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000423
424
Barry Warsawf71de3e2003-01-28 17:20:44 +0000425 def Modify(self, table, conditions={}, mappings={}):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000426 """Modify(table, conditions={}, mappings={}) - Modify items in rows matching 'conditions' using mapping functions in 'mappings'
427
428 * table - the table name
429 * conditions - a dictionary keyed on column names containing
430 a condition callable expecting the data string as an
431 argument and returning a boolean.
432 * mappings - a dictionary keyed on column names containing a
433 condition callable expecting the data string as an argument and
434 returning the new string for that column.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000435 """
436 try:
437 matching_rowids = self.__Select(table, [], conditions)
438
439 # modify only requested columns
440 columns = mappings.keys()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000441 for rowid in matching_rowids.keys():
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000442 rowid = rowid.encode("latin-1")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000443 txn = None
444 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000445 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000446 txn = self.env.txn_begin()
447 # modify the requested column
448 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000449 dataitem = self.db.get(
450 _data_key(table, column, rowid),
451 txn)
452 self.db.delete(
453 _data_key(table, column, rowid),
454 txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000455 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000456 # XXXXXXX row key somehow didn't exist, assume no
457 # error
458 dataitem = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000459 dataitem = mappings[column](dataitem)
Guido van Rossumb053cd82006-08-24 03:53:23 +0000460 if dataitem != None:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000461 self.db.put(
462 _data_key(table, column, rowid),
463 dataitem, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000464 txn.commit()
465 txn = None
466
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000467 # catch all exceptions here since we call unknown callables
468 except:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000469 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000470 txn.abort()
471 raise
472
Guido van Rossumb940e112007-01-10 16:19:56 +0000473 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000474 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000475
Barry Warsawf71de3e2003-01-28 17:20:44 +0000476 def Delete(self, table, conditions={}):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000477 """Delete(table, conditions) - Delete items matching the given
478 conditions from the table.
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000479
480 * conditions - a dictionary keyed on column names containing
481 condition functions expecting the data string as an
482 argument and returning a boolean.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000483 """
484 try:
485 matching_rowids = self.__Select(table, [], conditions)
486
487 # delete row data from all columns
488 columns = self.__tablecolumns[table]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000489 for rowid in matching_rowids.keys():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000490 txn = None
491 try:
492 txn = self.env.txn_begin()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000493 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000494 # delete the data key
495 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000496 self.db.delete(_data_key(table, column, rowid),
497 txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000498 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000499 # XXXXXXX column may not exist, assume no error
500 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000501
502 try:
503 self.db.delete(_rowid_key(table, rowid), txn)
504 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000505 # XXXXXXX row key somehow didn't exist, assume no error
506 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000507 txn.commit()
508 txn = None
Guido van Rossumb940e112007-01-10 16:19:56 +0000509 except DBError as dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000510 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000511 txn.abort()
512 raise
Guido van Rossumb940e112007-01-10 16:19:56 +0000513 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000514 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000515
516
Barry Warsawf71de3e2003-01-28 17:20:44 +0000517 def Select(self, table, columns, conditions={}):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000518 """Select(table, columns, conditions) - retrieve specific row data
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000519 Returns a list of row column->value mapping dictionaries.
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000520
521 * columns - a list of which column data to return. If
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000522 columns is None, all columns will be returned.
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000523 * conditions - a dictionary keyed on column names
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000524 containing callable conditions expecting the data string as an
525 argument and returning a boolean.
526 """
527 try:
Guido van Rossum20435132006-08-21 00:21:47 +0000528 if table not in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000529 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000530 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000531 columns = self.__tablecolumns[table]
532 matching_rowids = self.__Select(table, columns, conditions)
Guido van Rossumb940e112007-01-10 16:19:56 +0000533 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000534 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000535 # return the matches as a list of dictionaries
536 return matching_rowids.values()
537
538
Barry Warsawf71de3e2003-01-28 17:20:44 +0000539 def __Select(self, table, columns, conditions):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000540 """__Select() - Used to implement Select and Delete (above)
541 Returns a dictionary keyed on rowids containing dicts
542 holding the row data for columns listed in the columns param
543 that match the given conditions.
544 * conditions is a dictionary keyed on column names
545 containing callable conditions expecting the data string as an
546 argument and returning a boolean.
547 """
548 # check the validity of each column name
Guido van Rossum20435132006-08-21 00:21:47 +0000549 if table not in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000550 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000551 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000552 columns = self.tablecolumns[table]
Brett Cannon0072e432007-02-22 06:40:59 +0000553 for column in (columns + list(conditions.keys())):
Barry Warsawf71de3e2003-01-28 17:20:44 +0000554 if not self.__tablecolumns[table].count(column):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000555 raise TableDBError, "unknown column: %r" % (column,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000556
557 # keyed on rows that match so far, containings dicts keyed on
558 # column names containing the data for that row and column.
559 matching_rowids = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000560 # keys are rowids that do not match
561 rejected_rowids = {}
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000562
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000563 # attempt to sort the conditions in such a way as to minimize full
564 # column lookups
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000565 def cmp_conditions(atuple, btuple):
566 a = atuple[1]
567 b = btuple[1]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000568 if type(a) is type(b):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000569 if isinstance(a, PrefixCond) and isinstance(b, PrefixCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000570 # longest prefix first
571 return cmp(len(b.prefix), len(a.prefix))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000572 if isinstance(a, LikeCond) and isinstance(b, LikeCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000573 # longest likestr first
574 return cmp(len(b.likestr), len(a.likestr))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000575 return 0
576 if isinstance(a, ExactCond):
577 return -1
578 if isinstance(b, ExactCond):
579 return 1
580 if isinstance(a, PrefixCond):
581 return -1
582 if isinstance(b, PrefixCond):
583 return 1
584 # leave all unknown condition callables alone as equals
585 return 0
586
Brett Cannon0072e432007-02-22 06:40:59 +0000587 conditionlist = list(conditions.items())
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000588 conditionlist.sort(cmp_conditions)
589
590 # Apply conditions to column data to find what we want
591 cur = self.db.cursor()
592 column_num = -1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000593 for column, condition in conditionlist:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000594 column_num = column_num + 1
595 searchkey = _search_col_data_key(table, column)
596 # speedup: don't linear search columns within loop
Barry Warsawf71de3e2003-01-28 17:20:44 +0000597 if column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000598 savethiscolumndata = 1 # save the data for return
Barry Warsawf71de3e2003-01-28 17:20:44 +0000599 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000600 savethiscolumndata = 0 # data only used for selection
601
602 try:
603 key, data = cur.set_range(searchkey)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000604 while key[:len(searchkey)] == searchkey:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000605 # extract the rowid from the key
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000606 rowid = _D(key[-_rowid_str_len:])
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000607
Guido van Rossum20435132006-08-21 00:21:47 +0000608 if rowid not in rejected_rowids:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000609 # if no condition was specified or the condition
610 # succeeds, add row to our match list.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000611 if not condition or condition(data):
Guido van Rossum20435132006-08-21 00:21:47 +0000612 if rowid not in matching_rowids:
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000613 matching_rowids[rowid] = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000614 if savethiscolumndata:
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000615 matching_rowids[rowid][column] = data
Barry Warsawf71de3e2003-01-28 17:20:44 +0000616 else:
Guido van Rossum20435132006-08-21 00:21:47 +0000617 if rowid in matching_rowids:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000618 del matching_rowids[rowid]
619 rejected_rowids[rowid] = rowid
620
621 key, data = cur.next()
622
Guido van Rossumb940e112007-01-10 16:19:56 +0000623 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000624 if dberror.args[0] != DB_NOTFOUND:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000625 raise
626 continue
627
628 cur.close()
629
630 # we're done selecting rows, garbage collect the reject list
631 del rejected_rowids
632
633 # extract any remaining desired column data from the
634 # database for the matching rows.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000635 if len(columns) > 0:
636 for rowid, rowdata in matching_rowids.items():
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000637 rowid = rowid.encode("latin-1")
Barry Warsawf71de3e2003-01-28 17:20:44 +0000638 for column in columns:
Guido van Rossum20435132006-08-21 00:21:47 +0000639 if column in rowdata:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000640 continue
641 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000642 rowdata[column] = self.db.get(
643 _data_key(table, column, rowid))
Guido van Rossumb940e112007-01-10 16:19:56 +0000644 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000645 if dberror.args[0] != DB_NOTFOUND:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000646 raise
647 rowdata[column] = None
648
649 # return the matches
650 return matching_rowids
651
652
Barry Warsawf71de3e2003-01-28 17:20:44 +0000653 def Drop(self, table):
654 """Remove an entire table from the database"""
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000655 txn = None
656 try:
657 txn = self.env.txn_begin()
658
659 # delete the column list
660 self.db.delete(_columns_key(table), txn)
661
662 cur = self.db.cursor(txn)
663
664 # delete all keys containing this tables column and row info
665 table_key = _search_all_data_key(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000666 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000667 try:
668 key, data = cur.set_range(table_key)
669 except DBNotFoundError:
670 break
671 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000672 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000673 break
674 cur.delete()
675
676 # delete all rowids used by this table
677 table_key = _search_rowid_key(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000678 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000679 try:
680 key, data = cur.set_range(table_key)
681 except DBNotFoundError:
682 break
683 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000684 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000685 break
686 cur.delete()
687
688 cur.close()
689
690 # delete the tablename from the table name list
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000691 tablelist = pickle.loads(
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000692 self.db.get(_E(_table_names_key), txn=txn, flags=DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000693 try:
694 tablelist.remove(table)
695 except ValueError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000696 # hmm, it wasn't there, oh well, that's what we want.
697 pass
698 # delete 1st, incase we opened with DB_DUP
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000699 self.db.delete(_E(_table_names_key), txn)
700 self.db.put(_E(_table_names_key), pickle.dumps(tablelist, 1), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000701
702 txn.commit()
703 txn = None
704
Guido van Rossum20435132006-08-21 00:21:47 +0000705 if table in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000706 del self.__tablecolumns[table]
707
Guido van Rossumb940e112007-01-10 16:19:56 +0000708 except DBError as dberror:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000709 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000710 txn.abort()
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000711 raise TableDBError, dberror.args[1]