backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 08 Mar 2010 09:51:29 +0100
changeset 4829 3b79a0fc91db
parent 4823 e833520870ee (diff)
parent 4828 3e173d598cad (current diff)
child 4830 10e8bc190695
child 4831 c5aec27c1bf7
backport stable
hooks/syncschema.py
hooks/test/unittest_syncschema.py
server/sources/native.py
--- a/dbapi.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/dbapi.py	Mon Mar 08 09:51:29 2010 +0100
@@ -398,14 +398,20 @@
 
     def check(self):
         """raise `BadSessionId` if the connection is no more valid"""
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         self._repo.check_session(self.sessionid)
 
     def set_session_props(self, **props):
         """raise `BadSessionId` if the connection is no more valid"""
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         self._repo.set_session_props(self.sessionid, props)
 
     def get_shared_data(self, key, default=None, pop=False):
         """return value associated to `key` in shared data"""
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         return self._repo.get_shared_data(self.sessionid, key, default, pop)
 
     def set_shared_data(self, key, value, querydata=False):
@@ -416,6 +422,8 @@
         transaction, and won't be available through the connexion, only on the
         repository side.
         """
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         return self._repo.set_shared_data(self.sessionid, key, value, querydata)
 
     def get_schema(self):
@@ -501,6 +509,8 @@
     def user(self, req=None, props=None):
         """return the User object associated to this connection"""
         # cnx validity is checked by the call to .user_info
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         eid, login, groups, properties = self._repo.user_info(self.sessionid,
                                                               props)
         if req is None:
@@ -521,6 +531,8 @@
                 pass
 
     def describe(self, eid):
+        if self._closed is not None:
+            raise ProgrammingError('Closed connection')
         return self._repo.describe(self.sessionid, eid)
 
     def close(self):
@@ -535,6 +547,7 @@
         if self._closed:
             raise ProgrammingError('Connection is already closed')
         self._repo.close(self.sessionid)
+        del self._repo # necessary for proper garbage collection
         self._closed = 1
 
     def commit(self):
--- a/devtools/repotest.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/devtools/repotest.py	Mon Mar 08 09:51:29 2010 +0100
@@ -95,6 +95,31 @@
     def __iter__(self):
         return iter(sorted(self.origdict, key=self.sortkey))
 
+def schema_eids_idx(schema):
+    """return a dictionary mapping schema types to their eids so we can reread
+    it from the fs instead of the db (too costly) between tests
+    """
+    schema_eids = {}
+    for x in schema.entities():
+        schema_eids[x] = x.eid
+    for x in schema.relations():
+        schema_eids[x] = x.eid
+        for rdef in x.rdefs.itervalues():
+            schema_eids[(rdef.subject, rdef.rtype, rdef.object)] = rdef.eid
+    return schema_eids
+
+def restore_schema_eids_idx(schema, schema_eids):
+    """rebuild schema eid index"""
+    for x in schema.entities():
+        x.eid = schema_eids[x]
+        schema._eid_index[x.eid] = x
+    for x in schema.relations():
+        x.eid = schema_eids[x]
+        schema._eid_index[x.eid] = x
+        for rdef in x.rdefs.itervalues():
+            rdef.eid = schema_eids[(rdef.subject, rdef.rtype, rdef.object)]
+            schema._eid_index[rdef.eid] = rdef
+
 
 from logilab.common.testlib import TestCase
 from rql import RQLHelper
@@ -150,6 +175,7 @@
         self.pool = self.session.set_pool()
         self.maxeid = self.get_max_eid()
         do_monkey_patch()
+        self._dumb_sessions = []
 
     def get_max_eid(self):
         return self.session.unsafe_execute('Any MAX(X)')[0][0]
@@ -161,6 +187,10 @@
         self.session.rollback()
         self.cleanup()
         self.commit()
+        # properly close dumb sessions
+        for session in self._dumb_sessions:
+            session.rollback()
+            session.close()
         self.repo._free_pool(self.pool)
         assert self.session.user.eid != -1
 
@@ -198,6 +228,8 @@
         u._groups = set(groups)
         s = Session(u, self.repo)
         s._threaddata.pool = self.pool
+        # register session to ensure it gets closed
+        self._dumb_sessions.append(s)
         return s
 
     def execute(self, rql, args=None, eid_key=None, build_descr=True):
@@ -223,6 +255,7 @@
         self.sources = self.o._repo.sources
         self.system = self.sources[-1]
         do_monkey_patch()
+        self._dumb_sessions = [] # by hi-jacked parent setup
 
     def add_source(self, sourcecls, uri):
         self.sources.append(sourcecls(self.repo, self.o.schema,
@@ -237,6 +270,9 @@
             del self.repo.sources_by_uri[source.uri]
             self.newsources -= 1
         undo_monkey_patch()
+        for session in self._dumb_sessions:
+            session._threaddata.pool = None
+            session.close()
 
     def _prepare_plan(self, rql, kwargs=None):
         rqlst = self.o.parse(rql, annotate=True)
--- a/devtools/test/unittest_testlib.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/devtools/test/unittest_testlib.py	Mon Mar 08 09:51:29 2010 +0100
@@ -9,12 +9,12 @@
 from cStringIO import StringIO
 from unittest import TestSuite
 
-
-from logilab.common.testlib import (TestCase, unittest_main, 
+from logilab.common.testlib import (TestCase, unittest_main,
                                     SkipAwareTextTestRunner)
 
 from cubicweb.devtools import htmlparser
 from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.pytestconf import clean_repo_test_cls
 
 class WebTestTC(TestCase):
 
@@ -37,7 +37,7 @@
         self.assertEquals(result.testsRun, 2)
         self.assertEquals(len(result.errors), 0)
         self.assertEquals(len(result.failures), 1)
-
+        clean_repo_test_cls(MyWebTest)
 
 
 HTML_PAGE = u"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
--- a/etwist/server.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/etwist/server.py	Mon Mar 08 09:51:29 2010 +0100
@@ -350,32 +350,131 @@
 set_log_methods(CubicWebRootResource, getLogger('cubicweb.twisted'))
 
 
+listiterator = type(iter([]))
 
-def _gc_debug():
+def _gc_debug(all=True):
     import gc
     from pprint import pprint
     from cubicweb.appobject import AppObject
     gc.collect()
     count = 0
     acount = 0
+    fcount = 0
+    rcount = 0
+    ccount = 0
+    scount = 0
     ocount = {}
+    from rql.stmts import Union
+    from cubicweb.schema import CubicWebSchema
+    from cubicweb.rset import ResultSet
+    from cubicweb.dbapi import Connection, Cursor
+    from cubicweb.req import RequestSessionBase
+    from cubicweb.server.repository import Repository
+    from cubicweb.server.sources.native import NativeSQLSource
+    from cubicweb.server.session import Session
+    from cubicweb.devtools.testlib import CubicWebTC
+    from logilab.common.testlib import TestSuite
+    from optparse import Values
+    import types, weakref
     for obj in gc.get_objects():
-        if isinstance(obj, CubicWebTwistedRequestAdapter):
+        if isinstance(obj, RequestSessionBase):
             count += 1
+            if isinstance(obj, Session):
+                print '   session', obj, referrers(obj, True)
         elif isinstance(obj, AppObject):
             acount += 1
-        else:
+        elif isinstance(obj, ResultSet):
+            rcount += 1
+            #print '   rset', obj, referrers(obj)
+        elif isinstance(obj, Repository):
+            print '   REPO', obj, referrers(obj, True)
+        #elif isinstance(obj, NativeSQLSource):
+        #    print '   SOURCe', obj, referrers(obj)
+        elif isinstance(obj, CubicWebTC):
+            print '   TC', obj, referrers(obj)
+        elif isinstance(obj, TestSuite):
+            print '   SUITE', obj, referrers(obj)
+        #elif isinstance(obj, Values):
+        #    print '   values', '%#x' % id(obj), referrers(obj, True)
+        elif isinstance(obj, Connection):
+            ccount += 1
+            #print '   cnx', obj, referrers(obj)
+        #elif isinstance(obj, Cursor):
+        #    ccount += 1
+        #    print '   cursor', obj, referrers(obj)
+        elif isinstance(obj, file):
+            fcount += 1
+        #    print '   open file', file.name, file.fileno
+        elif isinstance(obj, CubicWebSchema):
+            scount += 1
+            print '   schema', obj, referrers(obj)
+        elif not isinstance(obj, (type, tuple, dict, list, set, frozenset,
+                                  weakref.ref, weakref.WeakKeyDictionary,
+                                  listiterator,
+                                  property, classmethod,
+                                  types.ModuleType, types.MemberDescriptorType,
+                                  types.FunctionType, types.MethodType)):
             try:
                 ocount[obj.__class__] += 1
             except KeyError:
                 ocount[obj.__class__] = 1
             except AttributeError:
                 pass
-    print 'IN MEM REQUESTS', count
-    print 'IN MEM APPOBJECTS', acount
-    ocount = sorted(ocount.items(), key=lambda x: x[1], reverse=True)[:20]
-    pprint(ocount)
-    print 'UNREACHABLE', gc.garbage
+    if count:
+        print ' NB REQUESTS/SESSIONS', count
+    if acount:
+        print ' NB APPOBJECTS', acount
+    if ccount:
+        print ' NB CONNECTIONS', ccount
+    if rcount:
+        print ' NB RSETS', rcount
+    if scount:
+        print ' NB SCHEMAS', scount
+    if fcount:
+        print ' NB FILES', fcount
+    if all:
+        ocount = sorted(ocount.items(), key=lambda x: x[1], reverse=True)[:20]
+        pprint(ocount)
+    if gc.garbage:
+        print 'UNREACHABLE', gc.garbage
+
+def referrers(obj, showobj=False):
+    try:
+        return sorted(set((type(x), showobj and x or getattr(x, '__name__', '%#x' % id(x)))
+                          for x in _referrers(obj)))
+    except TypeError:
+        s = set()
+        unhashable = []
+        for x in _referrers(obj):
+            try:
+                s.add(x)
+            except TypeError:
+                unhashable.append(x)
+        return sorted(s) + unhashable
+
+def _referrers(obj, seen=None, level=0):
+    import gc, types
+    from cubicweb.schema import CubicWebRelationSchema, CubicWebEntitySchema
+    interesting = []
+    if seen is None:
+        seen = set()
+    for x in gc.get_referrers(obj):
+        if id(x) in seen:
+            continue
+        seen.add(id(x))
+        if isinstance(x, types.FrameType):
+            continue
+        if isinstance(x, (CubicWebRelationSchema, CubicWebEntitySchema)):
+            continue
+        if isinstance(x, (list, tuple, set, dict, listiterator)):
+            if level >= 5:
+                pass
+                #interesting.append(x)
+            else:
+                interesting += _referrers(x, seen, level+1)
+        else:
+            interesting.append(x)
+    return interesting
 
 def run(config, debug):
     # create the site
--- a/ext/html4zope.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/ext/html4zope.py	Mon Mar 08 09:51:29 2010 +0100
@@ -24,12 +24,13 @@
 
 __docformat__ = 'reStructuredText'
 
+import os
+
 from logilab.mtconverter import xml_escape
 
 from docutils import nodes
 from docutils.writers.html4css1 import Writer as CSS1Writer
 from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
-import os
 
 default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
 
--- a/ext/rest.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/ext/rest.py	Mon Mar 08 09:51:29 2010 +0100
@@ -25,7 +25,7 @@
 from os.path import join
 
 from docutils import statemachine, nodes, utils, io
-from docutils.core import publish_string
+from docutils.core import Publisher
 from docutils.parsers.rst import Parser, states, directives
 from docutils.parsers.rst.roles import register_canonical_role, set_classes
 
@@ -92,14 +92,15 @@
     in `docutils.parsers.rst.directives.misc`
     """
     context = state.document.settings.context
+    cw = context._cw
     source = state_machine.input_lines.source(
         lineno - state_machine.input_offset - 1)
     #source_dir = os.path.dirname(os.path.abspath(source))
     fid = arguments[0]
-    for lang in chain((context._cw.lang, context.vreg.property_value('ui.language')),
-                      context.config.available_languages()):
+    for lang in chain((cw.lang, cw.vreg.property_value('ui.language')),
+                      cw.vreg.config.available_languages()):
         rid = '%s_%s.rst' % (fid, lang)
