Code

Multilinks can be filtered by combining elements with AND, OR and NOT
[roundup.git] / roundup / backends / back_anydbm.py
index 55c5456c8511ee57dabb8d4764b4c434e11998c8..4ea2a2dcdccdc5ad160733de44e8b47b9289327b 100644 (file)
@@ -49,6 +49,87 @@ def db_exists(config):
 def db_nuke(config):
     shutil.rmtree(config.DATABASE)
 
+class Binary:
+
+    def __init__(self, x, y):
+        self.x = x
+        self.y = y
+
+    def visit(self, visitor):
+        self.x.visit(visitor)
+        self.y.visit(visitor)
+
+class Unary:
+
+    def __init__(self, x):
+        self.x = x
+
+    def generate(self, atom):
+        return atom(self)
+
+    def visit(self, visitor):
+        self.x.visit(visitor)
+
+class Equals(Unary):
+
+    def evaluate(self, v):
+        return self.x in v
+
+    def visit(self, visitor):
+        visitor(self)
+
+class Not(Unary):
+
+    def evaluate(self, v):
+        return not self.x.evaluate(v)
+
+    def generate(self, atom):
+        return "NOT(%s)" % self.x.generate(atom)
+
+class Or(Binary):
+
+    def evaluate(self, v):
+        return self.x.evaluate(v) or self.y.evaluate(v)
+
+    def generate(self, atom):
+        return "(%s)OR(%s)" % (
+            self.x.generate(atom),
+            self.y.generate(atom))
+
+class And(Binary):
+
+    def evaluate(self, v):
+        return self.x.evaluate(v) and self.y.evaluate(v)
+
+    def generate(self, atom):
+        return "(%s)AND(%s)" % (
+            self.x.generate(atom),
+            self.y.generate(atom))
+
+def compile_expression(opcodes):
+
+    stack = []
+    push, pop = stack.append, stack.pop
+    for opcode in opcodes:
+        if   opcode == -2: push(Not(pop()))
+        elif opcode == -3: push(And(pop(), pop()))
+        elif opcode == -4: push(Or(pop(), pop()))
+        else:              push(Equals(opcode))
+
+    return pop()
+
+class Expression:
+
+    def __init__(self, v):
+        try:
+            opcodes = [int(x) for x in v]
+            if min(opcodes) >= -1: raise ValueError()
+
+            compiled = compile_expression(opcodes)
+            self.evaluate = lambda x: compiled.evaluate([int(y) for y in x])
+        except:
+            self.evaluate = lambda x: bool(set(x) & set(v))
+
 #
 # Now the database
 #
@@ -158,7 +239,9 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
 
     def getclasses(self):
         """Return a list of the names of all existing classes."""
-        return sorted(self.classes)
+        l = self.classes.keys()
+        l.sort()
+        return l
 
     def getclass(self, classname):
         """Get the Class object representing a particular class.
@@ -224,7 +307,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
         # whichdb() function to do this
         if not db_type or hasattr(anydbm, 'whichdb'):
             if __debug__:
-                logging.getLogger('hyperdb').debug(
+                logging.getLogger('roundup.hyperdb').debug(
                     "opendb anydbm.open(%r, 'c')"%path)
             return anydbm.open(path, 'c')
 
@@ -236,7 +319,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
             raise hyperdb.DatabaseError(_("Couldn't open database - the "
                 "required module '%s' is not available")%db_type)
         if __debug__:
-            logging.getLogger('hyperdb').debug(
+            logging.getLogger('roundup.hyperdb').debug(
                 "opendb %r.open(%r, %r)"%(db_type, path, mode))
         return dbm.open(path, mode)
 
@@ -297,7 +380,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
         """ perform the saving of data specified by the set/addnode
         """
         if __debug__:
-            logging.getLogger('hyperdb').debug('save %s%s %r'%(classname, nodeid, node))
+            logging.getLogger('roundup.hyperdb').debug(
+                'save %s%s %r'%(classname, nodeid, node))
         self.transactions.append((self.doSaveNode, (classname, nodeid, node)))
 
     def getnode(self, classname, nodeid, db=None, cache=1):
@@ -310,14 +394,16 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
         cache_dict = self.cache.setdefault(classname, {})
         if nodeid in cache_dict:
             if __debug__:
-                logging.getLogger('hyperdb').debug('get %s%s cached'%(classname, nodeid))
+                logging.getLogger('roundup.hyperdb').debug(
+                    'get %s%s cached'%(classname, nodeid))
                 self.stats['cache_hits'] += 1
             return cache_dict[nodeid]
 
         if __debug__:
             self.stats['cache_misses'] += 1
             start_t = time.time()
-            logging.getLogger('hyperdb').debug('get %s%s'%(classname, nodeid))
+            logging.getLogger('roundup.hyperdb').debug(
+                'get %s%s'%(classname, nodeid))
 
         # get from the database and save in the cache
         if db is None:
@@ -349,7 +435,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
         """Remove a node from the database. Called exclusively by the
            destroy() method on Class.
         """
-        logging.getLogger('hyperdb').info('destroy %s%s'%(classname, nodeid))
+        logging.getLogger('roundup.hyperdb').info(
+            'destroy %s%s'%(classname, nodeid))
 
         # remove from cache and newnodes if it's there
         if (classname in self.cache and nodeid in self.cache[classname]):
@@ -472,7 +559,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
             the current user.
         """
         if __debug__:
