diff roundup/backends/rdbms_common.py @ 6148:8497bf3f23a1

Allow to define reverse Multilinks Now it's possible to specify a rev_multilink parameter when creating Link or Multilink properties. The parameter takes a property name to be inserted into the linked-to class. It allows to navigate from the other side of the link as if it where a forward Multilink using the existing data structures.
author Ralf Schlatterbeck <rsc@runtux.com>
date Wed, 29 Apr 2020 16:30:27 +0200
parents e7cb0147e6fe
children ff059afae50a
line wrap: on
line diff
--- a/roundup/backends/rdbms_common.py	Sun Apr 12 21:03:55 2020 +0100
+++ b/roundup/backends/rdbms_common.py	Wed Apr 29 16:30:27 2020 +0200
@@ -293,6 +293,7 @@
             We should now confirm that the schema defined by our "classes"
             attribute actually matches the schema in the database.
         """
+        super(Database, self).post_init()
 
         # upgrade the database for column type changes, new internal
         # tables, etc.
@@ -512,6 +513,10 @@
         mls = []
         # add the multilinks separately
         for col, prop in properties:
+            # Computed props are not in the db
+            if prop.computed:
+                continue
+
             if isinstance(prop, Multilink):
                 mls.append(col)
                 continue
@@ -1128,8 +1133,11 @@
         """ evaluation of single Multilink (lazy eval may have skipped this)
         """
         if propname not in node:
-            sql = 'select linkid from %s_%s where nodeid=%s' % (
-                classname, propname, self.arg)
+            prop = self.getclass(classname).properties[propname]
+            tn  = prop.table_name
+            lid = prop.linkid_name
+            nid = prop.nodeid_name
+            sql = 'select %s from %s where %s=%s' % (lid, tn, nid, self.arg)
             self.sql(sql, (nodeid,))
             # extract the first column from the result
             # XXX numeric ids
@@ -1536,7 +1544,8 @@
         """ A dumpable version of the schema that we can store in the
             database
         """
-        return (self.key, [(x, repr(y)) for x, y in self.properties.items()])
+        return (self.key,
+          [(x, repr(y)) for x, y in self.properties.items() if not y.computed])
 
     def enableJournalling(self):
         """Turn journalling on for this class
@@ -1585,6 +1594,11 @@
             raise KeyError('"creator", "actor", "creation" and '
                            '"activity" are reserved')
 
+        for p in propvalues:
+            prop = self.properties[p]
+            if prop.computed:
+                raise KeyError('"%s" is a computed property'%p)
+
         # new node's id
         newid = self.db.newid(self.classname)
 
@@ -1814,6 +1828,11 @@
         if 'id' in propvalues:
             raise KeyError('"id" is reserved')
 
+        for p in propvalues:
+            prop = self.properties[p]
+            if prop.computed:
+                raise KeyError('"%s" is a computed property'%p)
+
         if self.db.journaltag is None:
             raise DatabaseError(_('Database open read-only'))
 
@@ -2312,13 +2331,13 @@
         ids = [str(x[0]) for x in self.db.cursor.fetchall()]
         return ids
 
-    def _subselect(self, classname, multilink_table):
+    def _subselect(self, classname, multilink_table, nodeid_name):
         """Create a subselect. This is factored out because some
            databases (hmm only one, so far) doesn't support subselects
            look for "I can't believe it's not a toy RDBMS" in the mysql
            backend.
         """
-        return '_%s.id not in (select nodeid from %s)'%(classname,
+        return '_%s.id not in (select %s from %s)'%(classname, nodeid_name,
             multilink_table)
 
     # Some DBs order NULL values last. Set this variable in the backend
@@ -2379,7 +2398,8 @@
         # we have ids of the classname table
         return ids.where("_%s.id" % classname, self.db.arg)
 
-    def _filter_multilink_expression(self, classname, multilink_table, v):
+    def _filter_multilink_expression(self, classname, multilink_table,
+        linkid_name, nodeid_name, v):
         """ Filters out elements of the classname table that do not
             match the given expression.
             Returns tuple of 'WHERE' introns for the overall filter.
@@ -2397,9 +2417,8 @@
                     classname, multilink_table, expr)
 
             atom = \
-                "%s IN(SELECT linkid FROM %s WHERE nodeid=a.id)" % (
-                self.db.arg,
-                multilink_table)
+                "%s IN(SELECT %s FROM %s WHERE %s=a.id)" % (
+                self.db.arg, linkid_name, multilink_table, nodeid_name)
 
             intron = \
                 "_%(classname)s.id in (SELECT id " \
@@ -2414,8 +2433,8 @@
             return intron, values
         except:
             # original behavior
-            where = "%s.linkid in (%s)" % (
-                multilink_table, ','.join([self.db.arg] * len(v)))
+            where = "%s.%s in (%s)" % (
+                multilink_table, linkid_name, ','.join([self.db.arg] * len(v)))
             return where, v, True # True to indicate original
 
     def _filter_sql (self, search_matches, filterspec, srt=[], grp=[], retr=0,
@@ -2479,34 +2498,36 @@
             if isinstance(propclass, Multilink):
                 if 'search' in p.need_for:
                     mlfilt = 1
-                    tn = '%s_%s'%(pcn, k)
+                    tn = propclass.table_name
+                    nid = propclass.nodeid_name
+                    lid = propclass.linkid_name
                     if v in ('-1', ['-1'], []):
                         # only match rows that have count(linkid)=0 in the
                         # corresponding multilink table)
-                        where.append(self._subselect(pcn, tn))
+                        where.append(self._subselect(pcn, tn, nid))
                     else:
                         frum.append(tn)
                         gen_join = True
 
                         if p.has_values and isinstance(v, type([])):
                             result = self._filter_multilink_expression(pln,
-                                tn, v)
+                                tn, lid, nid, v)
                             # XXX: We dont need an id join if we used the filter
                             gen_join = len(result) == 3
 
                         if gen_join:
-                            where.append('_%s.id=%s.nodeid'%(pln,tn))
+                            where.append('_%s.id=%s.%s'%(pln, tn, nid))
 
                         if p.children:
                             frum.append('_%s as _%s' % (cn, ln))
-                            where.append('%s.linkid=_%s.id'%(tn, ln))
+                            where.append('%s.%s=_%s.id'%(tn, lid, ln))
 
                         if p.has_values:
                             if isinstance(v, type([])):
                                 where.append(result[0])
                                 args += result[1]
                             else:
-                                where.append('%s.linkid=%s'%(tn, a))
+                                where.append('%s.%s=%s'%(tn, lid, a))
                                 args.append(v)
                 if 'sort' in p.need_for:
                     assert not p.attr_sort_done and not p.sort_ids_needed

Roundup Issue Tracker: http://roundup-tracker.org/