-        resourcedir = context.config.locate_doc_file(rid)
+        resourcedir = cw.vreg.config.locate_doc_file(rid)
         if resourcedir:
             break
     else:
@@ -196,6 +197,15 @@
         self.finish_parse()
 
 
+# XXX docutils keep a ref on context, can't find a correct way to remove it
+class CWReSTPublisher(Publisher):
+    def __init__(self, context, settings, **kwargs):
+        Publisher.__init__(self, **kwargs)
+        self.set_components('standalone', 'restructuredtext', 'pseudoxml')
+        self.process_programmatic_settings(None, settings, None)
+        self.settings.context = context
+
+
 def rest_publish(context, data):
     """publish a string formatted as ReStructured Text to HTML
 
@@ -218,7 +228,7 @@
         # remove unprintable characters unauthorized in xml
         data = data.translate(ESC_CAR_TABLE)
     settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
-                'warning_stream': StringIO(), 'context': context,
+                'warning_stream': StringIO(),
                 # dunno what's the max, severe is 4, and we never want a crash
                 # (though try/except may be a better option...)
                 'halt_level': 10,
@@ -233,9 +243,17 @@
     else:
         base_url = None
     try:
-        return publish_string(writer=Writer(base_url=base_url),
-                              parser=CubicWebReSTParser(), source=data,
-                              settings_overrides=settings)
+        pub = CWReSTPublisher(context, settings,
+                              parser=CubicWebReSTParser(),
+                              writer=Writer(base_url=base_url),
+                              source_class=io.StringInput,
+                              destination_class=io.StringOutput)
+        pub.set_source(data)
+        pub.set_destination()
+        res = pub.publish(enable_exit_status=None)
+        # necessary for proper garbage collection, else a ref is kept somewhere in docutils...
+        del pub.settings.context
+        return res
     except Exception:
         LOGGER.exception('error while publishing ReST text')
         if not isinstance(data, unicode):
--- a/hooks/syncschema.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/hooks/syncschema.py	Mon Mar 08 09:51:29 2010 +0100
@@ -12,11 +12,12 @@
 """
 __docformat__ = "restructuredtext en"
 
+from copy import copy
 from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
-from yams.buildobjs import EntityType, RelationType, RelationDefinition
-from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
+from yams import buildobjs as ybo, schema2sql as y2sql
 
 from logilab.common.decorators import clear_cache
+from logilab.common.testlib import mock_object
 
 from cubicweb import ValidationError
 from cubicweb.selectors import implements
@@ -253,7 +254,7 @@
             # need to create the relation if it has not been already done by
             # another event of the same transaction
             if not rschema.type in session.transaction_data.get('createdtables', ()):
-                tablesql = rschema2sql(rschema)
+                tablesql = y2sql.rschema2sql(rschema)
                 # create the necessary table
                 for sql in tablesql.split(';'):
                     if sql.strip():
@@ -321,13 +322,13 @@
         rtype = entity.rtype.name
         obj = str(entity.otype.name)
         constraints = get_constraints(self.session, entity)
-        rdef = RelationDefinition(subj, rtype, obj,
-                                  description=entity.description,
-                                  cardinality=entity.cardinality,
-                                  constraints=constraints,
-                                  order=entity.ordernum,
-                                  eid=entity.eid,
-                                  **kwargs)
+        rdef = ybo.RelationDefinition(subj, rtype, obj,
+                                      description=entity.description,
+                                      cardinality=entity.cardinality,
+                                      constraints=constraints,
+                                      order=entity.ordernum,
+                                      eid=entity.eid,
+                                      **kwargs)
         MemSchemaRDefAdd(self.session, rdef)
         return rdef
 
@@ -345,8 +346,8 @@
                  'internationalizable': entity.internationalizable}
         rdef = self.init_rdef(**props)
         sysource = session.pool.source('system')
-        attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
-                                         rdef.constraints)
+        attrtype = y2sql.type_from_constraints(
+            sysource.dbhelper, rdef.object, rdef.constraints)
         # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
         # add a new column with UNIQUE, it should be added after the ALTER TABLE
         # using ADD INDEX
@@ -377,12 +378,13 @@
                 self.error('error while creating index for %s.%s: %s',
                            table, column, ex)
         # final relations are not infered, propagate
+        schema = session.vreg.schema
         try:
-            eschema = session.vreg.schema.eschema(rdef.subject)
+            eschema = schema.eschema(rdef.subject)
         except KeyError:
             return # entity type currently being added
         # propagate attribute to children classes
-        rschema = session.vreg.schema.rschema(rdef.name)
+        rschema = schema.rschema(rdef.name)
         # if relation type has been inserted in the same transaction, its final
         # attribute is still set to False, so we've to ensure it's False
         rschema.final = True
@@ -392,15 +394,19 @@
                       'cardinality': rdef.cardinality,
                       'constraints': rdef.constraints,
                       'permissions': rdef.get_permissions(),
-                      'order': rdef.order})
+                      'order': rdef.order,
+                      'infered': False, 'eid': None
+                      })
+        cstrtypemap = ss.cstrtype_mapping(session)
         groupmap = group_mapping(session)
+        object = schema.eschema(rdef.object)
         for specialization in eschema.specialized_by(False):
             if (specialization, rdef.object) in rschema.rdefs:
                 continue
-            sperdef = RelationDefinitionSchema(specialization, rschema, rdef.object, props)
-            for rql, args in ss.rdef2rql(rschema, str(specialization),
-                                         rdef.object, sperdef, groupmap=groupmap):
-                session.execute(rql, args)
+            sperdef = RelationDefinitionSchema(specialization, rschema,
+                                               object, props)
+            ss.execschemarql(session.execute, sperdef,
+                             ss.rdef2rql(sperdef, cstrtypemap, groupmap))
         # set default value, using sql for performance and to avoid
         # modification_date update
         if default:
@@ -449,13 +455,13 @@
                     rtype in session.transaction_data.get('createdtables', ())):
                 try:
                     rschema = schema.rschema(rtype)
-                    tablesql = rschema2sql(rschema)
+                    tablesql = y2sql.rschema2sql(rschema)
                 except KeyError:
                     # fake we add it to the schema now to get a correctly
                     # initialized schema but remove it before doing anything
                     # more dangerous...
                     rschema = schema.add_relation_type(rdef)
-                    tablesql = rschema2sql(rschema)
+                    tablesql = y2sql.rschema2sql(rschema)
                     schema.del_relation_type(rtype)
                 # create the necessary table
                 for sql in tablesql.split(';'):
@@ -488,11 +494,11 @@
                 return
             atype = self.rschema.objects(etype)[0]
             constraints = self.rschema.rdef(etype, atype).constraints
-            coltype = type_from_constraints(adbh, atype, constraints,
-                                            creating=False)
+            coltype = y2sql.type_from_constraints(adbh, atype, constraints,
+                                                  creating=False)
             # XXX check self.values['cardinality'][0] actually changed?
-            sql = adbh.sql_set_null_allowed(table, column, coltype,
-                                            self.values['cardinality'][0] != '1')
+            notnull = self.values['cardinality'][0] != '1'
+            sql = adbh.sql_set_null_allowed(table, column, coltype, notnull)
             session.system_sql(sql)
         if 'fulltextindexed' in self.values:
             UpdateFTIndexOp(session)
@@ -525,8 +531,8 @@
             oldcstr is None or oldcstr.max != newcstr.max):
             adbh = self.session.pool.source('system').dbhelper
             card = rtype.rdef(subjtype, objtype).cardinality
-            coltype = type_from_constraints(adbh, objtype, [newcstr],
-                                            creating=False)
+            coltype = y2sql.type_from_constraints(adbh, objtype, [newcstr],
+                                                  creating=False)
             sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
             try:
                 session.system_sql(sql, rollback_on_failure=False)
@@ -827,23 +833,26 @@
             return
         schema = self._cw.vreg.schema
         name = entity['name']
-        etype = EntityType(name=name, description=entity.get('description'),
-                           meta=entity.get('meta')) # don't care about final
+        etype = ybo.EntityType(name=name, description=entity.get('description'),
+                               meta=entity.get('meta')) # don't care about final
         # fake we add it to the schema now to get a correctly initialized schema
         # but remove it before doing anything more dangerous...
         schema = self._cw.vreg.schema
         eschema = schema.add_entity_type(etype)
         # generate table sql and rql to add metadata
-        tablesql = eschema2sql(self._cw.pool.source('system').dbhelper, eschema,
-                               prefix=SQL_PREFIX)
-        relrqls = []
+        tablesql = y2sql.eschema2sql(self._cw.pool.source('system').dbhelper,
+                                     eschema, prefix=SQL_PREFIX)
+        rdefrqls = []
+        gmap = group_mapping(self._cw)
+        cmap = ss.cstrtype_mapping(self._cw)
         for rtype in (META_RTYPES - VIRTUAL_RTYPES):
             rschema = schema[rtype]
             sampletype = rschema.subjects()[0]
             desttype = rschema.objects()[0]
-            props = rschema.rdef(sampletype, desttype)
-            relrqls += list(ss.rdef2rql(rschema, name, desttype, props,
-                                        groupmap=group_mapping(self._cw)))
+            rdef = copy(rschema.rdef(sampletype, desttype))
+            rdef.subject = mock_object(eid=entity.eid)
+            mock = mock_object(eid=None)
+            rdefrqls.append( (mock, tuple(ss.rdef2rql(rdef, cmap, gmap))) )
         # now remove it !
         schema.del_entity_type(name)
         # create the necessary table
@@ -856,8 +865,8 @@
         etype.eid = entity.eid
         MemSchemaCWETypeAdd(self._cw, etype)
         # add meta relations
-        for rql, kwargs in relrqls:
-            self._cw.execute(rql, kwargs)
+        for rdef, relrqls in rdefrqls:
+            ss.execschemarql(self._cw.execute, rdef, relrqls)
 
 
 class BeforeUpdateCWETypeHook(DelCWETypeHook):
@@ -914,12 +923,12 @@
 
     def __call__(self):
         entity = self.entity
-        rtype = RelationType(name=entity.name,
-                             description=entity.get('description'),
-                             meta=entity.get('meta', False),
-                             inlined=entity.get('inlined', False),
-                             symmetric=entity.get('symmetric', False),
-                             eid=entity.eid)
+        rtype = ybo.RelationType(name=entity.name,
+                                 description=entity.get('description'),
+                                 meta=entity.get('meta', False),
+                                 inlined=entity.get('inlined', False),
+                                 symmetric=entity.get('symmetric', False),
+                                 eid=entity.eid)
         MemSchemaCWRTypeAdd(self._cw, rtype)
 
 
--- a/hooks/test/unittest_syncschema.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/hooks/test/unittest_syncschema.py	Mon Mar 08 09:51:29 2010 +0100
@@ -3,9 +3,11 @@
 from cubicweb import ValidationError
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.server.sqlutils import SQL_PREFIX
-
+from cubicweb.devtools.repotest import schema_eids_idx, restore_schema_eids_idx
 
-SCHEMA_EIDS = {}
+def teardown_module(*args):
+    del SchemaModificationHooksTC.schema_eids
+
 class SchemaModificationHooksTC(CubicWebTC):
     reset_schema = True
 
@@ -15,29 +17,12 @@
         # we have to read schema from the database to get eid for schema entities
         config._cubes = None
         cls.repo.fill_schema()
-        # remember them so we can reread it from the fs instead of the db (too
-        # costly) between tests
-        for x in cls.repo.schema.entities():
-            SCHEMA_EIDS[x] = x.eid
-        for x in cls.repo.schema.relations():
-            SCHEMA_EIDS[x] = x.eid
-            for rdef in x.rdefs.itervalues():
-                SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)] = rdef.eid
+        cls.schema_eids = schema_eids_idx(cls.repo.schema)
 
     @classmethod
     def _refresh_repo(cls):
         super(SchemaModificationHooksTC, cls)._refresh_repo()
-        # rebuild schema eid index
-        schema = cls.repo.schema
-        for x in schema.entities():
-            x.eid = SCHEMA_EIDS[x]
-            schema._eid_index[x.eid] = x
-        for x in cls.repo.schema.relations():
-            x.eid = SCHEMA_EIDS[x]
-            schema._eid_index[x.eid] = x
-            for rdef in x.rdefs.itervalues():
-                rdef.eid = SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)]
-                schema._eid_index[rdef.eid] = rdef
+        restore_schema_eids_idx(cls.repo.schema, cls.schema_eids)
 
     def index_exists(self, etype, attr, unique=False):
         self.session.set_pool()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytestconf.py	Mon Mar 08 09:51:29 2010 +0100
