backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 09 Mar 2010 11:05:29 +0100
changeset 4845 dc351b96f596
parent 4844 ad78b118b124 (current diff)
parent 4842 3653e09024a1 (diff)
child 4847 9466604ef448
backport stable
hooks/syncschema.py
hooks/test/unittest_syncschema.py
server/__init__.py
server/hook.py
server/serverctl.py
server/session.py
server/sources/native.py
server/sources/rql2sql.py
server/test/unittest_rql2sql.py
--- a/entities/schemaobjs.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/entities/schemaobjs.py	Tue Mar 09 11:05:29 2010 +0100
@@ -56,30 +56,21 @@
             return u'%s <<%s>>' % (self.dc_title(), ', '.join(stereotypes))
         return self.dc_title()
 
-    def inlined_changed(self, inlined):
-        """check inlining is necessary and possible:
-
-        * return False if nothing has changed
-        * raise ValidationError if inlining is'nt possible
-        * eventually return True
+    def check_inlined_allowed(self):
+        """check inlining is possible, raise ValidationError if not possible
         """
-        rschema = self._cw.vreg.schema.rschema(self.name)
-        if inlined == rschema.inlined:
-            return False
-        if inlined:
-            # don't use the persistent schema, we may miss cardinality changes
-            # in the same transaction
-            for rdef in self.reverse_relation_type:
-                card = rdef.cardinality[0]
-                if not card in '?1':
-                    rtype = self.name
-                    stype = rdef.stype
-                    otype = rdef.otype
-                    msg = self._cw._("can't set inlined=%(inlined)s, "
-                                     "%(stype)s %(rtype)s %(otype)s "
-                                     "has cardinality=%(card)s")
-                    raise ValidationError(self.eid, {'inlined': msg % locals()})
-        return True
+        # don't use the persistent schema, we may miss cardinality changes
+        # in the same transaction
+        for rdef in self.reverse_relation_type:
+            card = rdef.cardinality[0]
+            if not card in '?1':
+                rtype = self.name
+                stype = rdef.stype
+                otype = rdef.otype
+                msg = self._cw._("can't set inlined=%(inlined)s, "
+                                 "%(stype)s %(rtype)s %(otype)s "
+                                 "has cardinality=%(card)s")
+                raise ValidationError(self.eid, {'inlined': msg % locals()})
 
     def db_key_name(self):
         """XXX goa specific"""
--- a/goa/goactl.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/goa/goactl.py	Tue Mar 09 11:05:29 2010 +0100
@@ -15,38 +15,40 @@
                                  create_dir)
 from cubicweb.cwconfig import CubicWebConfiguration
 
-from logilab import common as lgc
-from logilab import constraint as lgcstr
-from logilab import mtconverter as lgmtc
-import rql, yams, yapps, simplejson, docutils, roman
 
-SLINK_DIRECTORIES = [
-    (lgc.__path__[0], 'logilab/common'),
-    (lgmtc.__path__[0], 'logilab/mtconverter'),
-    (lgcstr.__path__[0], 'logilab/constraint'),
-    (rql.__path__[0], 'rql'),
-    (simplejson.__path__[0], 'simplejson'),
-    (yams.__path__[0], 'yams'),
-    (yapps.__path__[0], 'yapps'),
-    (docutils.__path__[0], 'docutils'),
-    (roman.__file__.replace('.pyc', '.py'), 'roman.py'),
+def slink_directories():
+    import rql, yams, yapps, simplejson, docutils, roman
+    from logilab import common as lgc
+    from logilab import constraint as lgcstr
+    from logilab import mtconverter as lgmtc
+    dirs = [
+        (lgc.__path__[0], 'logilab/common'),
+        (lgmtc.__path__[0], 'logilab/mtconverter'),
+        (lgcstr.__path__[0], 'logilab/constraint'),
+        (rql.__path__[0], 'rql'),
+        (simplejson.__path__[0], 'simplejson'),
+        (yams.__path__[0], 'yams'),
+        (yapps.__path__[0], 'yapps'),
+        (docutils.__path__[0], 'docutils'),
+        (roman.__file__.replace('.pyc', '.py'), 'roman.py'),
 
-    ('/usr/share/fckeditor/', 'fckeditor'),
+        ('/usr/share/fckeditor/', 'fckeditor'),
+
+        (join(CW_SOFTWARE_ROOT, 'web', 'data'), join('cubes', 'shared', 'data')),
+        (join(CW_SOFTWARE_ROOT, 'web', 'wdoc'), join('cubes', 'shared', 'wdoc')),
+        (join(CW_SOFTWARE_ROOT, 'i18n'), join('cubes', 'shared', 'i18n')),
+        (join(CW_SOFTWARE_ROOT, 'goa', 'tools'), 'tools'),
+        (join(CW_SOFTWARE_ROOT, 'goa', 'bin'), 'bin'),
+        ]
 
-    (join(CW_SOFTWARE_ROOT, 'web', 'data'), join('cubes', 'shared', 'data')),
-    (join(CW_SOFTWARE_ROOT, 'web', 'wdoc'), join('cubes', 'shared', 'wdoc')),
-    (join(CW_SOFTWARE_ROOT, 'i18n'), join('cubes', 'shared', 'i18n')),
-    (join(CW_SOFTWARE_ROOT, 'goa', 'tools'), 'tools'),
-    (join(CW_SOFTWARE_ROOT, 'goa', 'bin'), 'bin'),
-    ]
-
-try:
-    import dateutil
-    import vobject
-    SLINK_DIRECTORIES.extend([ (dateutil.__path__[0], 'dateutil'),
-                               (vobject.__path__[0], 'vobject') ] )
-except ImportError:
-    pass
+    try:
+        import dateutil
+        import vobject
+        dirs.extend([ (dateutil.__path__[0], 'dateutil'),
+                      (vobject.__path__[0], 'vobject') ] )
+    except ImportError:
+        pass
+    return dirs
 
 COPY_CW_FILES = (
     '__init__.py',
@@ -194,7 +196,7 @@
         copy_skeleton(join(CW_SOFTWARE_ROOT, 'goa', 'skel'),
                       appldir, context, askconfirm=True)
         # cubicweb core dependancies
-        for directory, subdirectory in SLINK_DIRECTORIES:
+        for directory, subdirectory in slink_directories():
             subdirectory = join(appldir, subdirectory)
             if not exists(split(subdirectory)[0]):
                 create_dir(split(subdirectory)[0])
--- a/hooks/syncschema.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/hooks/syncschema.py	Tue Mar 09 11:05:29 2010 +0100
@@ -227,25 +227,26 @@
 
 class SourceDbCWRTypeUpdate(hook.Operation):
     """actually update some properties of a relation definition"""
-    rschema = entity = None # make pylint happy
+    rschema = entity = values = None # make pylint happy
 
     def precommit_event(self):
+        rschema = self.rschema
+        if rschema.final:
+            return
         session = self.session
-        rschema = self.rschema
-        entity = self.entity
-        if 'fulltext_container' in entity.edited_attributes:
+        if 'fulltext_container' in self.values:
             ftiupdates = session.transaction_data.setdefault(
                 'fti_update_etypes', set())
             for subjtype, objtype in rschema.rdefs:
                 ftiupdates.add(subjtype)
                 ftiupdates.add(objtype)
             UpdateFTIndexOp(session)
-        if rschema.final or not 'inlined' in entity.edited_attributes:
+        if not 'inlined' in self.values:
             return # nothing to do
-        inlined = entity.inlined
+        inlined = self.values['inlined']
         # check in-lining is necessary / possible
-        if not entity.inlined_changed(inlined):
-            return # nothing to do
+        if inlined:
+            self.entity.check_inlined_allowed()
         # inlined changed, make necessary physical changes!
         sqlexec = self.session.system_sql
         rtype = rschema.type
@@ -934,26 +935,22 @@
 
 class BeforeUpdateCWRTypeHook(DelCWRTypeHook):
     """check name change, handle final"""
-    __regid__ = 'checkupdatecwrtype'
+    __regid__ = 'syncupdatecwrtype'
     events = ('before_update_entity',)
 
     def __call__(self):
-        check_valid_changes(self._cw, self.entity)
-
-
-class AfterUpdateCWRTypeHook(DelCWRTypeHook):
-    __regid__ = 'syncupdatecwrtype'
-    events = ('after_update_entity',)
-
-    def __call__(self):
         entity = self.entity
+        check_valid_changes(self._cw, entity)
         newvalues = {}
         for prop in ('symmetric', 'inlined', 'fulltext_container'):
             if prop in entity.edited_attributes:
-                newvalues[prop] = entity[prop]
+                old, new = hook.entity_oldnewvalue(entity, prop)
+                if old != new:
+                    newvalues[prop] = entity[prop]
         if newvalues:
             rschema = self._cw.vreg.schema.rschema(entity.name)
-            SourceDbCWRTypeUpdate(self._cw, rschema=rschema, entity=entity)
+            SourceDbCWRTypeUpdate(self._cw, rschema=rschema, entity=entity,
+                                  values=newvalues)
             MemSchemaCWRTypeUpdate(self._cw, rschema=rschema, values=newvalues)
 
 
@@ -1033,8 +1030,8 @@
 class AfterUpdateCWRDefHook(SyncSchemaHook):
     __regid__ = 'syncaddcwattribute'
     __select__ = SyncSchemaHook.__select__ & implements('CWAttribute',
-                                                               'CWRelation')
-    events = ('after_update_entity',)
+                                                        'CWRelation')
+    events = ('before_update_entity',)
 
     def __call__(self):
         entity = self.entity
@@ -1049,7 +1046,9 @@
             if prop == 'order':
                 prop = 'ordernum'
             if prop in entity.edited_attributes:
-                newvalues[prop] = entity[prop]
+                old, new = hook.entity_oldnewvalue(entity, prop)
+                if old != new:
+                    newvalues[prop] = entity[prop]
         if newvalues:
             subjtype = entity.stype.name
             MemSchemaRDefUpdate(self._cw, kobj=(subjtype, desttype),
--- a/hooks/test/unittest_syncschema.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/hooks/test/unittest_syncschema.py	Tue Mar 09 11:05:29 2010 +0100
@@ -266,7 +266,7 @@
                             'WHERE E is CWEType, E name "Email", A is CWAttribute,'
                             'A from_entity E, A relation_type R, R name "subject"')
         self.commit()
-        rset = req.execute('Any X Where X has_text "rick.roll"')
+        rset = req.execute('Any X WHERE X has_text "rick.roll"')
         self.failIf(rset)
         assert req.execute('SET A fulltextindexed TRUE '
                            'WHERE A from_entity E, A relation_type R, '
@@ -285,7 +285,7 @@
         assert self.execute('SET R fulltext_container NULL '
                             'WHERE R name "use_email"')
         self.commit()
-        rset = self.execute('Any X Where X has_text "rick.roll"')
+        rset = self.execute('Any X WHERE X has_text "rick.roll"')
         self.assertIn(target.eid, [item[0] for item in rset])
         assert self.execute('SET R fulltext_container "subject" '
                             'WHERE R name "use_email"')
--- a/server/hook.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/server/hook.py	Tue Mar 09 11:05:29 2010 +0100
@@ -95,16 +95,16 @@
 
 VRegistry.REGISTRY_FACTORY['hooks'] = HooksRegistry
 
-
+_MARKER = object()
 def entity_oldnewvalue(entity, attr):
     """returns the couple (old attr value, new attr value)
     NOTE: will only work in a before_update_entity hook
     """
     # get new value and remove from local dict to force a db query to
     # fetch old value
-    newvalue = entity.pop(attr, None)
+    newvalue = entity.pop(attr, _MARKER)
     oldvalue = getattr(entity, attr)
-    if newvalue is not None:
+    if newvalue is not _MARKER:
         entity[attr] = newvalue
     return oldvalue, newvalue
 
--- a/server/hookhelper.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/server/hookhelper.py	Tue Mar 09 11:05:29 2010 +0100
@@ -13,7 +13,6 @@
 
 @deprecated('[3.6] entity_oldnewvalue should be imported from cw.server.hook')
 def entity_oldnewvalue(entity, attr):
-    """return the "name" attribute of the entity with the given eid"""
     return hook.entity_oldnewvalue(entity, attr)
 
 @deprecated('[3.6] entity_name is deprecated, use entity.name')
--- a/server/serverctl.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/server/serverctl.py	Tue Mar 09 11:05:29 2010 +0100
@@ -66,7 +66,13 @@
     cnx = get_connection(driver, dbhost, dbname, user, password=password,
                          port=source.get('db-port'),
                          **extra)
-    cnx.logged_user = logged_user
+    try:
+        cnx.logged_user = user
+    except AttributeError:
+        # C object, __slots__
+        from logilab.db import _SimpleConnectionWrapper
+        cnx = _SimpleConnectionWrapper(cnx)
+        cnx.logged_user = user
     return cnx
 
 def system_source_cnx(source, dbms_system_base=False,
--- a/server/session.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/server/session.py	Tue Mar 09 11:05:29 2010 +0100
@@ -280,9 +280,15 @@
         self.set_language(value)
 
     def deleted_in_transaction(self, eid):
+        """return True if the entity of the given eid is being deleted in the
+        current transaction
+        """
         return eid in self.transaction_data.get('pendingeids', ())
 
     def added_in_transaction(self, eid):
+        """return True if the entity of the given eid is being created in the
+        current transaction
+        """
         return eid in self.transaction_data.get('neweids', ())
 
     def schema_rproperty(self, rtype, eidfrom, eidto, rprop):
--- a/server/sources/rql2sql.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/server/sources/rql2sql.py	Tue Mar 09 11:05:29 2010 +0100
@@ -341,6 +341,9 @@
                             }
         if not self.dbms_helper.union_parentheses_support:
             self.union_sql = self.noparen_union_sql
+        if self.dbms_helper.fti_need_distinct:
+            self.__union_sql = self.union_sql
+            self.union_sql = self.has_text_need_distinct_union_sql
         self._lock = threading.Lock()
         if attrmap is None:
             attrmap = {}
@@ -374,6 +377,12 @@
         finally:
             self._lock.release()
 
+    def has_text_need_distinct_union_sql(self, union, needalias=False):
+        if getattr(union, 'has_text_query', False):
+            for select in union.children:
+                select.need_distinct = True
+        return self.__union_sql(union, needalias)
+
     def union_sql(self, union, needalias=False): # pylint: disable-msg=E0202
         if len(union.children) == 1:
             return self.select_sql(union.children[0], needalias)
--- a/server/test/unittest_rql2sql.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/server/test/unittest_rql2sql.py	Tue Mar 09 11:05:29 2010 +0100
@@ -1486,26 +1486,26 @@
     def test_has_text(self):
         for t in self._parse((
             ('Any X WHERE X has_text "toto tata"',
-             """SELECT appears0.uid
+             """SELECT DISTINCT appears0.uid
 FROM appears AS appears0
 WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata'))"""),
 
             ('Any X WHERE X has_text %(text)s',
-             """SELECT appears0.uid
+             """SELECT DISTINCT appears0.uid
 FROM appears AS appears0
 WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('hip', 'hop', 'momo'))"""),
 
             ('Personne X WHERE X has_text "toto tata"',
-             """SELECT _X.eid
+             """SELECT DISTINCT _X.eid
 FROM appears AS appears0, entities AS _X
 WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=_X.eid AND _X.type='Personne'"""),
 
             ('Any X WHERE X has_text "toto tata", X name "tutu", X is IN (Basket,Folder)',
-             """SELECT _X.cw_eid
+             """SELECT DISTINCT _X.cw_eid
 FROM appears AS appears0, cw_Basket AS _X
 WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=_X.cw_eid AND _X.cw_name=tutu
-UNION ALL
-SELECT _X.cw_eid
+UNION
+SELECT DISTINCT _X.cw_eid
 FROM appears AS appears0, cw_Folder AS _X
 WHERE appears0.word_id IN (SELECT word_id FROM word WHERE word in ('toto', 'tata')) AND appears0.uid=_X.cw_eid AND _X.cw_name=tutu
 """),
--- a/sobjects/test/unittest_supervising.py	Tue Mar 09 11:01:44 2010 +0100
+++ b/sobjects/test/unittest_supervising.py	Tue Mar 09 11:05:29 2010 +0100
@@ -27,7 +27,6 @@
 
 
     def test_supervision(self):
-        session = self.session
         # do some modification
         user = self.execute('INSERT CWUser X: X login "toto", X upassword "sosafe", X in_group G '
                             'WHERE G name "users"').get_entity(0, 0)
@@ -37,6 +36,7 @@
         self.execute('SET X content "duh?" WHERE X is Comment')
         self.execute('DELETE X comments Y WHERE Y is Card, Y title "une autre news !"')
         # check only one supervision email operation
+        session = self.session
         sentops = [op for op in session.pending_operations
                    if isinstance(op, SupervisionMailOp)]
         self.assertEquals(len(sentops), 1)