blob: c1628ab4d9eaf962bc446dddb7e44de1db812e4c [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
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
91#
92# keys used to store database metadata
93#
Martin v. Löwiscccc58d2007-08-10 08:36:56 +000094_table_names_key = '__TABLE_NAMES__' # list of the tables in this db
95_columns = '._COLUMNS__' # table_name+this key contains a list of columns
Barry Warsawf71de3e2003-01-28 17:20:44 +000096
97def _columns_key(table):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +000098 return _E(table + _columns)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000099
100#
101# these keys are found within table sub databases
102#
103_data = '._DATA_.' # this+column+this+rowid key contains table data
104_rowid = '._ROWID_.' # this+rowid+this key contains a unique entry for each
105 # row in the table. (no data is stored)
106_rowid_str_len = 8 # length in bytes of the unique rowid strings
Barry Warsawf71de3e2003-01-28 17:20:44 +0000107
108def _data_key(table, col, rowid):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000109 return _E(table + _data + col + _data) + rowid
Barry Warsawf71de3e2003-01-28 17:20:44 +0000110
111def _search_col_data_key(table, col):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000112 return _E(table + _data + col + _data)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000113
114def _search_all_data_key(table):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000115 return _E(table + _data)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000116
117def _rowid_key(table, rowid):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000118 return _E(table + _rowid) + rowid + _E(_rowid)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000119
120def _search_rowid_key(table):
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000121 return _E(table + _rowid)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000122
123def contains_metastrings(s) :
124 """Verify that the given string does not contain any
125 metadata strings that might interfere with dbtables database operation.
126 """
Barry Warsawf71de3e2003-01-28 17:20:44 +0000127 if (s.find(_table_names_key) >= 0 or
128 s.find(_columns) >= 0 or
129 s.find(_data) >= 0 or
130 s.find(_rowid) >= 0):
131 # Then
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000132 return 1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000133 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000134 return 0
135
136
137class bsdTableDB :
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000138 def __init__(self, filename, dbhome, create=0, truncate=0, mode=0o600,
Barry Warsawf71de3e2003-01-28 17:20:44 +0000139 recover=0, dbflags=0):
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000140 """bsdTableDB(filename, dbhome, create=0, truncate=0, mode=0o600)
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000141
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000142 Open database name in the dbhome BerkeleyDB directory.
143 Use keyword arguments when calling this constructor.
144 """
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000145 self.db = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000146 myflags = DB_THREAD
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000147 if create:
148 myflags |= DB_CREATE
149 flagsforenv = (DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG |
150 DB_INIT_TXN | dbflags)
151 # DB_AUTO_COMMIT isn't a valid flag for env.open()
152 try:
153 dbflags |= DB_AUTO_COMMIT
154 except AttributeError:
155 pass
156 if recover:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000157 flagsforenv = flagsforenv | DB_RECOVER
158 self.env = DBEnv()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000159 # enable auto deadlock avoidance
160 self.env.set_lk_detect(DB_LOCK_DEFAULT)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000161 self.env.open(dbhome, myflags | flagsforenv)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000162 if truncate:
163 myflags |= DB_TRUNCATE
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000164 self.db = DB(self.env)
Gregory P. Smith455d46f2003-07-09 04:45:59 +0000165 # this code relies on DBCursor.set* methods to raise exceptions
166 # rather than returning None
167 self.db.set_get_returns_none(1)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000168 # allow duplicate entries [warning: be careful w/ metadata]
169 self.db.set_flags(DB_DUP)
170 self.db.open(filename, DB_BTREE, dbflags | myflags, mode)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000171 self.dbfilename = filename
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000172 # Initialize the table names list if this is a new database
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000173 txn = self.env.txn_begin()
174 try:
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000175 if not self.db.has_key(_E(_table_names_key), txn):
176 self.db.put(_E(_table_names_key), pickle.dumps([], 1), txn=txn)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000177 # Yes, bare except
178 except:
179 txn.abort()
180 raise
181 else:
182 txn.commit()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000183 # TODO verify more of the database's metadata?
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000184 self.__tablecolumns = {}
185
186 def __del__(self):
187 self.close()
188
189 def close(self):
190 if self.db is not None:
191 self.db.close()
192 self.db = None
193 if self.env is not None:
194 self.env.close()
195 self.env = None
196
197 def checkpoint(self, mins=0):
198 try:
199 self.env.txn_checkpoint(mins)
200 except DBIncompleteError:
201 pass
202
203 def sync(self):
204 try:
205 self.db.sync()
206 except DBIncompleteError:
207 pass
208
209 def _db_print(self) :
210 """Print the database to stdout for debugging"""
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000211 print("******** Printing raw database for debugging ********")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000212 cur = self.db.cursor()
213 try:
214 key, data = cur.first()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000215 while 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000216 print(repr({key: data}))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000217 next = cur.next()
218 if next:
219 key, data = next
220 else:
221 cur.close()
222 return
223 except DBNotFoundError:
224 cur.close()
225
226
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000227 def CreateTable(self, table, columns):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000228 """CreateTable(table, columns) - Create a new table in the database.
229
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000230 raises TableDBError if it already exists or for other DB errors.
231 """
Guido van Rossum13257902007-06-07 23:15:56 +0000232 assert isinstance(columns, list)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000233 txn = None
234 try:
235 # checking sanity of the table and column names here on
236 # table creation will prevent problems elsewhere.
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000237 if contains_metastrings(table):
238 raise ValueError(
239 "bad table name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000240 for column in columns :
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000241 if contains_metastrings(column):
242 raise ValueError(
243 "bad column name: contains reserved metastrings")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000244
245 columnlist_key = _columns_key(table)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000246 if self.db.has_key(columnlist_key):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000247 raise TableAlreadyExists, "table already exists"
248
249 txn = self.env.txn_begin()
250 # store the table's column info
251 self.db.put(columnlist_key, pickle.dumps(columns, 1), txn=txn)
252
253 # add the table name to the tablelist
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000254 tablelist = pickle.loads(self.db.get(_E(_table_names_key), txn=txn,
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000255 flags=DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000256 tablelist.append(table)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000257 # delete 1st, in case we opened with DB_DUP
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000258 self.db.delete(_E(_table_names_key), txn)
259 self.db.put(_E(_table_names_key), pickle.dumps(tablelist, 1), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000260
261 txn.commit()
262 txn = None
Guido van Rossumb940e112007-01-10 16:19:56 +0000263 except DBError as dberror:
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000264 raise TableDBError, dberror.args[1]
265 finally:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000266 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000267 txn.abort()
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000268 txn = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000269
270 def ListTableColumns(self, table):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000271 """Return a list of columns in the given table.
272 [] if the table doesn't exist.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000273 """
Guido van Rossum13257902007-06-07 23:15:56 +0000274 assert isinstance(table, str)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000275 if contains_metastrings(table):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000276 raise ValueError, "bad table name: contains reserved metastrings"
277
278 columnlist_key = _columns_key(table)
279 if not self.db.has_key(columnlist_key):
280 return []
281 pickledcolumnlist = self.db.get(columnlist_key)
282 if pickledcolumnlist:
283 return pickle.loads(pickledcolumnlist)
284 else:
285 return []
286
287 def ListTables(self):
288 """Return a list of tables in this database."""
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000289 pickledtablelist = self.db.get(_E(_table_names_key))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000290 if pickledtablelist:
291 return pickle.loads(pickledtablelist)
292 else:
293 return []
294
295 def CreateOrExtendTable(self, table, columns):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000296 """CreateOrExtendTable(table, columns)
297
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000298 Create a new table in the database.
299
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000300 If a table of this name already exists, extend it to have any
301 additional columns present in the given list as well as
302 all of its current columns.
303 """
Guido van Rossum13257902007-06-07 23:15:56 +0000304 assert isinstance(columns, list)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000305 try:
306 self.CreateTable(table, columns)
307 except TableAlreadyExists:
308 # the table already existed, add any new columns
309 txn = None
310 try:
311 columnlist_key = _columns_key(table)
312 txn = self.env.txn_begin()
313
314 # load the current column list
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000315 oldcolumnlist = pickle.loads(
316 self.db.get(columnlist_key, txn=txn, flags=DB_RMW))
317 # create a hash table for fast lookups of column names in the
318 # loop below
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000319 oldcolumnhash = {}
320 for c in oldcolumnlist:
321 oldcolumnhash[c] = c
322
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000323 # create a new column list containing both the old and new
324 # column names
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000325 newcolumnlist = copy.copy(oldcolumnlist)
326 for c in columns:
Guido van Rossum20435132006-08-21 00:21:47 +0000327 if c not in oldcolumnhash:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000328 newcolumnlist.append(c)
329
330 # store the table's new extended column list
331 if newcolumnlist != oldcolumnlist :
332 # delete the old one first since we opened with DB_DUP
333 self.db.delete(columnlist_key, txn)
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000334 self.db.put(columnlist_key,
335 pickle.dumps(newcolumnlist, 1),
336 txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000337
338 txn.commit()
339 txn = None
340
341 self.__load_column_info(table)
Guido van Rossumb940e112007-01-10 16:19:56 +0000342 except DBError as dberror:
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000343 raise TableDBError, dberror.args[1]
344 finally:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000345 if txn:
346 txn.abort()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000347
348
349 def __load_column_info(self, table) :
350 """initialize the self.__tablecolumns dict"""
351 # check the column names
352 try:
353 tcolpickles = self.db.get(_columns_key(table))
354 except DBNotFoundError:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000355 raise TableDBError, "unknown table: %r" % (table,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000356 if not tcolpickles:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000357 raise TableDBError, "unknown table: %r" % (table,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000358 self.__tablecolumns[table] = pickle.loads(tcolpickles)
359
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000360 def __new_rowid(self, table, txn) :
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000361 """Create a new unique row identifier"""
362 unique = 0
Barry Warsawf71de3e2003-01-28 17:20:44 +0000363 while not unique:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000364 # Generate a random 64-bit row ID string
365 # (note: this code has <64 bits of randomness
366 # but it's plenty for our database id needs!)
367 p = xdrlib.Packer()
Tim Peters95334a52004-08-08 00:54:21 +0000368 p.pack_int(int(random.random()*2147483647))
369 p.pack_int(int(random.random()*2147483647))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000370 newid = p.get_buffer()
371
372 # Guarantee uniqueness by adding this key to the database
373 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000374 self.db.put(_rowid_key(table, newid), None, txn=txn,
375 flags=DB_NOOVERWRITE)
376 except DBKeyExistError:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000377 pass
378 else:
379 unique = 1
380
381 return newid
382
383
384 def Insert(self, table, rowdict) :
385 """Insert(table, datadict) - Insert a new row into the table
386 using the keys+values from rowdict as the column values.
387 """
388 txn = None
389 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000390 if not self.db.has_key(_columns_key(table)):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000391 raise TableDBError, "unknown table"
392
393 # check the validity of each column name
Guido van Rossum20435132006-08-21 00:21:47 +0000394 if table not in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000395 self.__load_column_info(table)
396 for column in rowdict.keys() :
Barry Warsawf71de3e2003-01-28 17:20:44 +0000397 if not self.__tablecolumns[table].count(column):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000398 raise TableDBError, "unknown column: %r" % (column,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000399
400 # get a unique row identifier for this row
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000401 txn = self.env.txn_begin()
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000402 rowid = self.__new_rowid(table, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000403
404 # insert the row values into the table database
Barry Warsawf71de3e2003-01-28 17:20:44 +0000405 for column, dataitem in rowdict.items():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000406 # store the value
407 self.db.put(_data_key(table, column, rowid), dataitem, txn=txn)
408
409 txn.commit()
410 txn = None
411
Guido van Rossumb940e112007-01-10 16:19:56 +0000412 except DBError as dberror:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000413 # WIBNI we could just abort the txn and re-raise the exception?
414 # But no, because TableDBError is not related to DBError via
415 # inheritance, so it would be backwards incompatible. Do the next
416 # best thing.
417 info = sys.exc_info()
418 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000419 txn.abort()
420 self.db.delete(_rowid_key(table, rowid))
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000421 txn = None
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000422 raise TableDBError, dberror.args[1], info[2]
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000423 finally:
424 if txn:
425 txn.abort()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000426
427
Barry Warsawf71de3e2003-01-28 17:20:44 +0000428 def Modify(self, table, conditions={}, mappings={}):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000429 """Modify(table, conditions={}, mappings={}) - Modify items in rows matching 'conditions' using mapping functions in 'mappings'
430
431 * table - the table name
432 * conditions - a dictionary keyed on column names containing
433 a condition callable expecting the data string as an
434 argument and returning a boolean.
435 * mappings - a dictionary keyed on column names containing a
436 condition callable expecting the data string as an argument and
437 returning the new string for that column.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000438 """
439 try:
440 matching_rowids = self.__Select(table, [], conditions)
441
442 # modify only requested columns
443 columns = mappings.keys()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000444 for rowid in matching_rowids.keys():
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000445 rowid = rowid.encode("latin-1")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000446 txn = None
447 try:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000448 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000449 txn = self.env.txn_begin()
450 # modify the requested column
451 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000452 dataitem = self.db.get(
453 _data_key(table, column, rowid),
454 txn)
455 self.db.delete(
456 _data_key(table, column, rowid),
457 txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000458 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000459 # XXXXXXX row key somehow didn't exist, assume no
460 # error
461 dataitem = None
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000462 dataitem = mappings[column](dataitem)
Guido van Rossumb053cd82006-08-24 03:53:23 +0000463 if dataitem != None:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000464 self.db.put(
465 _data_key(table, column, rowid),
466 dataitem, txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000467 txn.commit()
468 txn = None
469
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000470 # catch all exceptions here since we call unknown callables
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000471 finally:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000472 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000473 txn.abort()
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000474
Guido van Rossumb940e112007-01-10 16:19:56 +0000475 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000476 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000477
Barry Warsawf71de3e2003-01-28 17:20:44 +0000478 def Delete(self, table, conditions={}):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000479 """Delete(table, conditions) - Delete items matching the given
480 conditions from the table.
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000481
482 * conditions - a dictionary keyed on column names containing
483 condition functions expecting the data string as an
484 argument and returning a boolean.
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000485 """
486 try:
487 matching_rowids = self.__Select(table, [], conditions)
488
489 # delete row data from all columns
490 columns = self.__tablecolumns[table]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000491 for rowid in matching_rowids.keys():
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000492 txn = None
493 try:
494 txn = self.env.txn_begin()
Barry Warsawf71de3e2003-01-28 17:20:44 +0000495 for column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000496 # delete the data key
497 try:
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000498 self.db.delete(_data_key(table, column,
499 rowid.encode("latin-1")),
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000500 txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000501 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000502 # XXXXXXX column may not exist, assume no error
503 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000504
505 try:
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000506 self.db.delete(_rowid_key(table, rowid.encode("latin-1")), txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000507 except DBNotFoundError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000508 # XXXXXXX row key somehow didn't exist, assume no error
509 pass
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000510 txn.commit()
511 txn = None
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000512 finally:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000513 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000514 txn.abort()
Guido van Rossumb940e112007-01-10 16:19:56 +0000515 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000516 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000517
518
Barry Warsawf71de3e2003-01-28 17:20:44 +0000519 def Select(self, table, columns, conditions={}):
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000520 """Select(table, columns, conditions) - retrieve specific row data
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000521 Returns a list of row column->value mapping dictionaries.
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000522
523 * columns - a list of which column data to return. If
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000524 columns is None, all columns will be returned.
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000525 * conditions - a dictionary keyed on column names
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000526 containing callable conditions expecting the data string as an
527 argument and returning a boolean.
528 """
529 try:
Guido van Rossum20435132006-08-21 00:21:47 +0000530 if table not in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000531 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000532 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000533 columns = self.__tablecolumns[table]
534 matching_rowids = self.__Select(table, columns, conditions)
Guido van Rossumb940e112007-01-10 16:19:56 +0000535 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000536 raise TableDBError, dberror.args[1]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000537 # return the matches as a list of dictionaries
538 return matching_rowids.values()
539
540
Barry Warsawf71de3e2003-01-28 17:20:44 +0000541 def __Select(self, table, columns, conditions):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000542 """__Select() - Used to implement Select and Delete (above)
543 Returns a dictionary keyed on rowids containing dicts
544 holding the row data for columns listed in the columns param
545 that match the given conditions.
546 * conditions is a dictionary keyed on column names
547 containing callable conditions expecting the data string as an
548 argument and returning a boolean.
549 """
550 # check the validity of each column name
Guido van Rossum20435132006-08-21 00:21:47 +0000551 if table not in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000552 self.__load_column_info(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000553 if columns is None:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000554 columns = self.tablecolumns[table]
Brett Cannon0072e432007-02-22 06:40:59 +0000555 for column in (columns + list(conditions.keys())):
Barry Warsawf71de3e2003-01-28 17:20:44 +0000556 if not self.__tablecolumns[table].count(column):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000557 raise TableDBError, "unknown column: %r" % (column,)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000558
559 # keyed on rows that match so far, containings dicts keyed on
560 # column names containing the data for that row and column.
561 matching_rowids = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000562 # keys are rowids that do not match
563 rejected_rowids = {}
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000564
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000565 # attempt to sort the conditions in such a way as to minimize full
566 # column lookups
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000567 def cmp_conditions(atuple, btuple):
568 a = atuple[1]
569 b = btuple[1]
Barry Warsawf71de3e2003-01-28 17:20:44 +0000570 if type(a) is type(b):
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000571 if isinstance(a, PrefixCond) and isinstance(b, PrefixCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000572 # longest prefix first
573 return cmp(len(b.prefix), len(a.prefix))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000574 if isinstance(a, LikeCond) and isinstance(b, LikeCond):
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000575 # longest likestr first
576 return cmp(len(b.likestr), len(a.likestr))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000577 return 0
578 if isinstance(a, ExactCond):
579 return -1
580 if isinstance(b, ExactCond):
581 return 1
582 if isinstance(a, PrefixCond):
583 return -1
584 if isinstance(b, PrefixCond):
585 return 1
586 # leave all unknown condition callables alone as equals
587 return 0
588
Brett Cannon0072e432007-02-22 06:40:59 +0000589 conditionlist = list(conditions.items())
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000590 conditionlist.sort(cmp_conditions)
591
592 # Apply conditions to column data to find what we want
593 cur = self.db.cursor()
594 column_num = -1
Barry Warsawf71de3e2003-01-28 17:20:44 +0000595 for column, condition in conditionlist:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000596 column_num = column_num + 1
597 searchkey = _search_col_data_key(table, column)
598 # speedup: don't linear search columns within loop
Barry Warsawf71de3e2003-01-28 17:20:44 +0000599 if column in columns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000600 savethiscolumndata = 1 # save the data for return
Barry Warsawf71de3e2003-01-28 17:20:44 +0000601 else:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000602 savethiscolumndata = 0 # data only used for selection
603
604 try:
605 key, data = cur.set_range(searchkey)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000606 while key[:len(searchkey)] == searchkey:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000607 # extract the rowid from the key
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000608 rowid = key[-_rowid_str_len:].decode("latin-1")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000609
Guido van Rossum20435132006-08-21 00:21:47 +0000610 if rowid not in rejected_rowids:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000611 # if no condition was specified or the condition
612 # succeeds, add row to our match list.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000613 if not condition or condition(data):
Guido van Rossum20435132006-08-21 00:21:47 +0000614 if rowid not in matching_rowids:
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000615 matching_rowids[rowid] = {}
Barry Warsawf71de3e2003-01-28 17:20:44 +0000616 if savethiscolumndata:
Martin v. Löwisb2c7aff2002-11-23 11:26:07 +0000617 matching_rowids[rowid][column] = data
Barry Warsawf71de3e2003-01-28 17:20:44 +0000618 else:
Guido van Rossum20435132006-08-21 00:21:47 +0000619 if rowid in matching_rowids:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000620 del matching_rowids[rowid]
621 rejected_rowids[rowid] = rowid
622
623 key, data = cur.next()
624
Guido van Rossumb940e112007-01-10 16:19:56 +0000625 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000626 if dberror.args[0] != DB_NOTFOUND:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000627 raise
628 continue
629
630 cur.close()
631
632 # we're done selecting rows, garbage collect the reject list
633 del rejected_rowids
634
635 # extract any remaining desired column data from the
636 # database for the matching rows.
Barry Warsawf71de3e2003-01-28 17:20:44 +0000637 if len(columns) > 0:
638 for rowid, rowdata in matching_rowids.items():
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000639 rowid = rowid.encode("latin-1")
Barry Warsawf71de3e2003-01-28 17:20:44 +0000640 for column in columns:
Guido van Rossum20435132006-08-21 00:21:47 +0000641 if column in rowdata:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000642 continue
643 try:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000644 rowdata[column] = self.db.get(
645 _data_key(table, column, rowid))
Guido van Rossumb940e112007-01-10 16:19:56 +0000646 except DBError as dberror:
Guido van Rossumd84da1b2007-03-28 21:03:48 +0000647 if dberror.args[0] != DB_NOTFOUND:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000648 raise
649 rowdata[column] = None
650
651 # return the matches
652 return matching_rowids
653
654
Barry Warsawf71de3e2003-01-28 17:20:44 +0000655 def Drop(self, table):
656 """Remove an entire table from the database"""
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000657 txn = None
658 try:
659 txn = self.env.txn_begin()
660
661 # delete the column list
662 self.db.delete(_columns_key(table), txn)
663
664 cur = self.db.cursor(txn)
665
666 # delete all keys containing this tables column and row info
667 table_key = _search_all_data_key(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000668 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000669 try:
670 key, data = cur.set_range(table_key)
671 except DBNotFoundError:
672 break
673 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000674 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000675 break
676 cur.delete()
677
678 # delete all rowids used by this table
679 table_key = _search_rowid_key(table)
Barry Warsawf71de3e2003-01-28 17:20:44 +0000680 while 1:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000681 try:
682 key, data = cur.set_range(table_key)
683 except DBNotFoundError:
684 break
685 # only delete items in this table
Barry Warsawf71de3e2003-01-28 17:20:44 +0000686 if key[:len(table_key)] != table_key:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000687 break
688 cur.delete()
689
690 cur.close()
691
692 # delete the tablename from the table name list
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000693 tablelist = pickle.loads(
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000694 self.db.get(_E(_table_names_key), txn=txn, flags=DB_RMW))
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000695 try:
696 tablelist.remove(table)
697 except ValueError:
Barry Warsaw9a0d7792002-12-30 20:53:52 +0000698 # hmm, it wasn't there, oh well, that's what we want.
699 pass
700 # delete 1st, incase we opened with DB_DUP
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000701 self.db.delete(_E(_table_names_key), txn)
702 self.db.put(_E(_table_names_key), pickle.dumps(tablelist, 1), txn=txn)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000703
704 txn.commit()
705 txn = None
706
Guido van Rossum20435132006-08-21 00:21:47 +0000707 if table in self.__tablecolumns:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000708 del self.__tablecolumns[table]
709
Guido van Rossumb940e112007-01-10 16:19:56 +0000710 except DBError as dberror:
Martin v. Löwis32ca4422007-08-11 06:13:20 +0000711 raise TableDBError, dberror.args[1]
712 finally:
Barry Warsawf71de3e2003-01-28 17:20:44 +0000713 if txn:
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000714 txn.abort()