@@ -0,0 +1,34 @@
+"""pytest configuration file: we need this to properly remove ressources
+cached on test classes, at least until we've proper support for teardown_class
+"""
+import sys
+from os.path import split, splitext
+from logilab.common.pytest import PyTester
+
+from cubicweb.etwist.server import _gc_debug
+
+class CustomPyTester(PyTester):
+    def testfile(self, filename, batchmode=False):
+        try:
+            return super(CustomPyTester, self).testfile(filename, batchmode)
+        finally:
+            modname = splitext(split(filename)[1])[0]
+            try:
+                module = sys.modules[modname]
+            except KeyError:
+                # error during test module import
+                return
+            for cls in vars(module).values():
+                if getattr(cls, '__module__', None) != modname:
+                    continue
+                clean_repo_test_cls(cls)
+            #_gc_debug()
+
+def clean_repo_test_cls(cls):
+    if 'repo' in cls.__dict__:
+        if not cls.repo._shutting_down:
+            cls.repo.shutdown()
+        del cls.repo
+    for clsattr in ('cnx', '_orig_cnx', 'config', '_config', 'vreg', 'schema'):
+        if clsattr in cls.__dict__:
+            delattr(cls, clsattr)
--- a/server/__init__.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/__init__.py	Mon Mar 08 09:51:29 2010 +0100
@@ -200,6 +200,7 @@
     cnx.commit()
     cnx.close()
     session.close()
+    repo.shutdown()
     # restore initial configuration
     config.creating = False
     config.read_instance_schema = read_instance_schema
--- a/server/migractions.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/migractions.py	Mon Mar 08 09:51:29 2010 +0100
@@ -25,10 +25,12 @@
 import os.path as osp
 from datetime import datetime
 from glob import glob
+from copy import copy
 from warnings import warn
 
 from logilab.common.deprecation import deprecated
 from logilab.common.decorators import cached, clear_cache
+from logilab.common.testlib import mock_object
 
 from yams.constraints import SizeConstraint
 from yams.schema2sql import eschema2sql, rschema2sql
@@ -283,6 +285,11 @@
         """cached group mapping"""
         return ss.group_mapping(self._cw)
 
+    @cached
+    def cstrtype_mapping(self):
+        """cached constraint types mapping"""
+        return ss.cstrtype_mapping(self._cw)
+
     def exec_event_script(self, event, cubepath=None, funcname=None,
                           *args, **kwargs):
         if cubepath:
@@ -400,14 +407,17 @@
             return
         self._synchronized.add(rtype)
         rschema = self.fs_schema.rschema(rtype)
+        reporschema = self.repo.schema.rschema(rtype)
         if syncprops:
-            self.rqlexecall(ss.updaterschema2rql(rschema),
+            assert reporschema.eid, reporschema
+            self.rqlexecall(ss.updaterschema2rql(rschema, reporschema.eid),
                             ask_confirm=self.verbosity>=2)
         if syncrdefs:
-            reporschema = self.repo.schema.rschema(rtype)
             for subj, obj in rschema.rdefs:
                 if (subj, obj) not in reporschema.rdefs:
                     continue
+                if rschema in VIRTUAL_RTYPES:
+                    continue
                 self._synchronize_rdef_schema(subj, rschema, obj,
                                               syncprops=syncprops,
                                               syncperms=syncperms)
@@ -440,9 +450,11 @@
                          'Y is CWEType, Y name %(y)s',
                          {'x': str(repoeschema), 'y': str(espschema)},
                          ask_confirm=False)
-        self.rqlexecall(ss.updateeschema2rql(eschema),
+        self.rqlexecall(ss.updateeschema2rql(eschema, repoeschema.eid),
                         ask_confirm=self.verbosity >= 2)
         for rschema, targettypes, role in eschema.relation_definitions(True):
+            if rschema in VIRTUAL_RTYPES:
+                continue
             if role == 'subject':
                 if not rschema in repoeschema.subject_relations():
                     continue
@@ -480,11 +492,11 @@
         confirm = self.verbosity >= 2
         if syncprops:
             # properties
-            self.rqlexecall(ss.updaterdef2rql(rschema, subjtype, objtype),
+            rdef = rschema.rdef(subjtype, objtype)
+            repordef = reporschema.rdef(subjtype, objtype)
+            self.rqlexecall(ss.updaterdef2rql(rdef, repordef.eid),
                             ask_confirm=confirm)
             # constraints
-            rdef = rschema.rdef(subjtype, objtype)
-            repordef = reporschema.rdef(subjtype, objtype)
             newconstraints = list(rdef.constraints)
             # 1. remove old constraints and update constraints of the same type
             # NOTE: don't use rschema.constraint_by_type because it may be
@@ -510,10 +522,10 @@
                     self.rqlexec('SET X value %(v)s WHERE X eid %(x)s',
                                  values, 'x', ask_confirm=confirm)
             # 2. add new constraints
-            for newcstr in newconstraints:
-                self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
-                                                  newcstr),
-                                ask_confirm=confirm)
+            cstrtype_map = self.cstrtype_mapping()
+            self.rqlexecall(ss.constraints2rql(cstrtype_map, newconstraints,
+                                               repordef.eid),
+                            ask_confirm=confirm)
         if syncperms and not rschema in VIRTUAL_RTYPES:
             self._synchronize_permissions(rdef, repordef.eid)
 
@@ -674,18 +686,23 @@
         targeted type is known
         """
         instschema = self.repo.schema
-        if etype in instschema:
-            # XXX (syt) plz explain: if we're adding an entity type, it should
-            # not be there...
-            eschema = instschema[etype]
-            if eschema.final:
-                instschema.del_entity_type(etype)
-        else:
-            eschema = self.fs_schema.eschema(etype)
+        assert not etype in instschema
+        #     # XXX (syt) plz explain: if we're adding an entity type, it should
+        #     # not be there...
+        #     eschema = instschema[etype]
+        #     if eschema.final:
+        #         instschema.del_entity_type(etype)
+        # else:
+        eschema = self.fs_schema.eschema(etype)
         confirm = self.verbosity >= 2
         groupmap = self.group_mapping()
+        cstrtypemap = self.cstrtype_mapping()
         # register the entity into CWEType
-        self.rqlexecall(ss.eschema2rql(eschema, groupmap), ask_confirm=confirm)
+        try:
+            execute = self._cw.unsafe_execute
+        except AttributeError:
+            execute = self._cw.execute
+        ss.execschemarql(execute, eschema, ss.eschema2rql(eschema, groupmap))
         # add specializes relation if needed
         self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
         # register entity's attributes
@@ -698,9 +715,8 @@
                 # actually in the schema
                 self.cmd_add_relation_type(rschema.type, False, commit=True)
             # register relation definition
-            self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type,
-                                        groupmap=groupmap),
-                            ask_confirm=confirm)
+            rdef = self._get_rdef(rschema, eschema, eschema.destination(rschema))
+            ss.execschemarql(execute, rdef, ss.rdef2rql(rdef, cstrtypemap, groupmap),)
         # take care to newly introduced base class
         # XXX some part of this should probably be under the "if auto" block
         for spschema in eschema.specialized_by(recursive=False):
@@ -760,10 +776,12 @@
                     # remember this two avoid adding twice non symmetric relation
                     # such as "Emailthread forked_from Emailthread"
                     added.append((etype, rschema.type, targettype))
-                    self.rqlexecall(ss.rdef2rql(rschema, etype, targettype,
-                                                groupmap=groupmap),
-                                    ask_confirm=confirm)
+                    rdef = self._get_rdef(rschema, eschema, targetschema)
+                    ss.execschemarql(execute, rdef,
+                                     ss.rdef2rql(rdef, cstrtypemap, groupmap))
             for rschema in eschema.object_relations():
+                if rschema.type in META_RTYPES:
+                    continue
                 rtypeadded = rschema.type in instschema or rschema.type in added
                 for targetschema in rschema.subjects(etype):
                     # ignore relations where the targeted type is not in the
@@ -781,9 +799,9 @@
                     elif (targettype, rschema.type, etype) in added:
                         continue
                     # register relation definition
-                    self.rqlexecall(ss.rdef2rql(rschema, targettype, etype,
-                                                groupmap=groupmap),
-                                    ask_confirm=confirm)
+                    rdef = self._get_rdef(rschema, targetschema, eschema)
+                    ss.execschemarql(execute, rdef,
+                                     ss.rdef2rql(rdef, cstrtypemap, groupmap))
         if commit:
             self.commit()
 
@@ -822,15 +840,26 @@
         committing depends on the `commit` argument value).
 
         """
+        reposchema = self.repo.schema
         rschema = self.fs_schema.rschema(rtype)
+        try:
+            execute = self._cw.unsafe_execute
+        except AttributeError:
+            execute = self._cw.execute
         # register the relation into CWRType and insert necessary relation
         # definitions
-        self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
-                        ask_confirm=self.verbosity>=2)
+        ss.execschemarql(execute, rschema, ss.rschema2rql(rschema, addrdef=False))
         if addrdef:
             self.commit()
-            self.rqlexecall(ss.rdef2rql(rschema, groupmap=self.group_mapping()),
-                            ask_confirm=self.verbosity>=2)
+            gmap = self.group_mapping()
+            cmap = self.cstrtype_mapping()
+            for rdef in rschema.rdefs.itervalues():
+                if not (reposchema.has_entity(rdef.subject)
+                        and reposchema.has_entity(rdef.object)):
+                    continue
+                self._set_rdef_eid(rdef)
+                ss.execschemarql(execute, rdef,
+                                 ss.rdef2rql(rdef, cmap, gmap))
             if rtype in META_RTYPES:
                 # if the relation is in META_RTYPES, ensure we're adding it for
                 # all entity types *in the persistent schema*, not only those in
@@ -839,15 +868,14 @@
                     if not etype in self.fs_schema:
                         # get sample object type and rproperties
                         objtypes = rschema.objects()
-                        assert len(objtypes) == 1
+                        assert len(objtypes) == 1, objtypes
                         objtype = objtypes[0]
-                        props = rschema.rproperties(
-                            rschema.subjects(objtype)[0], objtype)
-                        assert props
-                        self.rqlexecall(ss.rdef2rql(rschema, etype, objtype, props,
-                                                    groupmap=self.group_mapping()),
-                                        ask_confirm=self.verbosity>=2)
-
+                        rdef = copy(rschema.rdef(rschema.subjects(objtype)[0], objtype))
+                        rdef.subject = etype
+                        rdef.rtype = self.repo.schema.rschema(rschema)
+                        rdef.object = self.repo.schema.rschema(objtype)
+                        ss.execschemarql(execute, rdef,
+                                         ss.rdef2rql(rdef, cmap, gmap))
         if commit:
             self.commit()
 
@@ -877,12 +905,28 @@
         rschema = self.fs_schema.rschema(rtype)
         if not rtype in self.repo.schema:
             self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
-        self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype,
-                                    groupmap=self.group_mapping()),
-                        ask_confirm=self.verbosity>=2)
+        try:
+            execute = self._cw.unsafe_execute
+        except AttributeError:
+            execute = self._cw.execute
+        rdef = self._get_rdef(rschema, subjtype, objtype)
+        ss.execschemarql(execute, rdef,
+                         ss.rdef2rql(rdef, self.cstrtype_mapping(),
+                                     self.group_mapping()))
         if commit:
             self.commit()
 
+    def _get_rdef(self, rschema, subjtype, objtype):
+        return self._set_rdef_eid(rschema.rdefs[(subjtype, objtype)])
+
+    def _set_rdef_eid(self, rdef):
+        for attr in ('rtype', 'subject', 'object'):
+            schemaobj = getattr(rdef, attr)
+            if getattr(schemaobj, 'eid', None) is None:
+                schemaobj.eid =  self.repo.schema[schemaobj].eid
+                assert schemaobj.eid is not None
+        return rdef
+
     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
         """unregister an existing relation definition"""
         rschema = self.repo.schema.rschema(rtype)
--- a/server/querier.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/querier.py	Mon Mar 08 09:51:29 2010 +0100
@@ -22,9 +22,8 @@
 
 from cubicweb.server.utils import cleanup_solutions
 from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