-            logging.getLogger('hyperdb').debug('addjournal %s%s %s %r %s %r'%(classname,
+            logging.getLogger('roundup.hyperdb').debug(
+                'addjournal %s%s %s %r %s %r'%(classname,
                 nodeid, action, params, creator, creation))
         if creator is None:
             creator = self.getuid()
@@ -482,8 +570,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
     def setjournal(self, classname, nodeid, journal):
         """Set the journal to the "journal" list."""
         if __debug__:
-            logging.getLogger('hyperdb').debug('setjournal %s%s %r'%(classname,
-                nodeid, journal))
+            logging.getLogger('roundup.hyperdb').debug(
+                'setjournal %s%s %r'%(classname, nodeid, journal))
         self.transactions.append((self.doSetJournal, (classname, nodeid,
             journal)))
 
@@ -569,8 +657,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
                         packed += 1
                 db[key] = marshal.dumps(l)
 
-                logging.getLogger('hyperdb').info('packed %d %s items'%(packed,
-                    classname))
+                logging.getLogger('roundup.hyperdb').info(
+                    'packed %d %s items'%(packed, classname))
 
             if db_type == 'gdbm':
                 db.reorganize()
@@ -592,7 +680,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
 
         The only backend this seems to affect is postgres.
         """
-        logging.getLogger('hyperdb').info('commit %s transactions'%(
+        logging.getLogger('roundup.hyperdb').info('commit %s transactions'%(
             len(self.transactions)))
 
         # keep a handle to all the database files opened
@@ -715,7 +803,7 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
     def rollback(self):
         """ Reverse all actions from the current transaction.
         """
-        logging.getLogger('hyperdb').info('rollback %s transactions'%(
+        logging.getLogger('roundup.hyperdb').info('rollback %s transactions'%(
             len(self.transactions)))
 
         for method, args in self.transactions:
@@ -1695,12 +1783,10 @@ class Class(hyperdb.Class):
                         if not v:
                             match = not nv
                         else:
-                            # othewise, make sure this node has each of the
+                            # otherwise, make sure this node has each of the
                             # required values
-                            for want in v:
-                                if want in nv:
-                                    match = 1
-                                    break
+                            expr = Expression(v)
+                            if expr.evaluate(nv): match = 1
                     elif t == STRING:
                         if nv is None:
                             nv = ''
@@ -1995,34 +2081,6 @@ class Class(hyperdb.Class):
                     repr(action), repr(params)])
         return r
 
-    def import_journals(self, entries):
-        """Import a class's journal.
-
-        Uses setjournal() to set the journal for each item."""
-        properties = self.getprops()
-        d = {}
-        for l in entries:
-            nodeid, jdate, user, action, params = tuple(map(eval, l))
-            r = d.setdefault(nodeid, [])
-            if action == 'set':
-                for propname, value in params.iteritems():
-                    prop = properties[propname]
-                    if value is None:
-                        pass
-                    elif isinstance(prop, hyperdb.Date):
-                        value = date.Date(value)
-                    elif isinstance(prop, hyperdb.Interval):
-                        value = date.Interval(value)
-                    elif isinstance(prop, hyperdb.Password):
-                        pwd = password.Password()
-                        pwd.unpack(value)
-                        value = pwd
-                    params[propname] = value
-            r.append((nodeid, date.Date(jdate), user, action, params))
-
-        for nodeid, l in d.iteritems():
-            self.db.setjournal(self.classname, nodeid, l)
-
 class FileClass(hyperdb.FileClass, Class):
     """This class defines a large chunk of data. To support this, it has a
        mandatory String property "content" which is typically saved off