ยปCore Development>Code coverage>Lib/bsddb/dbtables.py

Python code coverage for Lib/bsddb/dbtables.py

#countcontent
1n/a#-----------------------------------------------------------------------
2n/a#
3n/a# Copyright (C) 2000, 2001 by Autonomous Zone Industries
4n/a# Copyright (C) 2002 Gregory P. Smith
5n/a#
6n/a# License: This is free software. You may use this software for any
7n/a# purpose including modification/redistribution, so long as
8n/a# this header remains intact and that you do not claim any
9n/a# rights of ownership or authorship of this software. This
10n/a# software has been tested, but no warranty is expressed or
11n/a# implied.
12n/a#
13n/a# -- Gregory P. Smith <greg@krypto.org>
14n/a
15n/a# This provides a simple database table interface built on top of
16n/a# the Python Berkeley DB 3 interface.
17n/a#
18n/a_cvsid = '$Id: dbtables.py 79285 2010-03-22 14:22:26Z jesus.cea $'
19n/a
20n/aimport re
21n/aimport sys
22n/aimport copy
23n/aimport random
24n/aimport struct
25n/a
26n/a
27n/aif sys.version_info[0] >= 3 :
28n/a import pickle
29n/aelse :
30n/a if sys.version_info < (2, 6) :
31n/a import cPickle as pickle
32n/a else :
33n/a # When we drop support for python 2.3 and 2.4
34n/a # we could use: (in 2.5 we need a __future__ statement)
35n/a #
36n/a # with warnings.catch_warnings():
37n/a # warnings.filterwarnings(...)
38n/a # ...
39n/a #
40n/a # We can not use "with" as is, because it would be invalid syntax
41n/a # in python 2.3, 2.4 and (with no __future__) 2.5.
42n/a # Here we simulate "with" following PEP 343 :
43n/a import warnings
44n/a w = warnings.catch_warnings()
45n/a w.__enter__()
46n/a try :
47n/a warnings.filterwarnings('ignore',
48n/a message='the cPickle module has been removed in Python 3.0',
49n/a category=DeprecationWarning)
50n/a import cPickle as pickle
51n/a finally :
52n/a w.__exit__()
53n/a del w
54n/a
55n/atry:
56n/a # For Pythons w/distutils pybsddb
57n/a from bsddb3 import db
58n/aexcept ImportError:
59n/a # For Python 2.3
60n/a from bsddb import db
61n/a
62n/aclass TableDBError(StandardError):
63n/a pass
64n/aclass TableAlreadyExists(TableDBError):
65n/a pass
66n/a
67n/a
68n/aclass Cond:
69n/a """This condition matches everything"""
70n/a def __call__(self, s):
71n/a return 1
72n/a
73n/aclass ExactCond(Cond):
74n/a """Acts as an exact match condition function"""
75n/a def __init__(self, strtomatch):
76n/a self.strtomatch = strtomatch
77n/a def __call__(self, s):
78n/a return s == self.strtomatch
79n/a
80n/aclass PrefixCond(Cond):
81n/a """Acts as a condition function for matching a string prefix"""
82n/a def __init__(self, prefix):
83n/a self.prefix = prefix
84n/a def __call__(self, s):
85n/a return s[:len(self.prefix)] == self.prefix
86n/a
87n/aclass PostfixCond(Cond):
88n/a """Acts as a condition function for matching a string postfix"""
89n/a def __init__(self, postfix):
90n/a self.postfix = postfix
91n/a def __call__(self, s):
92n/a return s[-len(self.postfix):] == self.postfix
93n/a
94n/aclass LikeCond(Cond):
95n/a """
96n/a Acts as a function that will match using an SQL 'LIKE' style
97n/a string. Case insensitive and % signs are wild cards.
98n/a This isn't perfect but it should work for the simple common cases.
99n/a """
100n/a def __init__(self, likestr, re_flags=re.IGNORECASE):
101n/a # escape python re characters
102n/a chars_to_escape = '.*+()[]?'
103n/a for char in chars_to_escape :
104n/a likestr = likestr.replace(char, '\\'+char)
105n/a # convert %s to wildcards
106n/a self.likestr = likestr.replace('%', '.*')
107n/a self.re = re.compile('^'+self.likestr+'$', re_flags)
108n/a def __call__(self, s):
109n/a return self.re.match(s)
110n/a
111n/a#
112n/a# keys used to store database metadata
113n/a#
114n/a_table_names_key = '__TABLE_NAMES__' # list of the tables in this db
115n/a_columns = '._COLUMNS__' # table_name+this key contains a list of columns
116n/a
117n/adef _columns_key(table):
118n/a return table + _columns
119n/a
120n/a#
121n/a# these keys are found within table sub databases
122n/a#
123n/a_data = '._DATA_.' # this+column+this+rowid key contains table data
124n/a_rowid = '._ROWID_.' # this+rowid+this key contains a unique entry for each
125n/a # row in the table. (no data is stored)
126n/a_rowid_str_len = 8 # length in bytes of the unique rowid strings
127n/a
128n/a
129n/adef _data_key(table, col, rowid):
130n/a return table + _data + col + _data + rowid
131n/a
132n/adef _search_col_data_key(table, col):
133n/a return table + _data + col + _data
134n/a
135n/adef _search_all_data_key(table):
136n/a return table + _data
137n/a
138n/adef _rowid_key(table, rowid):
139n/a return table + _rowid + rowid + _rowid
140n/a
141n/adef _search_rowid_key(table):
142n/a return table + _rowid
143n/a
144n/adef contains_metastrings(s) :
145n/a """Verify that the given string does not contain any
146n/a metadata strings that might interfere with dbtables database operation.
147n/a """
148n/a if (s.find(_table_names_key) >= 0 or
149n/a s.find(_columns) >= 0 or
150n/a s.find(_data) >= 0 or
151n/a s.find(_rowid) >= 0):
152n/a # Then
153n/a return 1
154n/a else:
155n/a return 0
156n/a
157n/a
158n/aclass bsdTableDB :
159n/a def __init__(self, filename, dbhome, create=0, truncate=0, mode=0600,
160n/a recover=0, dbflags=0):
161n/a """bsdTableDB(filename, dbhome, create=0, truncate=0, mode=0600)
162n/a
163n/a Open database name in the dbhome Berkeley DB directory.
164n/a Use keyword arguments when calling this constructor.
165n/a """
166n/a self.db = None
167n/a myflags = db.DB_THREAD
168n/a if create:
169n/a myflags |= db.DB_CREATE
170n/a flagsforenv = (db.DB_INIT_MPOOL | db.DB_INIT_LOCK | db.DB_INIT_LOG |
171n/a db.DB_INIT_TXN | dbflags)
172n/a # DB_AUTO_COMMIT isn't a valid flag for env.open()
173n/a try:
174n/a dbflags |= db.DB_AUTO_COMMIT
175n/a except AttributeError:
176n/a pass
177n/a if recover:
178n/a flagsforenv = flagsforenv | db.DB_RECOVER
179n/a self.env = db.DBEnv()
180n/a # enable auto deadlock avoidance
181n/a self.env.set_lk_detect(db.DB_LOCK_DEFAULT)
182n/a self.env.open(dbhome, myflags | flagsforenv)
183n/a if truncate:
184n/a myflags |= db.DB_TRUNCATE
185n/a self.db = db.DB(self.env)
186n/a # this code relies on DBCursor.set* methods to raise exceptions
187n/a # rather than returning None
188n/a self.db.set_get_returns_none(1)
189n/a # allow duplicate entries [warning: be careful w/ metadata]
190n/a self.db.set_flags(db.DB_DUP)
191n/a self.db.open(filename, db.DB_BTREE, dbflags | myflags, mode)
192n/a self.dbfilename = filename
193n/a
194n/a if sys.version_info[0] >= 3 :
195n/a class cursor_py3k(object) :
196n/a def __init__(self, dbcursor) :
197n/a self._dbcursor = dbcursor
198n/a
199n/a def close(self) :
200n/a return self._dbcursor.close()
201n/a
202n/a def set_range(self, search) :
203n/a v = self._dbcursor.set_range(bytes(search, "iso8859-1"))
204n/a if v is not None :
205n/a v = (v[0].decode("iso8859-1"),
206n/a v[1].decode("iso8859-1"))
207n/a return v
208n/a
209n/a def __next__(self) :
210n/a v = getattr(self._dbcursor, "next")()
211n/a if v is not None :
212n/a v = (v[0].decode("iso8859-1"),
213n/a v[1].decode("iso8859-1"))
214n/a return v
215n/a
216n/a class db_py3k(object) :
217n/a def __init__(self, db) :
218n/a self._db = db
219n/a
220n/a def cursor(self, txn=None) :
221n/a return cursor_py3k(self._db.cursor(txn=txn))
222n/a
223n/a def has_key(self, key, txn=None) :
224n/a return getattr(self._db,"has_key")(bytes(key, "iso8859-1"),
225n/a txn=txn)
226n/a
227n/a def put(self, key, value, flags=0, txn=None) :
228n/a key = bytes(key, "iso8859-1")
229n/a if value is not None :
230n/a value = bytes(value, "iso8859-1")
231n/a return self._db.put(key, value, flags=flags, txn=txn)
232n/a
233n/a def put_bytes(self, key, value, txn=None) :
234n/a key = bytes(key, "iso8859-1")
235n/a return self._db.put(key, value, txn=txn)
236n/a
237n/a def get(self, key, txn=None, flags=0) :
238n/a key = bytes(key, "iso8859-1")
239n/a v = self._db.get(key, txn=txn, flags=flags)
240n/a if v is not None :
241n/a v = v.decode("iso8859-1")
242n/a return v
243n/a
244n/a def get_bytes(self, key, txn=None, flags=0) :
245n/a key = bytes(key, "iso8859-1")
246n/a return self._db.get(key, txn=txn, flags=flags)
247n/a
248n/a def delete(self, key, txn=None) :
249n/a key = bytes(key, "iso8859-1")
250n/a return self._db.delete(key, txn=txn)
251n/a
252n/a def close (self) :
253n/a return self._db.close()
254n/a
255n/a self.db = db_py3k(self.db)
256n/a else : # Python 2.x
257n/a pass
258n/a
259n/a # Initialize the table names list if this is a new database
260n/a txn = self.env.txn_begin()
261n/a try:
262n/a if not getattr(self.db, "has_key")(_table_names_key, txn):
263n/a getattr(self.db, "put_bytes", self.db.put) \
264n/a (_table_names_key, pickle.dumps([], 1), txn=txn)
265n/a # Yes, bare except
266n/a except:
267n/a txn.abort()
268n/a raise
269n/a else:
270n/a txn.commit()
271n/a # TODO verify more of the database's metadata?
272n/a self.__tablecolumns = {}
273n/a
274n/a def __del__(self):
275n/a self.close()
276n/a
277n/a def close(self):
278n/a if self.db is not None:
279n/a self.db.close()
280n/a self.db = None
281n/a if self.env is not None:
282n/a self.env.close()
283n/a self.env = None
284n/a
285n/a def checkpoint(self, mins=0):
286n/a self.env.txn_checkpoint(mins)
287n/a
288n/a def sync(self):
289n/a self.db.sync()
290n/a
291n/a def _db_print(self) :
292n/a """Print the database to stdout for debugging"""
293n/a print "******** Printing raw database for debugging ********"
294n/a cur = self.db.cursor()
295n/a try:
296n/a key, data = cur.first()
297n/a while 1:
298n/a print repr({key: data})
299n/a next = cur.next()
300n/a if next:
301n/a key, data = next
302n/a else:
303n/a cur.close()
304n/a return
305n/a except db.DBNotFoundError:
306n/a cur.close()
307n/a
308n/a
309n/a def CreateTable(self, table, columns):
310n/a """CreateTable(table, columns) - Create a new table in the database.
311n/a
312n/a raises TableDBError if it already exists or for other DB errors.
313n/a """
314n/a assert isinstance(columns, list)
315n/a
316n/a txn = None
317n/a try:
318n/a # checking sanity of the table and column names here on
319n/a # table creation will prevent problems elsewhere.
320n/a if contains_metastrings(table):
321n/a raise ValueError(
322n/a "bad table name: contains reserved metastrings")
323n/a for column in columns :
324n/a if contains_metastrings(column):
325n/a raise ValueError(
326n/a "bad column name: contains reserved metastrings")
327n/a
328n/a columnlist_key = _columns_key(table)
329n/a if getattr(self.db, "has_key")(columnlist_key):
330n/a raise TableAlreadyExists, "table already exists"
331n/a
332n/a txn = self.env.txn_begin()
333n/a # store the table's column info
334n/a getattr(self.db, "put_bytes", self.db.put)(columnlist_key,
335n/a pickle.dumps(columns, 1), txn=txn)
336n/a
337n/a # add the table name to the tablelist
338n/a tablelist = pickle.loads(getattr(self.db, "get_bytes",
339n/a self.db.get) (_table_names_key, txn=txn, flags=db.DB_RMW))
340n/a tablelist.append(table)
341n/a # delete 1st, in case we opened with DB_DUP
342n/a self.db.delete(_table_names_key, txn=txn)
343n/a getattr(self.db, "put_bytes", self.db.put)(_table_names_key,
344n/a pickle.dumps(tablelist, 1), txn=txn)
345n/a
346n/a txn.commit()
347n/a txn = None
348n/a except db.DBError, dberror:
349n/a if txn:
350n/a txn.abort()
351n/a if sys.version_info < (2, 6) :
352n/a raise TableDBError, dberror[1]
353n/a else :
354n/a raise TableDBError, dberror.args[1]
355n/a
356n/a
357n/a def ListTableColumns(self, table):
358n/a """Return a list of columns in the given table.
359n/a [] if the table doesn't exist.
360n/a """
361n/a assert isinstance(table, str)
362n/a if contains_metastrings(table):
363n/a raise ValueError, "bad table name: contains reserved metastrings"
364n/a
365n/a columnlist_key = _columns_key(table)
366n/a if not getattr(self.db, "has_key")(columnlist_key):
367n/a return []
368n/a pickledcolumnlist = getattr(self.db, "get_bytes",
369n/a self.db.get)(columnlist_key)
370n/a if pickledcolumnlist:
371n/a return pickle.loads(pickledcolumnlist)
372n/a else:
373n/a return []
374n/a
375n/a def ListTables(self):
376n/a """Return a list of tables in this database."""
377n/a pickledtablelist = self.db.get_get(_table_names_key)
378n/a if pickledtablelist:
379n/a return pickle.loads(pickledtablelist)
380n/a else:
381n/a return []
382n/a
383n/a def CreateOrExtendTable(self, table, columns):
384n/a """CreateOrExtendTable(table, columns)
385n/a
386n/a Create a new table in the database.
387n/a
388n/a If a table of this name already exists, extend it to have any
389n/a additional columns present in the given list as well as
390n/a all of its current columns.
391n/a """
392n/a assert isinstance(columns, list)
393n/a
394n/a try:
395n/a self.CreateTable(table, columns)
396n/a except TableAlreadyExists:
397n/a # the table already existed, add any new columns
398n/a txn = None
399n/a try:
400n/a columnlist_key = _columns_key(table)
401n/a txn = self.env.txn_begin()
402n/a
403n/a # load the current column list
404n/a oldcolumnlist = pickle.loads(
405n/a getattr(self.db, "get_bytes",
406n/a self.db.get)(columnlist_key, txn=txn, flags=db.DB_RMW))
407n/a # create a hash table for fast lookups of column names in the
408n/a # loop below
409n/a oldcolumnhash = {}
410n/a for c in oldcolumnlist:
411n/a oldcolumnhash[c] = c
412n/a
413n/a # create a new column list containing both the old and new
414n/a # column names
415n/a newcolumnlist = copy.copy(oldcolumnlist)
416n/a for c in columns:
417n/a if not c in oldcolumnhash:
418n/a newcolumnlist.append(c)
419n/a
420n/a # store the table's new extended column list
421n/a if newcolumnlist != oldcolumnlist :
422n/a # delete the old one first since we opened with DB_DUP
423n/a self.db.delete(columnlist_key, txn=txn)
424n/a getattr(self.db, "put_bytes", self.db.put)(columnlist_key,
425n/a pickle.dumps(newcolumnlist, 1),
426n/a txn=txn)
427n/a
428n/a txn.commit()
429n/a txn = None
430n/a
431n/a self.__load_column_info(table)
432n/a except db.DBError, dberror:
433n/a if txn:
434n/a txn.abort()
435n/a if sys.version_info < (2, 6) :
436n/a raise TableDBError, dberror[1]
437n/a else :
438n/a raise TableDBError, dberror.args[1]
439n/a
440n/a
441n/a def __load_column_info(self, table) :
442n/a """initialize the self.__tablecolumns dict"""
443n/a # check the column names
444n/a try:
445n/a tcolpickles = getattr(self.db, "get_bytes",
446n/a self.db.get)(_columns_key(table))
447n/a except db.DBNotFoundError:
448n/a raise TableDBError, "unknown table: %r" % (table,)
449n/a if not tcolpickles:
450n/a raise TableDBError, "unknown table: %r" % (table,)
451n/a self.__tablecolumns[table] = pickle.loads(tcolpickles)
452n/a
453n/a def __new_rowid(self, table, txn) :
454n/a """Create a new unique row identifier"""
455n/a unique = 0
456n/a while not unique:
457n/a # Generate a random 64-bit row ID string
458n/a # (note: might have <64 bits of true randomness
459n/a # but it's plenty for our database id needs!)
460n/a blist = []
461n/a for x in xrange(_rowid_str_len):
462n/a blist.append(random.randint(0,255))
463n/a newid = struct.pack('B'*_rowid_str_len, *blist)
464n/a
465n/a if sys.version_info[0] >= 3 :
466n/a newid = newid.decode("iso8859-1") # 8 bits
467n/a
468n/a # Guarantee uniqueness by adding this key to the database
469n/a try:
470n/a self.db.put(_rowid_key(table, newid), None, txn=txn,
471n/a flags=db.DB_NOOVERWRITE)
472n/a except db.DBKeyExistError:
473n/a pass
474n/a else:
475n/a unique = 1
476n/a
477n/a return newid
478n/a
479n/a
480n/a def Insert(self, table, rowdict) :
481n/a """Insert(table, datadict) - Insert a new row into the table
482n/a using the keys+values from rowdict as the column values.
483n/a """
484n/a
485n/a txn = None
486n/a try:
487n/a if not getattr(self.db, "has_key")(_columns_key(table)):
488n/a raise TableDBError, "unknown table"
489n/a
490n/a # check the validity of each column name
491n/a if not table in self.__tablecolumns:
492n/a self.__load_column_info(table)
493n/a for column in rowdict.keys() :
494n/a if not self.__tablecolumns[table].count(column):
495n/a raise TableDBError, "unknown column: %r" % (column,)
496n/a
497n/a # get a unique row identifier for this row
498n/a txn = self.env.txn_begin()
499n/a rowid = self.__new_rowid(table, txn=txn)
500n/a
501n/a # insert the row values into the table database
502n/a for column, dataitem in rowdict.items():
503n/a # store the value
504n/a self.db.put(_data_key(table, column, rowid), dataitem, txn=txn)
505n/a
506n/a txn.commit()
507n/a txn = None
508n/a
509n/a except db.DBError, dberror:
510n/a # WIBNI we could just abort the txn and re-raise the exception?
511n/a # But no, because TableDBError is not related to DBError via
512n/a # inheritance, so it would be backwards incompatible. Do the next
513n/a # best thing.
514n/a info = sys.exc_info()
515n/a if txn:
516n/a txn.abort()
517n/a self.db.delete(_rowid_key(table, rowid))
518n/a if sys.version_info < (2, 6) :
519n/a raise TableDBError, dberror[1], info[2]
520n/a else :
521n/a raise TableDBError, dberror.args[1], info[2]
522n/a
523n/a
524n/a def Modify(self, table, conditions={}, mappings={}):
525n/a """Modify(table, conditions={}, mappings={}) - Modify items in rows matching 'conditions' using mapping functions in 'mappings'
526n/a
527n/a * table - the table name
528n/a * conditions - a dictionary keyed on column names containing
529n/a a condition callable expecting the data string as an
530n/a argument and returning a boolean.
531n/a * mappings - a dictionary keyed on column names containing a
532n/a condition callable expecting the data string as an argument and
533n/a returning the new string for that column.
534n/a """
535n/a
536n/a try:
537n/a matching_rowids = self.__Select(table, [], conditions)
538n/a
539n/a # modify only requested columns
540n/a columns = mappings.keys()
541n/a for rowid in matching_rowids.keys():
542n/a txn = None
543n/a try:
544n/a for column in columns:
545n/a txn = self.env.txn_begin()
546n/a # modify the requested column
547n/a try:
548n/a dataitem = self.db.get(
549n/a _data_key(table, column, rowid),
550n/a txn=txn)
551n/a self.db.delete(
552n/a _data_key(table, column, rowid),
553n/a txn=txn)
554n/a except db.DBNotFoundError:
555n/a # XXXXXXX row key somehow didn't exist, assume no
556n/a # error
557n/a dataitem = None
558n/a dataitem = mappings[column](dataitem)
559n/a if dataitem is not None:
560n/a self.db.put(
561n/a _data_key(table, column, rowid),
562n/a dataitem, txn=txn)
563n/a txn.commit()
564n/a txn = None
565n/a
566n/a # catch all exceptions here since we call unknown callables
567n/a except:
568n/a if txn:
569n/a txn.abort()
570n/a raise
571n/a
572n/a except db.DBError, dberror:
573n/a if sys.version_info < (2, 6) :
574n/a raise TableDBError, dberror[1]
575n/a else :
576n/a raise TableDBError, dberror.args[1]
577n/a
578n/a def Delete(self, table, conditions={}):
579n/a """Delete(table, conditions) - Delete items matching the given
580n/a conditions from the table.
581n/a
582n/a * conditions - a dictionary keyed on column names containing
583n/a condition functions expecting the data string as an
584n/a argument and returning a boolean.
585n/a """
586n/a
587n/a try:
588n/a matching_rowids = self.__Select(table, [], conditions)
589n/a
590n/a # delete row data from all columns
591n/a columns = self.__tablecolumns[table]
592n/a for rowid in matching_rowids.keys():
593n/a txn = None
594n/a try:
595n/a txn = self.env.txn_begin()
596n/a for column in columns:
597n/a # delete the data key
598n/a try:
599n/a self.db.delete(_data_key(table, column, rowid),
600n/a txn=txn)
601n/a except db.DBNotFoundError:
602n/a # XXXXXXX column may not exist, assume no error
603n/a pass
604n/a
605n/a try:
606n/a self.db.delete(_rowid_key(table, rowid), txn=txn)
607n/a except db.DBNotFoundError:
608n/a # XXXXXXX row key somehow didn't exist, assume no error
609n/a pass
610n/a txn.commit()
611n/a txn = None
612n/a except db.DBError, dberror:
613n/a if txn:
614n/a txn.abort()
615n/a raise
616n/a except db.DBError, dberror:
617n/a if sys.version_info < (2, 6) :
618n/a raise TableDBError, dberror[1]
619n/a else :
620n/a raise TableDBError, dberror.args[1]
621n/a
622n/a
623n/a def Select(self, table, columns, conditions={}):
624n/a """Select(table, columns, conditions) - retrieve specific row data
625n/a Returns a list of row column->value mapping dictionaries.
626n/a
627n/a * columns - a list of which column data to return. If
628n/a columns is None, all columns will be returned.
629n/a * conditions - a dictionary keyed on column names
630n/a containing callable conditions expecting the data string as an
631n/a argument and returning a boolean.
632n/a """
633n/a try:
634n/a if not table in self.__tablecolumns:
635n/a self.__load_column_info(table)
636n/a if columns is None:
637n/a columns = self.__tablecolumns[table]
638n/a matching_rowids = self.__Select(table, columns, conditions)
639n/a except db.DBError, dberror:
640n/a if sys.version_info < (2, 6) :
641n/a raise TableDBError, dberror[1]
642n/a else :
643n/a raise TableDBError, dberror.args[1]
644n/a # return the matches as a list of dictionaries
645n/a return matching_rowids.values()
646n/a
647n/a
648n/a def __Select(self, table, columns, conditions):
649n/a """__Select() - Used to implement Select and Delete (above)
650n/a Returns a dictionary keyed on rowids containing dicts
651n/a holding the row data for columns listed in the columns param
652n/a that match the given conditions.
653n/a * conditions is a dictionary keyed on column names
654n/a containing callable conditions expecting the data string as an
655n/a argument and returning a boolean.
656n/a """
657n/a # check the validity of each column name
658n/a if not table in self.__tablecolumns:
659n/a self.__load_column_info(table)
660n/a if columns is None:
661n/a columns = self.tablecolumns[table]
662n/a for column in (columns + conditions.keys()):
663n/a if not self.__tablecolumns[table].count(column):
664n/a raise TableDBError, "unknown column: %r" % (column,)
665n/a
666n/a # keyed on rows that match so far, containings dicts keyed on
667n/a # column names containing the data for that row and column.
668n/a matching_rowids = {}
669n/a # keys are rowids that do not match
670n/a rejected_rowids = {}
671n/a
672n/a # attempt to sort the conditions in such a way as to minimize full
673n/a # column lookups
674n/a def cmp_conditions(atuple, btuple):
675n/a a = atuple[1]
676n/a b = btuple[1]
677n/a if type(a) is type(b):
678n/a
679n/a # Needed for python 3. "cmp" vanished in 3.0.1
680n/a def cmp(a, b) :
681n/a if a==b : return 0
682n/a if a<b : return -1
683n/a return 1
684n/a
685n/a if isinstance(a, PrefixCond) and isinstance(b, PrefixCond):
686n/a # longest prefix first
687n/a return cmp(len(b.prefix), len(a.prefix))
688n/a if isinstance(a, LikeCond) and isinstance(b, LikeCond):
689n/a # longest likestr first
690n/a return cmp(len(b.likestr), len(a.likestr))
691n/a return 0
692n/a if isinstance(a, ExactCond):
693n/a return -1
694n/a if isinstance(b, ExactCond):
695n/a return 1
696n/a if isinstance(a, PrefixCond):
697n/a return -1
698n/a if isinstance(b, PrefixCond):
699n/a return 1
700n/a # leave all unknown condition callables alone as equals
701n/a return 0
702n/a
703n/a if sys.version_info < (2, 6) :
704n/a conditionlist = conditions.items()
705n/a conditionlist.sort(cmp_conditions)
706n/a else : # Insertion Sort. Please, improve
707n/a conditionlist = []
708n/a for i in conditions.items() :
709n/a for j, k in enumerate(conditionlist) :
710n/a r = cmp_conditions(k, i)
711n/a if r == 1 :
712n/a conditionlist.insert(j, i)
713n/a break
714n/a else :
715n/a conditionlist.append(i)
716n/a
717n/a # Apply conditions to column data to find what we want
718n/a cur = self.db.cursor()
719n/a column_num = -1
720n/a for column, condition in conditionlist:
721n/a column_num = column_num + 1
722n/a searchkey = _search_col_data_key(table, column)
723n/a # speedup: don't linear search columns within loop
724n/a if column in columns:
725n/a savethiscolumndata = 1 # save the data for return
726n/a else:
727n/a savethiscolumndata = 0 # data only used for selection
728n/a
729n/a try:
730n/a key, data = cur.set_range(searchkey)
731n/a while key[:len(searchkey)] == searchkey:
732n/a # extract the rowid from the key
733n/a rowid = key[-_rowid_str_len:]
734n/a
735n/a if not rowid in rejected_rowids:
736n/a # if no condition was specified or the condition
737n/a # succeeds, add row to our match list.
738n/a if not condition or condition(data):
739n/a if not rowid in matching_rowids:
740n/a matching_rowids[rowid] = {}
741n/a if savethiscolumndata:
742n/a matching_rowids[rowid][column] = data
743n/a else:
744n/a if rowid in matching_rowids:
745n/a del matching_rowids[rowid]
746n/a rejected_rowids[rowid] = rowid
747n/a
748n/a key, data = cur.next()
749n/a
750n/a except db.DBError, dberror:
751n/a if dberror.args[0] != db.DB_NOTFOUND:
752n/a raise
753n/a continue
754n/a
755n/a cur.close()
756n/a
757n/a # we're done selecting rows, garbage collect the reject list
758n/a del rejected_rowids
759n/a
760n/a # extract any remaining desired column data from the
761n/a # database for the matching rows.
762n/a if len(columns) > 0:
763n/a for rowid, rowdata in matching_rowids.items():
764n/a for column in columns:
765n/a if column in rowdata:
766n/a continue
767n/a try:
768n/a rowdata[column] = self.db.get(
769n/a _data_key(table, column, rowid))
770n/a except db.DBError, dberror:
771n/a if sys.version_info < (2, 6) :
772n/a if dberror[0] != db.DB_NOTFOUND:
773n/a raise
774n/a else :
775n/a if dberror.args[0] != db.DB_NOTFOUND:
776n/a raise
777n/a rowdata[column] = None
778n/a
779n/a # return the matches
780n/a return matching_rowids
781n/a
782n/a
783n/a def Drop(self, table):
784n/a """Remove an entire table from the database"""
785n/a txn = None
786n/a try:
787n/a txn = self.env.txn_begin()
788n/a
789n/a # delete the column list
790n/a self.db.delete(_columns_key(table), txn=txn)
791n/a
792n/a cur = self.db.cursor(txn)
793n/a
794n/a # delete all keys containing this tables column and row info
795n/a table_key = _search_all_data_key(table)
796n/a while 1:
797n/a try:
798n/a key, data = cur.set_range(table_key)
799n/a except db.DBNotFoundError:
800n/a break
801n/a # only delete items in this table
802n/a if key[:len(table_key)] != table_key:
803n/a break
804n/a cur.delete()
805n/a
806n/a # delete all rowids used by this table
807n/a table_key = _search_rowid_key(table)
808n/a while 1:
809n/a try:
810n/a key, data = cur.set_range(table_key)
811n/a except db.DBNotFoundError:
812n/a break
813n/a # only delete items in this table
814n/a if key[:len(table_key)] != table_key:
815n/a break
816n/a cur.delete()
817n/a
818n/a cur.close()
819n/a
820n/a # delete the tablename from the table name list
821n/a tablelist = pickle.loads(
822n/a getattr(self.db, "get_bytes", self.db.get)(_table_names_key,
823n/a txn=txn, flags=db.DB_RMW))
824n/a try:
825n/a tablelist.remove(table)
826n/a except ValueError:
827n/a # hmm, it wasn't there, oh well, that's what we want.
828n/a pass
829n/a # delete 1st, incase we opened with DB_DUP
830n/a self.db.delete(_table_names_key, txn=txn)
831n/a getattr(self.db, "put_bytes", self.db.put)(_table_names_key,
832n/a pickle.dumps(tablelist, 1), txn=txn)
833n/a
834n/a txn.commit()
835n/a txn = None
836n/a
837n/a if table in self.__tablecolumns:
838n/a del self.__tablecolumns[table]
839n/a
840n/a except db.DBError, dberror:
841n/a if txn:
842n/a txn.abort()
843n/a raise TableDBError(dberror.args[1])