-from cubicweb.server.ssplanner import add_types_restriction
+from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
 
-READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
 
 def empty_rset(rql, args, rqlst=None):
     """build an empty result set object"""
@@ -377,39 +376,6 @@
         self._r_obj_index = {}
         self._expanded_r_defs = {}
 
-    def relation_definitions(self, rqlst, to_build):
-        """add constant values to entity def, mark variables to be selected
-        """
-        to_select = {}
-        for relation in rqlst.main_relations:
-            lhs, rhs = relation.get_variable_parts()
-            rtype = relation.r_type
-            if rtype in READ_ONLY_RTYPES:
-                raise QueryError("can't assign to %s" % rtype)
-            try:
-                edef = to_build[str(lhs)]
-            except KeyError:
-                # lhs var is not to build, should be selected and added as an
-                # object relation
-                edef = to_build[str(rhs)]
-                to_select.setdefault(edef, []).append((rtype, lhs, 1))
-            else:
-                if isinstance(rhs, Constant) and not rhs.uid:
-                    # add constant values to entity def
-                    value = rhs.eval(self.args)
-                    eschema = edef.e_schema
-                    attrtype = eschema.subjrels[rtype].objects(eschema)[0]
-                    if attrtype == 'Password' and isinstance(value, unicode):
-                        value = value.encode('UTF8')
-                    edef[rtype] = value
-                elif to_build.has_key(str(rhs)):
-                    # create a relation between two newly created variables
-                    self.add_relation_def((edef, rtype, to_build[rhs.name]))
-                else:
-                    to_select.setdefault(edef, []).append( (rtype, rhs, 0) )
-        return to_select
-
-
     def add_entity_def(self, edef):
         """add an entity definition to build"""
         edef.querier_pending_relations = {}
--- a/server/repository.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/repository.py	Mon Mar 08 09:51:29 2010 +0100
@@ -327,6 +327,7 @@
         """called on server stop event to properly close opened sessions and
         connections
         """
+        assert not self._shutting_down, 'already shutting down'
         self._shutting_down = True
         if isinstance(self._looping_tasks, tuple): # if tasks have been started
             for looptask in self._looping_tasks:
--- a/server/schemaserial.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/schemaserial.py	Mon Mar 08 09:51:29 2010 +0100
@@ -50,6 +50,10 @@
                     continue
     return res
 
+def cstrtype_mapping(cursor):
+    """cached constraint types mapping"""
+    return dict(cursor.execute('Any T, X WHERE X is CWConstraintType, X name T'))
+
 # schema / perms deserialization ##############################################
 
 def deserialize_schema(schema, session):
@@ -229,14 +233,15 @@
     eschemas.remove(schema.eschema('CWEType'))
     eschemas.insert(0, schema.eschema('CWEType'))
     for eschema in eschemas:
-        for rql, kwargs in eschema2rql(eschema, groupmap):
-            execute(rql, kwargs, build_descr=False)
+        execschemarql(execute, eschema, eschema2rql(eschema, groupmap))
         if pb is not None:
             pb.update()
     # serialize constraint types
+    cstrtypemap = {}
     rql = 'INSERT CWConstraintType X: X name %(ct)s'
     for cstrtype in CONSTRAINTS:
-        execute(rql, {'ct': unicode(cstrtype)}, build_descr=False)
+        cstrtypemap[cstrtype] = execute(rql, {'ct': unicode(cstrtype)},
+                                        build_descr=False)[0][0]
         if pb is not None:
             pb.update()
     # serialize relations
@@ -246,8 +251,15 @@
             if pb is not None:
                 pb.update()
             continue
-        for rql, kwargs in rschema2rql(rschema, groupmap=groupmap):
-            execute(rql, kwargs, build_descr=False)
+        execschemarql(execute, rschema, rschema2rql(rschema, addrdef=False))
+        if rschema.symmetric:
+            rdefs = [rdef for k, rdef in rschema.rdefs.iteritems()
+                     if (rdef.subject, rdef.object) == k]
+        else:
+            rdefs = rschema.rdefs.itervalues()
+        for rdef in rdefs:
+            execschemarql(execute, rdef,
+                          rdef2rql(rdef, cstrtypemap, groupmap))
         if pb is not None:
             pb.update()
     for rql, kwargs in specialize2rql(schema):
@@ -258,6 +270,55 @@
         print
 
 
+# high level serialization functions
+
+def execschemarql(execute, schema, rqls):
+    for rql, kwargs in rqls:
+        kwargs['x'] = schema.eid
+        rset = execute(rql, kwargs, build_descr=False)
+        if schema.eid is None:
+            schema.eid = rset[0][0]
+        else:
+            assert rset
+
+def erschema2rql(erschema, groupmap):
+    if isinstance(erschema, schemamod.EntitySchema):
+        return eschema2rql(erschema, groupmap=groupmap)
+    return rschema2rql(erschema, groupmap=groupmap)
+
+def specialize2rql(schema):
+    for eschema in schema.entities():
+        if eschema.final:
+            continue
+        for rql, kwargs in eschemaspecialize2rql(eschema):
+            yield rql, kwargs
+
+# etype serialization
+
+def eschema2rql(eschema, groupmap=None):
+    """return a list of rql insert statements to enter an entity schema
+    in the database as an CWEType entity
+    """
+    relations, values = eschema_relations_values(eschema)
+    # NOTE: 'specializes' relation can't be inserted here since there's no
+    # way to make sure the parent type is inserted before the child type
+    yield 'INSERT CWEType X: %s' % ','.join(relations) , values
+    # entity permissions
+    if groupmap is not None:
+        for rql, args in _erperms2rql(eschema, groupmap):
+            yield rql, args
+
+def eschema_relations_values(eschema):
+    values = _ervalues(eschema)
+    relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
+    return relations, values
+
+def eschemaspecialize2rql(eschema):
+    specialized_type = eschema.specializes()
+    if specialized_type:
+        values = {'x': eschema.eid, 'et': specialized_type.eid}
+        yield 'SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s', values
+
 def _ervalues(erschema):
     try:
         type_ = unicode(erschema.type)
@@ -273,10 +334,23 @@
         'description': desc,
         }
 
-def eschema_relations_values(eschema):
-    values = _ervalues(eschema)
-    relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
-    return relations, values
+# rtype serialization
+
+def rschema2rql(rschema, cstrtypemap=None, addrdef=True, groupmap=None):
+    """return a list of rql insert statements to enter a relation schema
+    in the database as an CWRType entity
+    """
+    if rschema.type == 'has_text':
+        return
+    relations, values = rschema_relations_values(rschema)
+    yield 'INSERT CWRType X: %s' % ','.join(relations), values
+    if addrdef:
+        assert cstrtypemap
+        # sort for testing purpose
+        for rdef in sorted(rschema.rdefs.itervalues(),
+                           key=lambda x: (x.subject, x.object)):
+            for rql, values in rdef2rql(rdef, cstrtypemap, groupmap):
+                yield rql, values
 
 def rschema_relations_values(rschema):
     values = _ervalues(rschema)
@@ -290,169 +364,58 @@
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
 
-def _rdef_values(objtype, props):
-    amap = {'order': 'ordernum'}
+# rdef serialization
+
+def rdef2rql(rdef, cstrtypemap, groupmap=None):
+    # don't serialize infered relations
+    if rdef.infered:
+        return
+    relations, values = _rdef_values(rdef)
+    relations.append('X relation_type ER,X from_entity SE,X to_entity OE')
+    values.update({'se': rdef.subject.eid, 'rt': rdef.rtype.eid, 'oe': rdef.object.eid})
+    if rdef.final:
+        etype = 'CWAttribute'
+    else:
+        etype = 'CWRelation'
+    yield 'INSERT %s X: %s WHERE SE eid %%(se)s,ER eid %%(rt)s,OE eid %%(oe)s' % (
+        etype, ','.join(relations), ), values
+    for rql, values in constraints2rql(cstrtypemap, rdef.constraints):
+        yield rql, values
+    # no groupmap means "no security insertion"
+    if groupmap:
+        for rql, args in _erperms2rql(rdef, groupmap):
+            yield rql, args
+
+def _rdef_values(rdef):
+    amap = {'order': 'ordernum', 'default': 'defaultval'}
     values = {}
-    for prop, default in schemamod.RelationDefinitionSchema.rproperty_defs(objtype).iteritems():
+    for prop, default in rdef.rproperty_defs(rdef.object).iteritems():
         if prop in ('eid', 'constraints', 'uid', 'infered', 'permissions'):
             continue
-        value = props.get(prop, default)
+        value = getattr(rdef, prop)
+        # XXX type cast really necessary?
         if prop in ('indexed', 'fulltextindexed', 'internationalizable'):
             value = bool(value)
         elif prop == 'ordernum':
             value = int(value)
         elif isinstance(value, str):
             value = unicode(value)
+        if value is not None and prop == 'default':
+            if value is False:
+                value = u''
+            if not isinstance(value, unicode):
+                value = unicode(value)
         values[amap.get(prop, prop)] = value
-    return values
-
-def nfrdef_relations_values(objtype, props):
-    values = _rdef_values(objtype, props)
-    relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
-    return relations, values
-
-def frdef_relations_values(objtype, props):
-    values = _rdef_values(objtype, props)
-    default = values['default']
-    del values['default']
-    if default is not None:
-        if default is False:
-            default = u''
-        elif not isinstance(default, unicode):
-            default = unicode(default)
-    values['defaultval'] = default
     relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)]
     return relations, values
 
