[PATCH 4 of 4 saem] Backport fix for cubicweb ticket #17074119

Sylvain Thenault sylvain.thenault at logilab.fr
Wed May 10 15:24:40 CEST 2017


# HG changeset patch
# User Sylvain Thénault <sylvain.thenault at logilab.fr>
# Date 1492765175 -7200
#      Fri Apr 21 10:59:35 2017 +0200
# Node ID e22d0c70415f1cce1f2ce6c37081af4683c787ff
# Parent  777ac17597157e83b8a610790428dda50b2e9727
# Available At http://hg.logilab.org/review/cubes/saem_ref
#              hg pull http://hg.logilab.org/review/cubes/saem_ref -r e22d0c70415f
Backport fix for cubicweb ticket #17074119

We need this to fix a key error on edition of an authority record, because of
the constraint on 'agent_kind' relation which use several ORed EXISTS
expressions.

Add insert test reproducing the crash in relevant schema tests.

Closes extranet #18336413

diff --git a/cubicweb_saem_ref/site_cubicweb.py b/cubicweb_saem_ref/site_cubicweb.py
--- a/cubicweb_saem_ref/site_cubicweb.py
+++ b/cubicweb_saem_ref/site_cubicweb.py
@@ -305,5 +305,101 @@ def _call_view(self, view, paginate=Fals
     if extresources:
         stream.write(u'<div class="ajaxHtmlHead">\n')
         stream.write(extresources)
         stream.write(u'</div>\n')
     return stream.getvalue()
+
+
+# Fixed rql reqwrite (https://www.cubicweb.org/ticket/17074119)
+
+from cubicweb.rqlrewrite import RQLRewriter, n, remove_solutions  # noqa
+
+
+def need_exists(node):
+    """Return true if the given node should be wrapped in an `Exists` node.
+
+    This is true when node isn't already an `Exists` or `Not` node, nor a
+    `And`/`Or` of `Exists` or `Not` nodes.
+    """
+    if isinstance(node, (n.Exists, n.Not)):
+        return False
+    if isinstance(node, (n.Or, n.And)):
+        return need_exists(node.children[0]) or need_exists(node.children[1])
+    return True
+
+
+ at monkeypatch(RQLRewriter)
+def _inserted_root(self, new):
+    if need_exists(new):
+        new = n.Exists(new)
+    return new
+
+
+ at monkeypatch(RQLRewriter)
+def remove_ambiguities(self, snippets, newsolutions):
+    # the snippet has introduced some ambiguities, we have to resolve them
+    # "manually"
+    variantes = self.build_variantes(newsolutions)
+    if not variantes:
+        return newsolutions
+    # insert "is" where necessary
+    varexistsmap = {}
+    self.removing_ambiguity = True
+    for (erqlexpr, varmap, oldvarname), etype in variantes[0].items():
+        varname = self.rewritten[(erqlexpr, varmap, oldvarname)]
+        var = self.select.defined_vars[varname]
+        exists = var.references()[0].scope
+        exists.add_constant_restriction(var, 'is', etype, 'etype')
+        varexistsmap[varmap] = exists
+    # insert ORED exists where necessary
+    for variante in variantes[1:]:
+        self.insert_snippets(snippets, varexistsmap)
+        for key, etype in variante.items():
+            varname = self.rewritten[key]
+            try:
+                var = self.select.defined_vars[varname]
+            except KeyError:
+                # not a newly inserted variable
+                continue
+            exists = var.references()[0].scope
+            exists.add_constant_restriction(var, 'is', etype, 'etype')
+    # recompute solutions
+    self.compute_solutions()
+    # clean solutions according to initial solutions
+    return remove_solutions(self.solutions, self.select.solutions,
+                            self.select.defined_vars)
+
+
+ at monkeypatch(RQLRewriter)
+def build_variantes(self, newsolutions):
+    variantes = set()
+    for sol in newsolutions:
+        variante = []
+        for key, var_name in self.rewritten.items():
+            var = self.select.defined_vars[var_name]
+            # skip variable which are only in a NOT EXISTS (mustn't be splitted)
+            if len(var.stinfo['relations']) == 1 and isinstance(var.scope.parent, n.Not):
+                continue
+            # skip variable whose type is already explicitly specified
+            if var.stinfo['typerel']:
+                continue
+            variante.append((key, sol[var_name]))
+        if variante:
+            variantes.add(tuple(variante))
+
+    # rebuild variantes as dict
+    variantes = [dict(v) for v in variantes]
+    # remove variable which have always the same type
+    for key in self.rewritten:
+        it = iter(variantes)
+        try:
+            etype = next(it)[key]
+        except StopIteration:
+            continue
+        for variante in it:
+            if variante[key] != etype:
+                break
+        else:
+            for variante in variantes:
+                del variante[key]
+
+    return variantes
diff --git a/test/unittest_schema.py b/test/unittest_schema.py
--- a/test/unittest_schema.py
+++ b/test/unittest_schema.py
@@ -119,18 +119,25 @@ class SchemaConstraintsTC(CubicWebTC):
         with self.admin_access.repo_cnx() as cnx:
             arecord = testutils.authority_record(cnx, u'service', kind=u'authority')
             testutils.organization_unit(cnx, u'unit', authority_record=arecord)
             cnx.commit()
 
+            self.assertEqual(
+                [k.name for k in arecord.unrelated('agent_kind', 'AgentKind').entities()],
+                [])
+
             self.assertCantChangeRecordKind(arecord, u'person')
 
             org = testutils.authority_with_naa(cnx)
             org.cw_set(authority_record=arecord)
             cnx.commit()
             self.assertCantChangeRecordKind(arecord, u'person')
 
             org.cw_set(authority_record=None)
+            self.assertEqual(
+                {k.name for k in arecord.unrelated('agent_kind', 'AgentKind').entities()},
+                {u'unknown-agent-kind', u'family', u'person'})
             arecord.cw_set(agent_kind=cnx.find('AgentKind', name=u'person').one())
             cnx.commit()
 
             testutils.agent(cnx, u'bob', authority_record=arecord)
             cnx.commit()


More information about the saem-devel mailing list