blob: 563390ba6fcce8489be10e7c5923adb8434e51d5 [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. Smith3fd22da2007-08-28 08:05:56 +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
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
Gregory P. Smith66077d82007-10-18 16:55:12 +000023import struct
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
Martin v. Löwis32ca4422007-08-11 06:13:20 +000030# However, rowids, when represented as strings, are latin-1 encoded
Martin v. Löwiscccc58d2007-08-10 08:36:56 +000031def _E(s):
32 return s.encode("ascii")
33
Thomas Wouters0e3f5912006-08-11 14:57:12 +000034# XXX(nnorwitz): is this correct? DBIncompleteError is conditional in _bsddb.c
35try:
36 DBIncompleteError
37except NameError:
38 class DBIncompleteError(Exception):
39 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000040
Guido van Rossumcd16bf62007-06-13 18:07:49 +000041class TableDBError(Exception):
Barry Warsawf71de3e2003-01-28 17:20:44 +000042 pass
43class TableAlreadyExists(TableDBError):
44 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000045
46
47class Cond:
48 """This condition matches everything"""
49 def __call__(self, s):
50 return 1
51
52class ExactCond(Cond):
53 """Acts as an exact match condition function"""
Martin v. Löwis32ca4422007-08-11 06:13:20 +000054 def __init__(self, strtomatch, encoding="utf-8"):
55 self.strtomatch = strtomatch.encode(encoding)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000056 def __call__(self, s):
57 return s == self.strtomatch
58
59class PrefixCond(Cond):
60 """Acts as a condition function for matching a string prefix"""
Martin v. Löwis32ca4422007-08-11 06:13:20 +000061 def __init__(self, prefix, encoding="utf-8"):
62 self.prefix = prefix.encode(encoding)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000063 def __call__(self, s):
64 return s[:len(self.prefix)] == self.prefix
65
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +000066class PostfixCond(Cond):
67 """Acts as a condition function for matching a string postfix"""
Martin v. Löwis32ca4422007-08-11 06:13:20 +000068 def __init__(self, postfix, encoding="utf-8"):
69 self.postfix = postfix.encode(encoding)
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +000070 def __call__(self, s):
71 return s[-len(self.postfix):] == self.postfix
72
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000073class LikeCond(Cond):
74 """
75 Acts as a function that will match using an SQL 'LIKE' style
76 string. Case insensitive and % signs are wild cards.
77 This isn't perfect but it should work for the simple common cases.
78 """
Martin v. Löwis32ca4422007-08-11 06:13:20 +000079 def __init__(self, likestr, re_flags=re.IGNORECASE, encoding="utf-8"):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000080 # escape python re characters
81 chars_to_escape = '.*+()[]?'
82 for char in chars_to_escape :
Barry Warsawf71de3e2003-01-28 17:20:44 +000083 likestr = likestr.replace(char, '\\'+char)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000084 # convert %s to wildcards
Barry Warsawf71de3e2003-01-28 17:20:44 +000085 self.likestr = likestr.replace('%', '.*')
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000086 self.re = re.compile('^'+self.likestr+'$', re_flags)
Martin v. Löwis32ca4422007-08-11 06:13:20 +000087 self.encoding = encoding
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000088 def __call__(self, s):
Martin v. Löwis32ca4422007-08-11 06:13:20 +000089 return self.re.match(s.decode(self.encoding))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000090
Raymond Hettingerd4cb56d2008-01-30 02:55:10 +000091def CmpToKey(mycmp):
92 'Convert a cmp= function into a key= function'
93 class K(object):
94 def __init__(self, obj, *args):
95 self.obj = obj
96 def __lt__(self, other):
97 return mycmp(self.obj, other.obj) == -1
98 return K
99
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000100#
101# keys used to store database metadata
102#
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000103_table_names_key = '__TABLE_NAMES__' # list of the tables in this db
104_columns = '._COLUMNS__' # table_name+this key contains a list of columns
Barry Warsawf71de3e2003-01-28 17:20:44 +0000105
106def _columns_key(table):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000107 return _E(table + _columns)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000108
109#
110# these keys are found within table sub databases
111#
112_data = '._DATA_.' # this+column+this+rowid key contains table data
113_rowid = '._ROWID_.' # this+rowid+this key contains a unique entry for each
114 # row in the table. (no data is stored)
115_rowid_str_len = 8 # length in bytes of the unique rowid strings
Barry Warsawf71de3e2003-01-28 17:20:44 +0000116
117def _data_key(table, col, rowid):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000118 return _E(table + _data + col + _data) + rowid
Barry Warsawf71de3e2003-01-28 17:20:44 +0000119
120def _search_col_data_key(table, col):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000121 return _E(table + _data + col + _data)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000122
123def _search_all_data_key(table):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000124 return _E(table + _data)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000125
126def _rowid_key(table, rowid):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000127 return _E(table + _rowid) + rowid + _E(_rowid)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000128
129def _search_rowid_key(table):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000130 return _E(table + _rowid)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000131
132def contains_metastrings(s) :
133 """Verify that the given string does not contain any
134 metadata strings that might interfere with dbtables database operation.
135 """
Barry Warsawf71de3e2003-01-28 17:20:44 +0000136 if (s.find(_table_names_key) >= 0 or
137 s.find(_columns) >= 0 or
138 s.find(_data) >= 0 or
139 s.find(_rowid) >= 0):
140 # Then
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000141 return 1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000142 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000143 return 0
144
145
146class bsdTableDB :
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000147 def __init__(self, filename, dbhome, create=0, truncate=0, mode=0o600,
Barry Warsawf71de3e2003-01-28 17:20:44 +0000148 recover=0, dbflags=0):
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000149 """bsdTableDB(filename, dbhome, create=0, truncate=0, mode=0o600)
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000150
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000151 Open database name in the dbhome BerkeleyDB directory.
152 Use keyword arguments when calling this constructor.
153 """
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000154 self.db = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000155 myflags = DB_THREAD
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000156 if create:
157 myflags |= DB_CREATE
158 flagsforenv = (DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG |
159 DB_INIT_TXN | dbflags)
160 # DB_AUTO_COMMIT isn't a valid flag for env.open()
161 try:
162 dbflags |= DB_AUTO_COMMIT
163 except AttributeError:
164 pass
165 if recover:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000166 flagsforenv = flagsforenv | DB_RECOVER
167 self.env = DBEnv()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000168 # enable auto deadlock avoidance
169 self.env.set_lk_detect(DB_LOCK_DEFAULT)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000170 self.env.open(dbhome, myflags | flagsforenv)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000171 if truncate:
172 myflags |= DB_TRUNCATE
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000173 self.db = DB(self.env)
Gregory P. Smith455d46f2003-07-09 04:45:59 +0000174 # this code relies on DBCursor.set* methods to raise exceptions
175 # rather than returning None
176 self.db.set_get_returns_none(1)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000177 # allow duplicate entries [warning: be careful w/ metadata]
178 self.db.set_flags(DB_DUP)
179 self.db.open(filename, DB_BTREE, dbflags | myflags, mode)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000180 self.dbfilename = filename
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000181 # Initialize the table names list if this is a new database
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000182 txn = self.env.txn_begin()
183 try:
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000184 if not self.db.has_key(_E(_table_names_key), txn):
185 self.db.put(_E(_table_names_key), pickle.dumps([], 1), txn=txn)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000186 # Yes, bare except
187 except:
188 txn.abort()
189 raise
190 else:
191 txn.commit()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000192 # TODO verify more of the database's metadata?
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000193 self.__tablecolumns = {}
194
195 def __del__(self):
196 self.close()
197
198 def close(self):
199 if self.db is not None:
200 self.db.close()
201 self.db = None
202 if self.env is not None:
203 self.env.close()
204 self.env = None
205
206 def checkpoint(self, mins=0):
207 try:
208 self.env.txn_checkpoint(mins)
209 except DBIncompleteError:
210 pass
211
212 def sync(self):
213 try:
214 self.db.sync()
215 except DBIncompleteError:
216 pass
217
218 def _db_print(self) :
219 """Print the database to stdout for debugging"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000220 print("******** Printing raw database for debugging ********")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000221 cur = self.db.cursor()
222 try:
223 key, data = cur.first()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000224 while 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000225 print(repr({key: data}))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000226 next = cur.next()
227 if next:
228 key, data = next
229 else:
230 cur.close()
231 return
232 except DBNotFoundError:
233 cur.close()
234
235
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000236 def CreateTable(self, table, columns):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000237 """CreateTable(table, columns) - Create a new table in the database.
238
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000239 raises TableDBError if it already exists or for other DB errors.
240 """
Guido van Rossum13257902007-06-07 23:15:56 +0000241 assert isinstance(columns, list)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000242 txn = None
243 try:
244 # checking sanity of the table and column names here on
245 # table creation will prevent problems elsewhere.
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000246 if contains_metastrings(table):
247 raise ValueError(
248 "bad table name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000249 for column in columns :
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000250 if contains_metastrings(column):
251 raise ValueError(
252 "bad column name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000253
254 columnlist_key = _columns_key(table)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000255 if self.db.has_key(columnlist_key):
Collin Wintera65e94c2007-08-22 21:45:20 +0000256 raise TableAlreadyExists("table already exists")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000257
258 txn = self.env.txn_begin()
259 # store the table's column info
260 self.db.put(columnlist_key, pickle.dumps(columns, 1), txn=txn)
261
262 # add the table name to the tablelist
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000263 tablelist = pickle.loads(self.db.get(_E(_table_names_key), txn=txn,
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000264 flags=DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000265 tablelist.append(table)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000266 # delete 1st, in case we opened with DB_DUP
Gregory P. Smith66077d82007-10-18 16:55:12 +0000267 self.db.delete(_E(_table_names_key), txn=txn)
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000268 self.db.put(_E(_table_names_key), pickle.dumps(tablelist, 1), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000269
270 txn.commit()
271 txn = None
Guido van Rossumb940e112007-01-10 16:19:56 +0000272 except DBError as dberror:
Collin Wintera65e94c2007-08-22 21:45:20 +0000273 raise TableDBError(dberror.args[1])
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000274 finally:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000275 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000276 txn.abort()
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000277 txn = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000278
279 def ListTableColumns(self, table):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000280 """Return a list of columns in the given table.
281 [] if the table doesn't exist.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000282 """
Guido van Rossum13257902007-06-07 23:15:56 +0000283 assert isinstance(table, str)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000284 if contains_metastrings(table):
Collin Wintera65e94c2007-08-22 21:45:20 +0000285 raise ValueError("bad table name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000286
287 columnlist_key = _columns_key(table)
288 if not self.db.has_key(columnlist_key):
289 return []
290 pickledcolumnlist = self.db.get(columnlist_key)
291 if pickledcolumnlist:
292 return pickle.loads(pickledcolumnlist)
293 else:
294 return []
295
296 def ListTables(self):
297 """Return a list of tables in this database."""
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000298 pickledtablelist = self.db.get(_E(_table_names_key))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000299 if pickledtablelist:
300 return pickle.loads(pickledtablelist)
301 else:
302 return []
303
304 def CreateOrExtendTable(self, table, columns):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000305 """CreateOrExtendTable(table, columns)
306
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000307 Create a new table in the database.
308
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000309 If a table of this name already exists, extend it to have any
310 additional columns present in the given list as well as
311 all of its current columns.
312 """
Guido van Rossum13257902007-06-07 23:15:56 +0000313 assert isinstance(columns, list)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000314 try:
315 self.CreateTable(table, columns)
316 except TableAlreadyExists:
317 # the table already existed, add any new columns
318 txn = None
319 try:
320 columnlist_key = _columns_key(table)
321 txn = self.env.txn_begin()
322
323 # load the current column list
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000324 oldcolumnlist = pickle.loads(
325 self.db.get(columnlist_key, txn=txn, flags=DB_RMW))
326 # create a hash table for fast lookups of column names in the
327 # loop below
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000328 oldcolumnhash = {}
329 for c in oldcolumnlist:
330 oldcolumnhash[c] = c
331
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000332 # create a new column list containing both the old and new
333 # column names
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000334 newcolumnlist = copy.copy(oldcolumnlist)
335 for c in columns:
Guido van Rossum20435132006-08-21 00:21:47 +0000336 if c not in oldcolumnhash:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000337 newcolumnlist.append(c)
338
339 # store the table's new extended column list
340 if newcolumnlist != oldcolumnlist :
341 # delete the old one first since we opened with DB_DUP
Gregory P. Smith66077d82007-10-18 16:55:12 +0000342 self.db.delete(columnlist_key, txn=txn)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000343 self.db.put(columnlist_key,
344 pickle.dumps(newcolumnlist, 1),
345 txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000346
347 txn.commit()
348 txn = None
349
350 self.__load_column_info(table)
Guido van Rossumb940e112007-01-10 16:19:56 +0000351 except DBError as dberror:
Collin Wintera65e94c2007-08-22 21:45:20 +0000352 raise TableDBError(dberror.args[1])
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000353 finally:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000354 if txn:
355 txn.abort()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000356
357
358 def __load_column_info(self, table) :
359 """initialize the self.__tablecolumns dict"""
360 # check the column names
361 try:
362 tcolpickles = self.db.get(_columns_key(table))
363 except DBNotFoundError:
Collin Wintera65e94c2007-08-22 21:45:20 +0000364 raise TableDBError("unknown table: %r" % (table,))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000365 if not tcolpickles:
Collin Wintera65e94c2007-08-22 21:45:20 +0000366 raise TableDBError("unknown table: %r" % (table,))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000367 self.__tablecolumns[table] = pickle.loads(tcolpickles)
368
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000369 def __new_rowid(self, table, txn) :
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000370 """Create a new unique row identifier"""
371 unique = 0
Barry Warsawf71de3e2003-01-28 17:20:44 +0000372 while not unique:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000373 # Generate a random 64-bit row ID string
Guido van Rossum2cc30da2007-11-02 23:46:40 +0000374 # (note: might have <64 bits of true randomness
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000375 # but it's plenty for our database id needs!)
Gregory P. Smith66077d82007-10-18 16:55:12 +0000376 blist = []
377 for x in range(_rowid_str_len):
Gregory P. Smith568065e2007-11-01 21:55:08 +0000378 blist.append(random.randint(0,255))
Gregory P. Smith66077d82007-10-18 16:55:12 +0000379 newid = bytes(blist)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000380
381 # Guarantee uniqueness by adding this key to the database
382 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000383 self.db.put(_rowid_key(table, newid), None, txn=txn,
384 flags=DB_NOOVERWRITE)
385 except DBKeyExistError:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000386 pass
387 else:
388 unique = 1
389
390 return newid
391
392
393 def Insert(self, table, rowdict) :
394 """Insert(table, datadict) - Insert a new row into the table
395 using the keys+values from rowdict as the column values.
396 """
397 txn = None
398 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000399 if not self.db.has_key(_columns_key(table)):
Collin Wintera65e94c2007-08-22 21:45:20 +0000400 raise TableDBError("unknown table")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000401
402 # check the validity of each column name
Guido van Rossum20435132006-08-21 00:21:47 +0000403 if table not in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000404 self.__load_column_info(table)
405 for column in rowdict.keys() :
Barry Warsawf71de3e2003-01-28 17:20:44 +0000406 if not self.__tablecolumns[table].count(column):
Collin Wintera65e94c2007-08-22 21:45:20 +0000407 raise TableDBError("unknown column: %r" % (column,))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000408
409 # get a unique row identifier for this row
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000410 txn = self.env.txn_begin()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000411 rowid = self.__new_rowid(table, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000412
413 # insert the row values into the table database
Barry Warsawf71de3e2003-01-28 17:20:44 +0000414 for column, dataitem in rowdict.items():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000415 # store the value
416 self.db.put(_data_key(table, column, rowid), dataitem, txn=txn)
417
418 txn.commit()
419 txn = None
420
Guido van Rossumb940e112007-01-10 16:19:56 +0000421 except DBError as dberror:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000422 # WIBNI we could just abort the txn and re-raise the exception?
423 # But no, because TableDBError is not related to DBError via
424 # inheritance, so it would be backwards incompatible. Do the next
425 # best thing.
426 info = sys.exc_info()
427 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000428 txn.abort()
429 self.db.delete(_rowid_key(table, rowid))
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000430 txn = None
Collin Wintera65e94c2007-08-22 21:45:20 +0000431 raise TableDBError(dberror.args[1]).with_traceback(info[2])
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000432 finally:
433 if txn:
434 txn.abort()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000435
436
Barry Warsawf71de3e2003-01-28 17:20:44 +0000437 def Modify(self, table, conditions={}, mappings={}):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000438 """Modify(table, conditions={}, mappings={}) - Modify items in rows matching 'conditions' using mapping functions in 'mappings'
439
440 * table - the table name
441 * conditions - a dictionary keyed on column names containing
442 a condition callable expecting the data string as an
443 argument and returning a boolean.
444 * mappings - a dictionary keyed on column names containing a
445 condition callable expecting the data string as an argument and
446 returning the new string for that column.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000447 """
448 try:
449 matching_rowids = self.__Select(table, [], conditions)
450
451 # modify only requested columns
452 columns = mappings.keys()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000453 for rowid in matching_rowids.keys():
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000454 rowid = rowid.encode("latin-1")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000455 txn = None
456 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000457 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000458 txn = self.env.txn_begin()
459 # modify the requested column
460 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000461 dataitem = self.db.get(
462 _data_key(table, column, rowid),
Gregory P. Smith66077d82007-10-18 16:55:12 +0000463 txn=txn)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000464 self.db.delete(
465 _data_key(table, column, rowid),
Gregory P. Smith66077d82007-10-18 16:55:12 +0000466 txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000467 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000468 # XXXXXXX row key somehow didn't exist, assume no
469 # error
470 dataitem = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000471 dataitem = mappings[column](dataitem)
Guido van Rossumb053cd82006-08-24 03:53:23 +0000472 if dataitem != None:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000473 self.db.put(
474 _data_key(table, column, rowid),
475 dataitem, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000476 txn.commit()
477 txn = None
478
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000479 # catch all exceptions here since we call unknown callables
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000480 finally:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000481 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000482 txn.abort()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000483
Guido van Rossumb940e112007-01-10 16:19:56 +0000484 except DBError as dberror:
Collin Wintera65e94c2007-08-22 21:45:20 +0000485 raise TableDBError(dberror.args[1])
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000486
Barry Warsawf71de3e2003-01-28 17:20:44 +0000487 def Delete(self, table, conditions={}):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000488 """Delete(table, conditions) - Delete items matching the given
489 conditions from the table.
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000490
491 * conditions - a dictionary keyed on column names containing
492 condition functions expecting the data string as an
493 argument and returning a boolean.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000494 """
495 try:
496 matching_rowids = self.__Select(table, [], conditions)
497
498 # delete row data from all columns
499 columns = self.__tablecolumns[table]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000500 for rowid in matching_rowids.keys():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000501 txn = None
502 try:
503 txn = self.env.txn_begin()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000504 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000505 # delete the data key
506 try:
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000507 self.db.delete(_data_key(table, column,
508 rowid.encode("latin-1")),
Gregory P. Smith66077d82007-10-18 16:55:12 +0000509 txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000510 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000511 # XXXXXXX column may not exist, assume no error
512 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000513
514 try:
Gregory P. Smith66077d82007-10-18 16:55:12 +0000515 self.db.delete(_rowid_key(table, rowid.encode("latin-1")), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000516 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000517 # XXXXXXX row key somehow didn't exist, assume no error
518 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000519 txn.commit()
520 txn = None
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000521 finally:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000522 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000523 txn.abort()
Guido van Rossumb940e112007-01-10 16:19:56 +0000524 except DBError as dberror:
Collin Wintera65e94c2007-08-22 21:45:20 +0000525 raise TableDBError(dberror.args[1])
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000526
527
Barry Warsawf71de3e2003-01-28 17:20:44 +0000528 def Select(self, table, columns, conditions={}):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000529 """Select(table, columns, conditions) - retrieve specific row data
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000530 Returns a list of row column->value mapping dictionaries.
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000531
532 * columns - a list of which column data to return. If
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000533 columns is None, all columns will be returned.
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000534 * conditions - a dictionary keyed on column names
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000535 containing callable conditions expecting the data string as an
536 argument and returning a boolean.
537 """
538 try:
Guido van Rossum20435132006-08-21 00:21:47 +0000539 if table not in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000540 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000541 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000542 columns = self.__tablecolumns[table]
543 matching_rowids = self.__Select(table, columns, conditions)
Guido van Rossumb940e112007-01-10 16:19:56 +0000544 except DBError as dberror:
Collin Wintera65e94c2007-08-22 21:45:20 +0000545 raise TableDBError(dberror.args[1])
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000546 # return the matches as a list of dictionaries
547 return matching_rowids.values()
548
549
Barry Warsawf71de3e2003-01-28 17:20:44 +0000550 def __Select(self, table, columns, conditions):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000551 """__Select() - Used to implement Select and Delete (above)
552 Returns a dictionary keyed on rowids containing dicts
553 holding the row data for columns listed in the columns param
554 that match the given conditions.
555 * conditions is a dictionary keyed on column names
556 containing callable conditions expecting the data string as an
557 argument and returning a boolean.
558 """
559 # check the validity of each column name
Guido van Rossum20435132006-08-21 00:21:47 +0000560 if table not in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000561 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000562 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000563 columns = self.tablecolumns[table]
Brett Cannon0072e432007-02-22 06:40:59 +0000564 for column in (columns + list(conditions.keys())):
Barry Warsawf71de3e2003-01-28 17:20:44 +0000565 if not self.__tablecolumns[table].count(column):
Collin Wintera65e94c2007-08-22 21:45:20 +0000566 raise TableDBError("unknown column: %r" % (column,))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000567
568 # keyed on rows that match so far, containings dicts keyed on
569 # column names containing the data for that row and column.
570 matching_rowids = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000571 # keys are rowids that do not match
572 rejected_rowids = {}
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000573
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000574 # attempt to sort the conditions in such a way as to minimize full
575 # column lookups
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000576 def cmp_conditions(atuple, btuple):
577 a = atuple[1]
578 b = btuple[1]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000579 if type(a) is type(b):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000580 if isinstance(a, PrefixCond) and isinstance(b, PrefixCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000581 # longest prefix first
582 return cmp(len(b.prefix), len(a.prefix))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000583 if isinstance(a, LikeCond) and isinstance(b, LikeCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000584 # longest likestr first
585 return cmp(len(b.likestr), len(a.likestr))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000586 return 0
587 if isinstance(a, ExactCond):
588 return -1
589 if isinstance(b, ExactCond):
590 return 1
591 if isinstance(a, PrefixCond):
592 return -1
593 if isinstance(b, PrefixCond):
594 return 1
595 # leave all unknown condition callables alone as equals
596 return 0
597
Brett Cannon0072e432007-02-22 06:40:59 +0000598 conditionlist = list(conditions.items())
Raymond Hettingerd4cb56d2008-01-30 02:55:10 +0000599 conditionlist.sort(key=CmpToKey(cmp_conditions))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000600
601 # Apply conditions to column data to find what we want
602 cur = self.db.cursor()
603 column_num = -1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000604 for column, condition in conditionlist:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000605 column_num = column_num + 1
606 searchkey = _search_col_data_key(table, column)
607 # speedup: don't linear search columns within loop
Barry Warsawf71de3e2003-01-28 17:20:44 +0000608 if column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000609 savethiscolumndata = 1 # save the data for return
Barry Warsawf71de3e2003-01-28 17:20:44 +0000610 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000611 savethiscolumndata = 0 # data only used for selection
612
613 try:
614 key, data = cur.set_range(searchkey)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000615 while key[:len(searchkey)] == searchkey:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000616 # extract the rowid from the key
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000617 rowid = key[-_rowid_str_len:].decode("latin-1")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000618
Guido van Rossum20435132006-08-21 00:21:47 +0000619 if rowid not in rejected_rowids:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000620 # if no condition was specified or the condition
621 # succeeds, add row to our match list.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000622 if not condition or condition(data):
Guido van Rossum20435132006-08-21 00:21:47 +0000623 if rowid not in matching_rowids:
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000624 matching_rowids[rowid] = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000625 if savethiscolumndata:
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000626 matching_rowids[rowid][column] = data
Barry Warsawf71de3e2003-01-28 17:20:44 +0000627 else:
Guido van Rossum20435132006-08-21 00:21:47 +0000628 if rowid in matching_rowids:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000629 del matching_rowids[rowid]
630 rejected_rowids[rowid] = rowid
631
632 key, data = cur.next()
633
Guido van Rossumb940e112007-01-10 16:19:56 +0000634 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000635 if dberror.args[0] != DB_NOTFOUND:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000636 raise
637 continue
638
639 cur.close()
640
641 # we're done selecting rows, garbage collect the reject list
642 del rejected_rowids
643
644 # extract any remaining desired column data from the
645 # database for the matching rows.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000646 if len(columns) > 0:
647 for rowid, rowdata in matching_rowids.items():
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000648 rowid = rowid.encode("latin-1")
Barry Warsawf71de3e2003-01-28 17:20:44 +0000649 for column in columns:
Guido van Rossum20435132006-08-21 00:21:47 +0000650 if column in rowdata:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000651 continue
652 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000653 rowdata[column] = self.db.get(
654 _data_key(table, column, rowid))
Guido van Rossumb940e112007-01-10 16:19:56 +0000655 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000656 if dberror.args[0] != DB_NOTFOUND:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000657 raise
658 rowdata[column] = None
659
660 # return the matches
661 return matching_rowids
662
663
Barry Warsawf71de3e2003-01-28 17:20:44 +0000664 def Drop(self, table):
665 """Remove an entire table from the database"""
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000666 txn = None
667 try:
668 txn = self.env.txn_begin()
669
670 # delete the column list
Gregory P. Smith66077d82007-10-18 16:55:12 +0000671 self.db.delete(_columns_key(table), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000672
673 cur = self.db.cursor(txn)
674
675 # delete all keys containing this tables column and row info
676 table_key = _search_all_data_key(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000677 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000678 try:
679 key, data = cur.set_range(table_key)
680 except DBNotFoundError:
681 break
682 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000683 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000684 break
685 cur.delete()
686
687 # delete all rowids used by this table
688 table_key = _search_rowid_key(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000689 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000690 try:
691 key, data = cur.set_range(table_key)
692 except DBNotFoundError:
693 break
694 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000695 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000696 break
697 cur.delete()
698
699 cur.close()
700
701 # delete the tablename from the table name list
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000702 tablelist = pickle.loads(
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000703 self.db.get(_E(_table_names_key), txn=txn, flags=DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000704 try:
705 tablelist.remove(table)
706 except ValueError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000707 # hmm, it wasn't there, oh well, that's what we want.
708 pass
709 # delete 1st, incase we opened with DB_DUP
Gregory P. Smith66077d82007-10-18 16:55:12 +0000710 self.db.delete(_E(_table_names_key), txn=txn)
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000711 self.db.put(_E(_table_names_key), pickle.dumps(tablelist, 1), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000712
713 txn.commit()
714 txn = None
715
Guido van Rossum20435132006-08-21 00:21:47 +0000716 if table in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000717 del self.__tablecolumns[table]
718
Guido van Rossumb940e112007-01-10 16:19:56 +0000719 except DBError as dberror:
Collin Wintera65e94c2007-08-22 21:45:20 +0000720 raise TableDBError(dberror.args[1])
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000721 finally:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000722 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000723 txn.abort()