-
-def __rdef2rql(genmap, rschema, subjtype=None, objtype=None, props=None,
-               groupmap=None):
-    if subjtype is None:
-        assert objtype is None
-        assert props is None
-        targets = sorted(rschema.rdefs)
-    else:
-        assert not objtype is None
-        targets = [(subjtype, objtype)]
-    # relation schema
-    if rschema.final:
-        etype = 'CWAttribute'
-    else:
-        etype = 'CWRelation'
-    for subjtype, objtype in targets:
-        if props is None:
-            _props = rschema.rdef(subjtype, objtype)
-        else:
-            _props = props
-        # don't serialize infered relations
-        if _props.get('infered'):
-            continue
-        gen = genmap[rschema.final]
-        for rql, values in gen(rschema, subjtype, objtype, _props):
-            yield rql, values
-        # no groupmap means "no security insertion"
-        if groupmap:
-            for rql, args in _erperms2rql(_props, groupmap):
-                args['st'] = str(subjtype)
-                args['rt'] = str(rschema)
-                args['ot'] = str(objtype)
-                yield rql + 'X is %s, X from_entity ST, X to_entity OT, '\
-                      'X relation_type RT, RT name %%(rt)s, ST name %%(st)s, '\
-                      'OT name %%(ot)s' % etype, args
-
-
-def schema2rql(schema, skip=None, allow=None):
-    """return a list of rql insert statements to enter the schema in the
-    database as CWRType and CWEType entities
-    """
-    assert not (skip is not None and allow is not None), \
-           'can\'t use both skip and allow'
-    all = schema.entities() + schema.relations()
-    if skip is not None:
-        return chain(*[erschema2rql(schema[t]) for t in all if not t in skip])
-    elif allow is not None:
-        return chain(*[erschema2rql(schema[t]) for t in all if t in allow])
-    return chain(*[erschema2rql(schema[t]) for t in all])
-
-def erschema2rql(erschema, groupmap):
-    if isinstance(erschema, schemamod.EntitySchema):
-        return eschema2rql(erschema, groupmap=groupmap)
-    return rschema2rql(erschema, groupmap=groupmap)
-
-def eschema2rql(eschema, groupmap=None):
-    """return a list of rql insert statements to enter an entity schema
-    in the database as an CWEType entity
-    """
-    relations, values = eschema_relations_values(eschema)
-    # NOTE: 'specializes' relation can't be inserted here since there's no
-    # way to make sure the parent type is inserted before the child type
-    yield 'INSERT CWEType X: %s' % ','.join(relations) , values
-    # entity permissions
-    if groupmap is not None:
-        for rql, args in _erperms2rql(eschema, groupmap):
-            args['name'] = str(eschema)
-            yield rql + 'X is CWEType, X name %(name)s', args
-
-def specialize2rql(schema):
-    for eschema in schema.entities():
-        for rql, kwargs in eschemaspecialize2rql(eschema):
-            yield rql, kwargs
-
-def eschemaspecialize2rql(eschema):
-    specialized_type = eschema.specializes()
-    if specialized_type:
-        values = {'x': eschema.type, 'et': specialized_type.type}
-        yield 'SET X specializes ET WHERE X name %(x)s, ET name %(et)s', values
-
-def rschema2rql(rschema, addrdef=True, groupmap=None):
-    """return a list of rql insert statements to enter a relation schema
-    in the database as an CWRType entity
-    """
-    if rschema.type == 'has_text':
-        return
-    relations, values = rschema_relations_values(rschema)
-    yield 'INSERT CWRType X: %s' % ','.join(relations), values
-    if addrdef:
-        for rql, values in rdef2rql(rschema, groupmap=groupmap):
-            yield rql, values
-
-def rdef2rql(rschema, subjtype=None, objtype=None, props=None, groupmap=None):
-    genmap = {True: frdef2rql, False: nfrdef2rql}
-    return __rdef2rql(genmap, rschema, subjtype, objtype, props, groupmap)
-
-
-_LOCATE_RDEF_RQL0 = 'X relation_type ER,X from_entity SE,X to_entity OE'
-_LOCATE_RDEF_RQL1 = 'SE name %(se)s,ER name %(rt)s,OE name %(oe)s'
-
-def frdef2rql(rschema, subjtype, objtype, props):
-    relations, values = frdef_relations_values(objtype, props)
-    relations.append(_LOCATE_RDEF_RQL0)
-    values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)})
-    yield 'INSERT CWAttribute X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
-    for rql, values in rdefrelations2rql(rschema, subjtype, objtype, props):
-        yield rql + ', EDEF is CWAttribute', values
-
-def nfrdef2rql(rschema, subjtype, objtype, props):
-    relations, values = nfrdef_relations_values(objtype, props)
-    relations.append(_LOCATE_RDEF_RQL0)
-    values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)})
-    yield 'INSERT CWRelation X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values
-    for rql, values in rdefrelations2rql(rschema, subjtype, objtype, props):
-        yield rql + ', EDEF is CWRelation', values
-
-def rdefrelations2rql(rschema, subjtype, objtype, props):
-    iterators = []
-    for constraint in props.constraints:
-        iterators.append(constraint2rql(rschema, subjtype, objtype, constraint))
-    return chain(*iterators)
-
-def constraint2rql(rschema, subjtype, objtype, constraint):
-    values = {'ctname': unicode(constraint.type()),
-              'value': unicode(constraint.serialize()),
-              'rt': str(rschema), 'se': str(subjtype), 'oe': str(objtype)}
-    yield 'INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \
-CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, \
-ER name %(rt)s, SE name %(se)s, OE name %(oe)s', values
+def constraints2rql(cstrtypemap, constraints, rdefeid=None):
+    for constraint in constraints:
+        values = {'ct': cstrtypemap[constraint.type()],
+                  'value': unicode(constraint.serialize()),
+                  'x': rdefeid} # when not specified, will have to be set by the caller
+        yield 'INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \
+CT eid %(ct)s, EDEF eid %(x)s', values
 
 
 def _erperms2rql(erschema, groupmap):
@@ -471,7 +434,7 @@
             if isinstance(group_or_rqlexpr, basestring):
                 # group
                 try:
-                    yield ('SET X %s_permission Y WHERE Y eid %%(g)s, ' % action,
+                    yield ('SET X %s_permission Y WHERE Y eid %%(g)s, X eid %%(x)s' % action,
                            {'g': groupmap[group_or_rqlexpr]})
                 except KeyError:
                     continue
@@ -479,36 +442,24 @@
                 # rqlexpr
                 rqlexpr = group_or_rqlexpr
                 yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
-                       'E mainvars %%(v)s, X %s_permission E WHERE ' % action,
+                       'E mainvars %%(v)s, X %s_permission E WHERE X eid %%(x)s' % action,
                        {'e': unicode(rqlexpr.expression),
                         'v': unicode(rqlexpr.mainvars),
                         't': unicode(rqlexpr.__class__.__name__)})
 
+# update functions
 
-def updateeschema2rql(eschema):
+def updateeschema2rql(eschema, eid):
     relations, values = eschema_relations_values(eschema)
-    values['et'] = eschema.type
-    yield 'SET %s WHERE X is CWEType, X name %%(et)s' % ','.join(relations), values
-
-def updaterschema2rql(rschema):
-    relations, values = rschema_relations_values(rschema)
-    values['rt'] = rschema.type
-    yield 'SET %s WHERE X is CWRType, X name %%(rt)s' % ','.join(relations), values
+    values['x'] = eid
+    yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
 
-def updaterdef2rql(rschema, subjtype=None, objtype=None, props=None):
-    genmap = {True: updatefrdef2rql, False: updatenfrdef2rql}
-    return __rdef2rql(genmap, rschema, subjtype, objtype, props)
+def updaterschema2rql(rschema, eid):
+    relations, values = rschema_relations_values(rschema)
+    values['x'] = eid
+    yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
 
-def updatefrdef2rql(rschema, subjtype, objtype, props):
-    relations, values = frdef_relations_values(objtype, props)
-    values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
-    yield 'SET %s WHERE %s, %s, X is CWAttribute' % (','.join(relations),
-                                                     _LOCATE_RDEF_RQL0,
-                                                     _LOCATE_RDEF_RQL1), values
-
-def updatenfrdef2rql(rschema, subjtype, objtype, props):
-    relations, values = nfrdef_relations_values(objtype, props)
-    values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
-    yield 'SET %s WHERE %s, %s, X is CWRelation' % (','.join(relations),
-                                                    _LOCATE_RDEF_RQL0,
-                                                    _LOCATE_RDEF_RQL1), values
+def updaterdef2rql(rdef, eid):
+    relations, values = _rdef_values(rdef)
+    values['x'] = eid
+    yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values
--- a/server/session.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/session.py	Mon Mar 08 09:51:29 2010 +0100
@@ -585,10 +585,10 @@
     def add_operation(self, operation, index=None):
         """add an observer"""
         assert self.commit_state != 'commit'
-        if index is not None:
+        if index is None:
+            self.pending_operations.append(operation)
+        else:
             self.pending_operations.insert(index, operation)
-        else:
-            self.pending_operations.append(operation)
 
     # querier helpers #########################################################
 
--- a/server/ssplanner.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/ssplanner.py	Mon Mar 08 09:51:29 2010 +0100
@@ -10,12 +10,104 @@
 from copy import copy
 
 from rql.stmts import Union, Select
-from rql.nodes import Constant
+from rql.nodes import Constant, Relation
 
 from cubicweb import QueryError, typed_eid
 from cubicweb.schema import VIRTUAL_RTYPES
 from cubicweb.rqlrewrite import add_types_restriction
 
+READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
+
+_CONSTANT = object()
+_FROM_SUBSTEP = object()
+
+def _extract_const_attributes(plan, rqlst, to_build):
+    """add constant values to entity def, mark variables to be selected
+    """
+    to_select = {}
+    for relation in rqlst.main_relations:
+        lhs, rhs = relation.get_variable_parts()
+        rtype = relation.r_type
+        if rtype in READ_ONLY_RTYPES:
+            raise QueryError("can't assign to %s" % rtype)
+        try:
+            edef = to_build[str(lhs)]
+        except KeyError:
+            # lhs var is not to build, should be selected and added as an
+            # object relation
+            edef = to_build[str(rhs)]
+            to_select.setdefault(edef, []).append((rtype, lhs, 1))
+        else:
+            if isinstance(rhs, Constant) and not rhs.uid:
+                # add constant values to entity def
+                value = rhs.eval(plan.args)
+                eschema = edef.e_schema
+                attrtype = eschema.subjrels[rtype].objects(eschema)[0]
+                if attrtype == 'Password' and isinstance(value, unicode):
+                    value = value.encode('UTF8')
+                edef[rtype] = value
+            elif to_build.has_key(str(rhs)):
+                # create a relation between two newly created variables
+                plan.add_relation_def((edef, rtype, to_build[rhs.name]))
+            else:
+                to_select.setdefault(edef, []).append( (rtype, rhs, 0) )
+    return to_select
+
+def _extract_eid_consts(plan, rqlst):
+    """return a dict mapping rqlst variable object to their eid if specified in
+    the syntax tree
+    """
+    session = plan.session
+    eschema = session.vreg.schema.eschema
+    if rqlst.where is None:
+        return {}
+    eidconsts = {}
+    neweids = session.transaction_data.get('neweids', ())
+    for rel in rqlst.where.get_nodes(Relation):
+        if rel.r_type == 'eid' and not rel.neged(strict=True):
+            lhs, rhs = rel.get_variable_parts()
+            if isinstance(rhs, Constant):
+                eid = typed_eid(rhs.eval(plan.args))
+                # check read permission here since it may not be done by
+                # the generated select substep if not emited (eg nothing
+                # to be selected)
+                if eid not in neweids:
+                    eschema(session.describe(eid)[0]).check_perm(session, 'read')
+                eidconsts[lhs.variable] = eid
+    return eidconsts
+
+def _build_substep_query(select, origrqlst):
+    """Finalize substep select query that should be executed to get proper
+    selection of stuff to insert/update.
+
+    Return None when no query actually needed, else the given select node that
+    will be used as substep query.
+
+    When select has nothing selected, search in origrqlst for restriction that
+    should be considered.
+    """
+    if select.selection:
+        if origrqlst.where is not None:
+            select.set_where(origrqlst.where.copy(select))
+        return select
+    if origrqlst.where is None:
+        return
+    for rel in origrqlst.where.iget_nodes(Relation):
+        # search for a relation which is neither a type restriction (is) nor an
+        # eid specification (not neged eid with constant node
+        if rel.neged(strict=True) or not (
+            rel.is_types_restriction() or
+            (rel.r_type == 'eid'
+             and isinstance(rel.get_variable_parts()[1], Constant))):
+            break
+    else:
+        return
+    select.set_where(origrqlst.where.copy(select))
+    if not select.selection:
+        # no selection, append one randomly
+        select.append_selected(rel.children[0].copy(select))
+    return select
+
 
 class SSPlanner(object):
     """SingleSourcePlanner: build execution plan for rql queries
@@ -56,34 +148,37 @@
             to_build[var.name] = etype_class(etype)(session)
             plan.add_entity_def(to_build[var.name])
         # add constant values to entity def, mark variables to be selected
-        to_select = plan.relation_definitions(rqlst, to_build)
+        to_select = _extract_const_attributes(plan, rqlst, to_build)
         # add necessary steps to add relations and update attributes
         step = InsertStep(plan) # insert each entity and its relations
-        step.children += self._compute_relation_steps(plan, rqlst.solutions,
-                                                      rqlst.where, to_select)
+        step.children += self._compute_relation_steps(plan, rqlst, to_select)
         return (step,)
 
-    def _compute_relation_steps(self, plan, solutions, restriction, to_select):
+    def _compute_relation_steps(self, plan, rqlst, to_select):
         """handle the selection of relations for an insert query"""
+        eidconsts = _extract_eid_consts(plan, rqlst)
         for edef, rdefs in to_select.items():
             # create a select rql st to fetch needed data
             select = Select()
             eschema = edef.e_schema
-            for i in range(len(rdefs)):
-                rtype, term, reverse = rdefs[i]
-                select.append_selected(term.copy(select))
+            for i, (rtype, term, reverse) in enumerate(rdefs):
+                if getattr(term, 'variable', None) in eidconsts:
+                    value = eidconsts[term.variable]
+                else:
+                    select.append_selected(term.copy(select))
+                    value = _FROM_SUBSTEP
                 if reverse:
-                    rdefs[i] = rtype, RelationsStep.REVERSE_RELATION
+                    rdefs[i] = (rtype, InsertRelationsStep.REVERSE_RELATION, value)
                 else:
                     rschema = eschema.subjrels[rtype]
                     if rschema.final or rschema.inlined:
-                        rdefs[i] = rtype, RelationsStep.FINAL
+                        rdefs[i] = (rtype, InsertRelationsStep.FINAL, value)
                     else:
-                        rdefs[i] = rtype, RelationsStep.RELATION
-            if restriction is not None:
-                select.set_where(restriction.copy(select))
-            step = RelationsStep(plan, edef, rdefs)
-            step.children += self._select_plan(plan, select, solutions)
+                        rdefs[i] = (rtype, InsertRelationsStep.RELATION, value)
+            step = InsertRelationsStep(plan, edef, rdefs)
+            select = _build_substep_query(select, rqlst)
+            if select is not None:
+                step.children += self._select_plan(plan, select, rqlst.solutions)
             yield step
 
     def build_delete_plan(self, plan, rqlst):
@@ -127,37 +222,61 @@
 
     def build_set_plan(self, plan, rqlst):
         """get an execution plan from an SET RQL query"""
-        select = Select()
-        # extract variables to add to the selection
-        selected_index = {}
-        index = 0
-        relations, attrrelations = [], []
         getrschema = self.schema.rschema
-        for relation in rqlst.main_relations:
+        select = Select()   # potential substep query
+        selectedidx = {}    # local state
+        attributes = set()  # edited attributes
+        updatedefs = []     # definition of update attributes/relations
+        selidx = residx = 0 # substep selection / resulting rset indexes
+        # search for eid const in the WHERE clause
+        eidconsts = _extract_eid_consts(plan, rqlst)
+        # build `updatedefs` describing things to update and add necessary
+        # variables to the substep selection
+        for i, relation in enumerate(rqlst.main_relations):
             if relation.r_type in VIRTUAL_RTYPES:
                 raise QueryError('can not assign to %r relation'
                                  % relation.r_type)
             lhs, rhs = relation.get_variable_parts()
-            if not lhs.as_string('utf-8') in selected_index:
-                select.append_selected(lhs.copy(select))
-                selected_index[lhs.as_string('utf-8')] = index
-                index += 1
-            if not rhs.as_string('utf-8') in selected_index:
-                select.append_selected(rhs.copy(select))
-                selected_index[rhs.as_string('utf-8')] = index
-                index += 1
+            lhskey = lhs.as_string('utf-8')
+            if not lhskey in selectedidx:
+                if lhs.variable in eidconsts:
+                    eid = eidconsts[lhs.variable]
+                    lhsinfo = (_CONSTANT, eid, residx)
+                else:
+                    select.append_selected(lhs.copy(select))
+                    lhsinfo = (_FROM_SUBSTEP, selidx, residx)
+                    selidx += 1
+                residx += 1
+                selectedidx[lhskey] = lhsinfo
+            else:
+                lhsinfo = selectedidx[lhskey][:-1] + (None,)
+            rhskey = rhs.as_string('utf-8')
+            if not rhskey in selectedidx:
+                if isinstance(rhs, Constant):
+                    rhsinfo = (_CONSTANT, rhs.eval(plan.args), residx)
+                elif getattr(rhs, 'variable', None) in eidconsts:
+                    eid = eidconsts[rhs.variable]
+                    rhsinfo = (_CONSTANT, eid, residx)
+                else:
+                    select.append_selected(rhs.copy(select))
+                    rhsinfo = (_FROM_SUBSTEP, selidx, residx)
+                    selidx += 1
+                residx += 1
+                selectedidx[rhskey] = rhsinfo
+            else:
+                rhsinfo = selectedidx[rhskey][:-1] + (None,)
             rschema = getrschema(relation.r_type)
+            updatedefs.append( (lhsinfo, rhsinfo, rschema) )
             if rschema.final or rschema.inlined:
-                attrrelations.append(relation)
-            else:
-                relations.append(relation)
-        # add step necessary to fetch all selected variables values
-        if rqlst.where is not None:
-            select.set_where(rqlst.where.copy(select))
-        # set distinct to avoid potential duplicate key error
-        select.distinct = True
-        step = UpdateStep(plan, attrrelations, relations, selected_index)
-        step.children += self._select_plan(plan, select, rqlst.solutions)
+                attributes.add(relation.r_type)
+        # the update step
+        step = UpdateStep(plan, updatedefs, attributes)
+        # when necessary add substep to fetch yet unknown values
+        select = _build_substep_query(select, rqlst)
+        if select is not None:
+            # set distinct to avoid potential duplicate key error
+            select.distinct = True
+            step.children += self._select_plan(plan, select, rqlst.solutions)
         return (step,)
 
     # internal methods ########################################################
@@ -308,7 +427,7 @@
 
 # UPDATE/INSERT/DELETE steps ##################################################
 
-class RelationsStep(Step):
+class InsertRelationsStep(Step):
     """step consisting in adding attributes/relations to entity defs from a
     previous FetchStep
 
@@ -334,33 +453,38 @@
         """execute this step"""
         base_edef = self.edef
         edefs = []
-        result = self.execute_child()
+        if self.children:
+            result = self.execute_child()
+        else:
+            result = [[]]
         for row in result:
             # get a new entity definition for this row
             edef = copy(base_edef)
             # complete this entity def using row values
-            for i in range(len(self.rdefs)):
-                rtype, rorder = self.rdefs[i]
-                if rorder == RelationsStep.FINAL:
-                    edef[rtype] = row[i]
-                elif rorder == RelationsStep.RELATION:
-                    self.plan.add_relation_def( (edef, rtype, row[i]) )
-                    edef.querier_pending_relations[(rtype, 'subject')] = row[i]
+            index = 0
+            for rtype, rorder, value in self.rdefs:
+                if value is _FROM_SUBSTEP:
+                    value = row[index]
+                    index += 1
+                if rorder == InsertRelationsStep.FINAL:
+                    edef[rtype] = value
+                elif rorder == InsertRelationsStep.RELATION:
+                    self.plan.add_relation_def( (edef, rtype, value) )
+                    edef.querier_pending_relations[(rtype, 'subject')] = value
                 else:
-                    self.plan.add_relation_def( (row[i], rtype, edef) )
-                    edef.querier_pending_relations[(rtype, 'object')] = row[i]
+                    self.plan.add_relation_def( (value, rtype, edef) )
+                    edef.querier_pending_relations[(rtype, 'object')] = value
             edefs.append(edef)
         self.plan.substitute_entity_def(base_edef, edefs)
         return result
 
-
 class InsertStep(Step):
     """step consisting in inserting new entities / relations"""
 
     def execute(self):
         """execute this step"""
         for step in self.children:
-            assert isinstance(step, RelationsStep)
+            assert isinstance(step, InsertRelationsStep)
             step.plan = self.plan
             step.execute()
         # insert entities first
@@ -408,40 +532,46 @@
     definitions and from results fetched in previous step
     """
 
-    def __init__(self, plan, attribute_relations, relations, selected_index):
+    def __init__(self, plan, updatedefs, attributes):
         Step.__init__(self, plan)
-        self.attribute_relations = attribute_relations
-        self.relations = relations
-        self.selected_index = selected_index
+        self.updatedefs = updatedefs
+        self.attributes = attributes
 
     def execute(self):
         """execute this step"""
-        plan = self.plan
         session = self.plan.session
         repo = session.repo
         edefs = {}
         # insert relations
-        attributes = set([relation.r_type for relation in self.attribute_relations])
-        result = self.execute_child()
-        for row in result:
-            for relation in self.attribute_relations:
-                lhs, rhs = relation.get_variable_parts()
-                eid = typed_eid(row[self.selected_index[str(lhs)]])
-                try:
-                    edef = edefs[eid]
-                except KeyError:
-                    edefs[eid] = edef = session.entity_from_eid(eid)
-                if isinstance(rhs, Constant):
-                    # add constant values to entity def
-                    value = rhs.eval(plan.args)
-                    edef[relation.r_type] = value
+        if self.children:
+            result = self.execute_child()
+        else:
+            result = [[]]
+        for i, row in enumerate(result):
+            newrow = []
+            for (lhsinfo, rhsinfo, rschema) in self.updatedefs:
+                lhsval = _handle_relterm(lhsinfo, row, newrow)
+                rhsval = _handle_relterm(rhsinfo, row, newrow)
+                if rschema.final or rschema.inlined:
+                    eid = typed_eid(lhsval)
+                    try:
+                        edef = edefs[eid]
+                    except KeyError:
+                        edefs[eid] = edef = session.entity_from_eid(eid)
+                    edef[str(rschema)] = rhsval
                 else:
-                    edef[relation.r_type] = row[self.selected_index[str(rhs)]]
-            for relation in self.relations:
-                subj = row[self.selected_index[str(relation.children[0])]]
-                obj = row[self.selected_index[str(relation.children[1])]]
-                repo.glob_add_relation(session, subj, relation.r_type, obj)
+                    repo.glob_add_relation(session, lhsval, str(rschema), rhsval)
+            result[i] = newrow
         # update entities
         for eid, edef in edefs.iteritems():
-            repo.glob_update_entity(session, edef, attributes)
+            repo.glob_update_entity(session, edef, self.attributes)
         return result
+
+def _handle_relterm(info, row, newrow):
+    if info[0] is _CONSTANT:
+        val = info[1]
+    else: # _FROM_SUBSTEP
+        val = row[info[1]]
+    if info[-1] is not None:
+        newrow.append(val)
+    return val
--- a/server/test/unittest_checkintegrity.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_checkintegrity.py	Mon Mar 08 09:51:29 2010 +0100
@@ -13,10 +13,9 @@
 
 from cubicweb.server.checkintegrity import check
 
-repo, cnx = init_test_database()
-
 class CheckIntegrityTC(TestCase):
     def test(self):
+        repo, cnx = init_test_database()
         sys.stderr = sys.stdout = StringIO()
         try:
             check(repo, cnx, ('entities', 'relations', 'text_index', 'metadata'),
@@ -24,6 +23,7 @@
         finally:
             sys.stderr = sys.__stderr__
             sys.stdout = sys.__stdout__
+        repo.shutdown()
 
 if __name__ == '__main__':
     unittest_main()
--- a/server/test/unittest_hook.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_hook.py	Mon Mar 08 09:51:29 2010 +0100
@@ -69,6 +69,10 @@
 config.bootstrap_cubes()
 schema = config.load_schema()
 
+def teardown_module(*args):
+    global config, schema
+    del config, schema
+
 class AddAnyHook(hook.Hook):
     __regid__ = 'addany'
     category = 'cat1'
--- a/server/test/unittest_ldapuser.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_ldapuser.py	Mon Mar 08 09:51:29 2010 +0100
@@ -370,6 +370,11 @@
 LDAPUserSourceTC._init_repo()
 repo = LDAPUserSourceTC.repo
 
+def teardown_module(*args):
+    global repo
+    del repo
+    del RQL2LDAPFilterTC.schema
+
 class RQL2LDAPFilterTC(RQLGeneratorTC):
     schema = repo.schema
 
--- a/server/test/unittest_migractions.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_migractions.py	Mon Mar 08 09:51:29 2010 +0100
@@ -14,6 +14,11 @@
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.migractions import *
 
+migrschema = None
+def teardown_module(*args):
+    global migrschema
+    del migrschema
+    del MigrationCommandsTC.origschema
 
 class MigrationCommandsTC(CubicWebTC):
 
@@ -35,6 +40,13 @@
     def _refresh_repo(cls):
         super(MigrationCommandsTC, cls)._refresh_repo()
         cls.repo.set_schema(deepcopy(cls.origschema), resetvreg=False)
+        # reset migration schema eids
+        for eschema in migrschema.entities():
+            eschema.eid = None
+        for rschema in migrschema.relations():
+            rschema.eid = None
+            for rdef in rschema.rdefs.values():
+                rdef.eid = None
 
     def setUp(self):
         CubicWebTC.setUp(self)
@@ -44,7 +56,6 @@
         assert self.cnx is self.mh._cnx
         assert self.session is self.mh.session, (self.session.id, self.mh.session.id)
 
-
     def test_add_attribute_int(self):
         self.failIf('whatever' in self.schema)
         self.request().create_entity('Note')
--- a/server/test/unittest_msplanner.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_msplanner.py	Mon Mar 08 09:51:29 2010 +0100
@@ -60,6 +60,11 @@
 # keep cnx so it's not garbage collected and the associated session is closed
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    del repo, cnx
+
+
 class BaseMSPlannerTC(BasePlannerTC):
     """test planner related feature on a 3-sources repository:
 
@@ -87,10 +92,10 @@
         self.add_source(FakeCardSource, 'cards')
 
     def tearDown(self):
-        super(BaseMSPlannerTC, self).tearDown()
         # restore hijacked security
         self.restore_orig_affaire_security()
         self.restore_orig_cwuser_security()
+        super(BaseMSPlannerTC, self).tearDown()
 
     def restore_orig_affaire_security(self):
         affreadperms = list(self.schema['Affaire'].permissions['read'])
@@ -1517,15 +1522,11 @@
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
         repo._type_source_cache[999998] = ('State', 'system', None)
         self._test('INSERT Note X: X in_state S, X type T WHERE S eid %(s)s, N eid %(n)s, N type T',
-                   [('FetchStep', [('Any T WHERE N eid 999999, N type T, N is Note',
-                                    [{'N': 'Note', 'T': 'String'}])],
-                     [self.cards], None, {'N.type': 'table0.C0', 'T': 'table0.C0'}, []),
-                    ('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,T WHERE N type T, N is Note',
+                   [('InsertStep',
+                     [('InsertRelationsStep',
+                       [('OneFetchStep', [('Any T WHERE N eid 999999, N type T, N is Note',
                                            [{'N': 'Note', 'T': 'String'}])],
-                        None, None, [self.system],
-                        {'N.type': 'table0.C0', 'T': 'table0.C0'}, [])])
+                        None, None, [self.cards], {}, [])])
                       ])
                     ],
                    {'n': 999999, 's': 999998})
@@ -1534,15 +1535,11 @@
         repo._type_source_cache[999999] = ('Note', 'cards', 999999)
         repo._type_source_cache[999998] = ('State', 'system', None)
         self._test('INSERT Note X: X in_state S, X type T, X migrated_from N WHERE S eid %(s)s, N eid %(n)s, N type T',
-                   [('FetchStep', [('Any T,N WHERE N eid 999999, N type T, N is Note',
-                                    [{'N': 'Note', 'T': 'String'}])],
-                     [self.cards], None, {'N': 'table0.C1', 'N.type': 'table0.C0', 'T': 'table0.C0'}, []),
-                    ('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,T,N WHERE N type T, N is Note',
+                   [('InsertStep',
+                     [('InsertRelationsStep',
+                       [('OneFetchStep', [('Any T WHERE N eid 999999, N type T, N is Note',
                                            [{'N': 'Note', 'T': 'String'}])],
-                         None, None, [self.system],
-                         {'N': 'table0.C1', 'N.type': 'table0.C0', 'T': 'table0.C0'}, [])
+                         None, None, [self.cards], {}, [])
                         ])
                       ])
                     ],
@@ -1553,8 +1550,8 @@
         repo._type_source_cache[999998] = ('State', 'cards', 999998)
         self._test('INSERT Note X: X in_state S, X type T WHERE S eid %(s)s, N eid %(n)s, N type T',
                    [('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,T WHERE N eid 999999, N type T, N is Note',
+                     [('InsertRelationsStep',
+                       [('OneFetchStep', [('Any T WHERE N eid 999999, N type T, N is Note',
                                            [{'N': 'Note', 'T': 'String'}])],
                          None, None, [self.cards], {}, [])]
                        )]
@@ -1566,10 +1563,7 @@
         repo._type_source_cache[999998] = ('State', 'system', None)
         self._test('INSERT Note X: X in_state S, X type "bla", X migrated_from N WHERE S eid %(s)s, N eid %(n)s',
                    [('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,999999', [{}])],
-                         None, None, [self.system], {}, [])]
-                       )]
+                      [('InsertRelationsStep', [])]
                      )],
                    {'n': 999999, 's': 999998})
 
@@ -1578,12 +1572,14 @@
         repo._type_source_cache[999998] = ('State', 'system', None)
         self._test('INSERT Note X: X in_state S, X type "bla", X migrated_from N WHERE S eid %(s)s, N eid %(n)s, A concerne N',
                    [('InsertStep',
-                     [('RelationsStep',
-                       [('OneFetchStep', [('Any 999998,999999 WHERE A concerne 999999, A is Affaire',
-                                           [{'A': 'Affaire'}])],
-                         None, None, [self.system], {}, [])]
-                       )]
-                     )],
+                     [('InsertRelationsStep',
+                       [('OneFetchStep',
+                         [('Any A WHERE A concerne 999999, A is Affaire',
+                           [{'A': 'Affaire'}])],
+                         None, None, [self.system], {}, []),
+                        ]),
+                      ])
+                    ],
                    {'n': 999999, 's': 999998})
 
     def test_delete_relation1(self):
@@ -1664,7 +1660,7 @@
         # source, states should only be searched in the system source as well
         self._test('SET X in_state S WHERE X eid %(x)s, S name "deactivated"',
                    [('UpdateStep', [
-                       ('OneFetchStep', [('DISTINCT Any 5,S WHERE S name "deactivated", S is State',
+                       ('OneFetchStep', [('DISTINCT Any S WHERE S name "deactivated", S is State',
                                           [{'S': 'State'}])],
                         None, None, [self.system], {}, []),
                        ]),
@@ -1814,7 +1810,7 @@
                    [('FetchStep', [('Any Y WHERE Y multisource_rel 999998, Y is Note', [{'Y': 'Note'}])],
                      [self.cards], None, {'Y': u'table0.C0'}, []),
                     ('UpdateStep',
-                     [('OneFetchStep', [('DISTINCT Any 999999,Y WHERE Y migrated_from 999998, Y is Note',
+                     [('OneFetchStep', [('DISTINCT Any Y WHERE Y migrated_from 999998, Y is Note',
                                          [{'Y': 'Note'}])],
                        None, None, [self.system],
                        {'Y': u'table0.C0'}, [])])],
@@ -1841,14 +1837,9 @@
     def test_nonregr11(self):
         repo._type_source_cache[999999] = ('Bookmark', 'system', 999999)
         self._test('SET X bookmarked_by Y WHERE X eid %(x)s, Y login "hop"',
-                   [('FetchStep',
-                     [('Any Y WHERE Y login "hop", Y is CWUser', [{'Y': 'CWUser'}])],
-                     [self.ldap, self.system],
-                     None, {'Y': 'table0.C0'}, []),
-                    ('UpdateStep',
-                     [('OneFetchStep', [('DISTINCT Any 999999,Y WHERE Y is CWUser', [{'Y': 'CWUser'}])],
-                       None, None, [self.system], {'Y': 'table0.C0'},
-                       [])]
+                   [('UpdateStep',
+                     [('OneFetchStep', [('DISTINCT Any Y WHERE Y login "hop", Y is CWUser', [{'Y': 'CWUser'}])],
+                       None, None, [self.ldap, self.system], {}, [])]
                      )],
                    {'x': 999999})
 
--- a/server/test/unittest_multisources.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_multisources.py	Mon Mar 08 09:51:29 2010 +0100
@@ -48,7 +48,12 @@
 def teardown_module(*args):
     PyroRQLSource.get_connection = PyroRQLSource_get_connection
     Connection.close = Connection_close
-
+    global repo2, cnx2, repo3, cnx3
+    repo2.shutdown()
+    repo3.shutdown()
+    del repo2, cnx2, repo3, cnx3
+    #del TwoSourcesTC.config.vreg
+    #del TwoSourcesTC.config
 
 class TwoSourcesTC(CubicWebTC):
     config = TwoSourcesConfiguration('data')
@@ -130,7 +135,7 @@
         cu = cnx.cursor()
         rset = cu.execute('Any X WHERE X has_text "card"')
         self.assertEquals(len(rset), 5, zip(rset.rows, rset.description))
-        cnx.close()
+        Connection_close(cnx)
 
     def test_synchronization(self):
         cu = cnx2.cursor()
--- a/server/test/unittest_querier.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_querier.py	Mon Mar 08 09:51:29 2010 +0100
@@ -48,6 +48,11 @@
 
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    cnx.close()
+    repo.shutdown()
+    del repo, cnx
 
 
 class UtilsTC(BaseQuerierTC):
@@ -1023,6 +1028,10 @@
                       {'x': str(eid1), 'y': str(eid2)})
         rset = self.execute('Any X, Y WHERE X travaille Y')
         self.assertEqual(len(rset.rows), 1)
+        # test add of an existant relation but with NOT X rel Y protection
+        self.failIf(self.execute("SET X travaille Y WHERE X eid %(x)s, Y eid %(y)s,"
+                                 "NOT X travaille Y",
+                                 {'x': str(eid1), 'y': str(eid2)}))
 
     def test_update_2ter(self):
         rset = self.execute("INSERT Personne X, Societe Y: X nom 'bidule', Y nom 'toto'")
--- a/server/test/unittest_repository.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_repository.py	Mon Mar 08 09:51:29 2010 +0100
@@ -21,7 +21,7 @@
 from cubicweb import (BadConnectionId, RepositoryError, ValidationError,
                       UnknownEid, AuthenticationError)
 from cubicweb.schema import CubicWebSchema, RQLConstraint
-from cubicweb.dbapi import connect, repo_connect, multiple_connections_unfix
+from cubicweb.dbapi import connect, multiple_connections_unfix
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.repotest import tuplify
 from cubicweb.server import repository, hook
@@ -38,25 +38,29 @@
     """
 
     def test_fill_schema(self):
-        self.repo.schema = CubicWebSchema(self.repo.config.appid)
-        self.repo.config._cubes = None # avoid assertion error
-        self.repo.config.repairing = True # avoid versions checking
-        self.repo.fill_schema()
-        table = SQL_PREFIX + 'CWEType'
-        namecol = SQL_PREFIX + 'name'
-        finalcol = SQL_PREFIX + 'final'
-        self.session.set_pool()
-        cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
-            namecol, table, finalcol))
-        self.assertEquals(cu.fetchall(), [])
-        cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
-                          % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
-        self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
-                                          (u'Date',), (u'Datetime',),
-                                          (u'Decimal',),(u'Float',),
-                                          (u'Int',),
-                                          (u'Interval',), (u'Password',),
-                                          (u'String',), (u'Time',)])
+        origshema = self.repo.schema
+        try:
+            self.repo.schema = CubicWebSchema(self.repo.config.appid)
+            self.repo.config._cubes = None # avoid assertion error
+            self.repo.config.repairing = True # avoid versions checking
+            self.repo.fill_schema()
+            table = SQL_PREFIX + 'CWEType'
+            namecol = SQL_PREFIX + 'name'
+            finalcol = SQL_PREFIX + 'final'
+            self.session.set_pool()
+            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
+                namecol, table, finalcol))
+            self.assertEquals(cu.fetchall(), [])
+            cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
+                                         % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
+            self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
+                                              (u'Date',), (u'Datetime',),
+                                              (u'Decimal',),(u'Float',),
+                                              (u'Int',),
+                                              (u'Interval',), (u'Password',),
+                                              (u'String',), (u'Time',)])
+        finally:
+            self.repo.set_schema(origshema)
 
     def test_schema_has_owner(self):
         repo = self.repo
@@ -263,6 +267,8 @@
                 self.fail('something went wrong, thread still alive')
         finally:
             repository.pyro_unregister(self.repo.config)
+            from logilab.common import pyro_ext
+            pyro_ext._DAEMONS.clear()
 
     def _pyro_client(self, done):
         cnx = connect(self.repo.config.appid, u'admin', password='gingkow')
@@ -470,13 +476,6 @@
                            u'system.version.tag'])
 
 CALLED = []
-class EcritParHook(hook.Hook):
-    __regid__ = 'inlinedrelhook'
-    __select__ = hook.Hook.__select__ & hook.match_rtype('ecrit_par')
-    events = ('before_add_relation', 'after_add_relation',
-              'before_delete_relation', 'after_delete_relation')
-    def __call__(self):
-        CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
 
 class InlineRelHooksTC(CubicWebTC):
     """test relation hooks are called for inlined relations
@@ -491,6 +490,14 @@
 
     def test_inline_relation(self):
         """make sure <event>_relation hooks are called for inlined relation"""
+        class EcritParHook(hook.Hook):
+            __regid__ = 'inlinedrelhook'
+            __select__ = hook.Hook.__select__ & hook.match_rtype('ecrit_par')
+            events = ('before_add_relation', 'after_add_relation',
+                      'before_delete_relation', 'after_delete_relation')
+            def __call__(self):
+                CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
+
         self.hm.register(EcritParHook)
         eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
         eidn = self.execute('INSERT Note X: X type "T"')[0][0]
--- a/server/test/unittest_rql2sql.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_rql2sql.py	Mon Mar 08 09:51:29 2010 +0100
@@ -37,6 +37,10 @@
 schema['state_of'].inlined = False
 schema['comments'].inlined = False
 
+def teardown_module(*args):
+    global config, schema
+    del config, schema
+
 PARSER = [
     (r"Personne P WHERE P nom 'Zig\'oto';",
      '''SELECT _P.cw_eid
--- a/server/test/unittest_rqlannotation.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_rqlannotation.py	Mon Mar 08 09:51:29 2010 +0100
@@ -8,6 +8,11 @@
 
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    del repo, cnx
+
+
 class SQLGenAnnotatorTC(BaseQuerierTC):
     repo = repo
 
--- a/server/test/unittest_schemaserial.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_schemaserial.py	Mon Mar 08 09:51:29 2010 +0100
@@ -15,9 +15,19 @@
 config.bootstrap_cubes()
 schema = loader.load(config)
 
+def teardown_module(*args):
+    global schema, config, loader
+    del schema, config, loader
+
 from cubicweb.server.schemaserial import *
 from cubicweb.server.schemaserial import _erperms2rql as erperms2rql
 
+cstrtypemap = {'RQLConstraint': 'RQLConstraint_eid',
+               'SizeConstraint': 'SizeConstraint_eid',
+               'StaticVocabularyConstraint': 'StaticVocabularyConstraint_eid',
+               'FormatConstraint': 'FormatConstraint_eid',
+               }
+
 class Schema2RQLTC(TestCase):
 
     def test_eschema2rql1(self):
@@ -34,104 +44,124 @@
                  {'description': u'', 'final': True, 'name': u'String'})])
 
     def test_eschema2rql_specialization(self):
+        # x: None since eschema.eid are None
         self.assertListEquals(sorted(specialize2rql(schema)),
-                              [('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
-                                {'et': 'BaseTransition', 'x': 'Transition'}),
-                               ('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
-                                {'et': 'BaseTransition', 'x': 'WorkflowTransition'}),
-                               ('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
-                                {'et': 'Division', 'x': 'SubDivision'}),
-                               # ('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
+                              [('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
+                                {'et': None, 'x': None}),
+                               ('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
+                                {'et': None, 'x': None}),
+                               ('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
+                                {'et': None, 'x': None}),
+                               # ('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
                                #  {'et': 'File', 'x': 'Image'}),
-                               ('SET X specializes ET WHERE X name %(x)s, ET name %(et)s',
-                                {'et': 'Societe', 'x': 'Division'})])
+                               ('SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s',
+                                {'et': None, 'x': None})])
 
     def test_rschema2rql1(self):
-        self.assertListEquals(list(rschema2rql(schema.rschema('relation_type'))),
+        self.assertListEquals(list(rschema2rql(schema.rschema('relation_type'), cstrtypemap)),
                              [
             ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s',
              {'description': u'link a relation definition to its relation type', 'symmetric': False, 'name': u'relation_type', 'final' : False, 'fulltext_container': None, 'inlined': True}),
 
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
-              'ordernum': 1, 'cardinality': u'1*', 'se': 'CWAttribute'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
-             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWAttribute', 'value': u';O;O final TRUE\n'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'', 'composite': u'object', 'cardinality': u'1*',
+              'ordernum': 1}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'RQLConstraint_eid', 'value': u';O;O final TRUE\n'}),
 
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
-              'ordernum': 1, 'cardinality': u'1*', 'se': 'CWRelation'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
-             {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWRelation', 'value': u';O;O final FALSE\n'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'', 'composite': u'object', 
+              'ordernum': 1, 'cardinality': u'1*'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'RQLConstraint_eid', 'value': u';O;O final FALSE\n'}),
             ])
 
     def test_rschema2rql2(self):
-        self.assertListEquals(list(rschema2rql(schema.rschema('add_permission'))),
+        self.assertListEquals(list(rschema2rql(schema.rschema('add_permission'), cstrtypemap)),
                               [
             ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s', {'description': u'', 'symmetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
 
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 9999, 'cardinality': u'**', 'se': 'CWEType'}),
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 9999, 'cardinality': u'*?', 'se': 'CWEType'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'ordernum': 9999, 'cardinality': u'**'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'ordernum': 9999, 'cardinality': u'*?'}),
 
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 9999, 'cardinality': u'**', 'se': 'CWRelation'}),
-            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 9999, 'cardinality': u'*?', 'se': 'CWRelation'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'ordernum': 9999, 'cardinality': u'**'}),
+            ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'ordernum': 9999, 'cardinality': u'*?'}),
             ])
 
     def test_rschema2rql3(self):
-        self.assertListEquals(list(rschema2rql(schema.rschema('cardinality'))),
+        self.assertListEquals(list(rschema2rql(schema.rschema('cardinality'), cstrtypemap)),
                              [
             ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s',
              {'description': u'', 'symmetric': False, 'name': u'cardinality', 'final': True, 'fulltext_container': None, 'inlined': False}),
 
-            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWAttribute'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWAttribute', 'value': u'max=2'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11'"}),
+            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'SizeConstraint_eid', 'value': u'max=2'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'StaticVocabularyConstraint_eid', 'value': u"u'?1', u'11'"}),
 
-            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
-             {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWRelation'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWRelation', 'value': u'max=2'}),
-            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
-             {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWRelation', 'value': u"u'?*', u'1*', u'+*', u'**', u'?+', u'1+', u'++', u'*+', u'?1', u'11', u'+1', u'*1', u'??', u'1?', u'+?', u'*?'"}),
+            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'SizeConstraint_eid', 'value': u'max=2'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'ct': u'StaticVocabularyConstraint_eid', 'value': u"u'?*', u'1*', u'+*', u'**', u'?+', u'1+', u'++', u'*+', u'?1', u'11', u'+1', u'*1', u'??', u'1?', u'+?', u'*?'"}),
             ])
 
+    def test_rdef2rql(self):
+        self.assertListEquals(list(rdef2rql(schema['description_format'].rdefs[('CWRType', 'String')], cstrtypemap)),
+                              [
+            ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE eid %(se)s,ER eid %(rt)s,OE eid %(oe)s',
+             {'se': None, 'rt': None, 'oe': None,
+              'description': u'', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 7, 'defaultval': u'text/plain', 'indexed': False, 'cardinality': u'?1'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'value': u'None', 'ct': 'FormatConstraint_eid'}),
+            ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT eid %(ct)s, EDEF eid %(x)s',
+             {'x': None, 'value': u'max=50', 'ct': 'SizeConstraint_eid'})])
+
 
     def test_updateeschema2rql1(self):
-        self.assertListEquals(list(updateeschema2rql(schema.eschema('CWAttribute'))),
-                              [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
-                                {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the instance schema', 'et': 'CWAttribute', 'final': False, 'name': u'CWAttribute'}),
+        self.assertListEquals(list(updateeschema2rql(schema.eschema('CWAttribute'), 1)),
+                              [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X eid %(x)s',
+                                {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the instance schema', 'x': 1, 'final': False, 'name': u'CWAttribute'}),
                                ])
 
     def test_updateeschema2rql2(self):
-        self.assertListEquals(list(updateeschema2rql(schema.eschema('String'))),
-                              [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
-                                {'description': u'', 'et': 'String', 'final': True, 'name': u'String'})
+        self.assertListEquals(list(updateeschema2rql(schema.eschema('String'), 1)),
+                              [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X eid %(x)s',
+                                {'description': u'', 'x': 1, 'final': True, 'name': u'String'})
                                ])
 
     def test_updaterschema2rql1(self):
-        self.assertListEquals(list(updaterschema2rql(schema.rschema('relation_type'))),
+        self.assertListEquals(list(updaterschema2rql(schema.rschema('relation_type'), 1)),
                              [
-            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X is CWRType, X name %(rt)s',
-             {'rt': 'relation_type', 'symmetric': False,
+            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X eid %(x)s',
+             {'x': 1, 'symmetric': False,
               'description': u'link a relation definition to its relation type',
               'final': False, 'fulltext_container': None, 'inlined': True, 'name': u'relation_type'})
             ])
 
     def test_updaterschema2rql2(self):
         expected = [
-            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X is CWRType, X name %(rt)s',
-             {'rt': 'add_permission', 'symmetric': False,
+            ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X eid %(x)s',
+             {'x': 1, 'symmetric': False,
               'description': u'', 'final': False, 'fulltext_container': None,
               'inlined': False, 'name': u'add_permission'})
             ]
-        for i, (rql, args) in enumerate(updaterschema2rql(schema.rschema('add_permission'))):
+        for i, (rql, args) in enumerate(updaterschema2rql(schema.rschema('add_permission'), 1)):
             yield self.assertEquals, (rql, args), expected[i]
 
 class Perms2RQLTC(TestCase):
@@ -144,29 +174,29 @@
 
     def test_eperms2rql1(self):
         self.assertListEquals([(rql, kwargs) for rql, kwargs in erperms2rql(schema.eschema('CWEType'), self.GROUP_MAPPING)],
-                              [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
-                               ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+                              [('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 1}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 2}),
+                               ('SET X add_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X update_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X delete_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
                                ])
 
     def test_rperms2rql2(self):
         self.assertListEquals([(rql, kwargs) for rql, kwargs in erperms2rql(schema.rschema('read_permission').rdef('CWEType', 'CWGroup'), self.GROUP_MAPPING)],
-                              [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
-                               ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+                              [('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 1}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 2}),
+                               ('SET X add_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X delete_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
                                ])
 
     def test_rperms2rql3(self):
         self.assertListEquals([(rql, kwargs) for rql, kwargs in erperms2rql(schema.rschema('name').rdef('CWEType', 'String'), self.GROUP_MAPPING)],
-                              [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
-                               ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
-                               ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+                              [('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 1}),
+                               ('SET X read_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 2}),
+                               ('SET X update_permission Y WHERE Y eid %(g)s, X eid %(x)s', {'g': 0}),
                                ])
 
     #def test_perms2rql(self):
--- a/server/test/unittest_security.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_security.py	Mon Mar 08 09:51:29 2010 +0100
@@ -456,8 +456,8 @@
         rset = cu.execute('CWUser X WHERE X eid %(x)s', {'x': anon.eid}, 'x')
         self.assertEquals(rset.rows, [[anon.eid]])
         # but can't modify it
-        cu.execute('SET X login "toto" WHERE X eid %(x)s', {'x': anon.eid})
-        self.assertRaises(Unauthorized, cnx.commit)
+        self.assertRaises(Unauthorized,
+                          cu.execute, 'SET X login "toto" WHERE X eid %(x)s', {'x': anon.eid})
 
     def test_in_group_relation(self):
         cnx = self.login('iaminusersgrouponly')
--- a/server/test/unittest_ssplanner.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/server/test/unittest_ssplanner.py	Mon Mar 08 09:51:29 2010 +0100
@@ -12,6 +12,10 @@
 # keep cnx so it's not garbage collected and the associated session closed
 repo, cnx = init_test_database()
 
+def teardown_module(*args):
+    global repo, cnx
+    del repo, cnx
+
 class SSPlannerTC(BasePlannerTC):
     repo = repo
     _test = test_plan
--- a/test/unittest_dbapi.py	Mon Mar 08 09:05:17 2010 +0100
+++ b/test/unittest_dbapi.py	Mon Mar 08 09:51:29 2010 +0100
@@ -35,8 +35,8 @@
         self.assertEquals(cnx.user(None).login, 'anon')
         self.assertEquals(cnx.describe(1), (u'CWGroup', u'system', None))
         self.restore_connection() # proper way to close cnx
-        self.assertRaises(ConnectionError, cnx.user, None)
-        self.assertRaises(ConnectionError, cnx.describe, 1)
+        self.assertRaises(ProgrammingError, cnx.user, None)
+        self.assertRaises(ProgrammingError, cnx.describe, 1)
 
     def test_session_data_api(self):
         cnx = self.login('anon')
@@ -64,9 +64,9 @@
         cnx.set_shared_data('data', 4)
         self.assertEquals(cnx.get_shared_data('data'), 4)
         self.restore_connection() # proper way to close cnx
-        self.assertRaises(ConnectionError, cnx.check)
-        self.assertRaises(ConnectionError, cnx.set_shared_data, 'data', 0)
-        self.assertRaises(ConnectionError, cnx.get_shared_data, 'data')
+        self.assertRaises(ProgrammingError, cnx.check)
+        self.assertRaises(ProgrammingError, cnx.set_shared_data, 'data', 0)
+        self.assertRaises(ProgrammingError, cnx.get_shared_data, 'data')
 
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main