server/sources/native.py
brancholdstable
changeset 6665 90f2f20367bc
parent 6359 0bff5a05385c
child 6366 1806148d6ce8
--- a/server/sources/native.py	Tue Jul 27 12:36:03 2010 +0200
+++ b/server/sources/native.py	Wed Nov 03 16:38:28 2010 +0100
@@ -34,6 +34,7 @@
 from base64 import b64decode, b64encode
 from contextlib import contextmanager
 from os.path import abspath
+import re
 
 from logilab.common.compat import any
 from logilab.common.cache import Cache
@@ -42,7 +43,9 @@
 from logilab.common.shellutils import getlogin
 from logilab.database import get_db_helper
 
-from cubicweb import UnknownEid, AuthenticationError, ValidationError, Binary
+from yams import schema2sql as y2sql
+
+from cubicweb import UnknownEid, AuthenticationError, ValidationError, Binary, UniqueTogetherError
 from cubicweb import transaction as tx, server, neg_role
 from cubicweb.schema import VIRTUAL_RTYPES
 from cubicweb.cwconfig import CubicWebNoAppConfiguration
@@ -127,6 +130,25 @@
         restr = '(%s)' % ' OR '.join(clauses)
     return '%s WHERE %s' % (select, restr)
 
+def rdef_table_column(rdef):
+    """return table and column used to store the given relation definition in
+    the database
+    """
+    return (SQL_PREFIX + str(rdef.subject),
+            SQL_PREFIX + str(rdef.rtype))
+
+def rdef_physical_info(dbhelper, rdef):
+    """return backend type and a boolean flag if NULL values should be allowed
+    for a given relation definition
+    """
+    if rdef.object.final:
+        ttype = rdef.object
+    else:
+        ttype = 'Int' # eid type
+    coltype = y2sql.type_from_constraints(dbhelper, ttype,
+                                          rdef.constraints, creating=False)
+    allownull = rdef.cardinality[0] != '1'
+    return coltype, allownull
 
 class UndoException(Exception):
     """something went wrong during undoing"""
@@ -193,7 +215,7 @@
           'default': 'postgres',
           # XXX use choice type
           'help': 'database driver (postgres, mysql, sqlite, sqlserver2005)',
-          'group': 'native-source', 'level': 1,
+          'group': 'native-source', 'level': 0,
           }),
         ('db-host',
          {'type' : 'string',
@@ -488,7 +510,7 @@
     def manual_insert(self, results, table, session):
         """insert given result into a temporary table on the system source"""
         if server.DEBUG & server.DBG_RQL:
-            print '  manual insertion of', results, 'into', table
+            print '  manual insertion of', len(results), 'results into', table
         if not results:
             return
         query_args = ['%%(%s)s' % i for i in xrange(len(results[0]))]
@@ -649,6 +671,21 @@
                         self.critical('transaction has been rollbacked')
                 except:
                     pass
+            if ex.__class__.__name__ == 'IntegrityError':
+                # need string comparison because of various backends
+                for arg in ex.args:
+                    mo = re.search('unique_cw_[^ ]+_idx', arg)
+                    if mo is not None:
+                        index_name = mo.group(0)
+                        elements = index_name.rstrip('_idx').split('_cw_')[1:]
+                        etype = elements[0]
+                        rtypes = elements[1:]
+                        raise UniqueTogetherError(etype, rtypes)
+                    mo = re.search('columns (.*) are not unique', arg)
+                    if mo is not None: # sqlite in use
+                        rtypes = [c.strip().lstrip('cw_') for c in mo.group(1).split(',')]
+                        etype = '???'
+                        raise UniqueTogetherError(etype, rtypes)
             raise
         return cursor
 
@@ -678,6 +715,47 @@
 
     # short cut to method requiring advanced db helper usage ##################
 
+    def update_rdef_column(self, session, rdef):
+        """update physical column for a relation definition (final or inlined)
+        """
+        table, column = rdef_table_column(rdef)
+        coltype, allownull = rdef_physical_info(self.dbhelper, rdef)
+        if not self.dbhelper.alter_column_support:
+            self.error("backend can't alter %s.%s to %s%s", table, column, coltype,
+                       not allownull and 'NOT NULL' or '')
+            return
+        self.dbhelper.change_col_type(LogCursor(session.pool[self.uri]),
+                                      table, column, coltype, allownull)
+        self.info('altered %s.%s: now %s%s', table, column, coltype,
+                  not allownull and 'NOT NULL' or '')
+
+    def update_rdef_null_allowed(self, session, rdef):
+        """update NULL / NOT NULL of physical column for a relation definition
+        (final or inlined)
+        """
+        if not self.dbhelper.alter_column_support:
+            # not supported (and NOT NULL not set by yams in that case, so no
+            # worry)
+            return
+        table, column = rdef_table_column(rdef)
+        coltype, allownull = rdef_physical_info(self.dbhelper, rdef)
+        self.dbhelper.set_null_allowed(LogCursor(session.pool[self.uri]),
+                                       table, column, coltype, allownull)
+
+    def update_rdef_indexed(self, session, rdef):
+        table, column = rdef_table_column(rdef)
+        if rdef.indexed:
+            self.create_index(session, table, column)
+        else:
+            self.drop_index(session, table, column)
+
+    def update_rdef_unique(self, session, rdef):
+        table, column = rdef_table_column(rdef)
+        if rdef.constraint_by_type('UniqueConstraint'):
+            self.create_index(session, table, column, unique=True)
+        else:
+            self.drop_index(session, table, column, unique=True)
+
     def create_index(self, session, table, column, unique=False):
         cursor = LogCursor(session.pool[self.uri])
         self.dbhelper.create_index(cursor, table, column, unique)
@@ -686,14 +764,6 @@
         cursor = LogCursor(session.pool[self.uri])
         self.dbhelper.drop_index(cursor, table, column, unique)
 
-    def change_col_type(self, session, table, column, coltype, null_allowed):
-        cursor = LogCursor(session.pool[self.uri])
-        self.dbhelper.change_col_type(cursor, table, column, coltype, null_allowed)
-
-    def set_null_allowed(self, session, table, column, coltype, null_allowed):
-        cursor = LogCursor(session.pool[self.uri])
-        self.dbhelper.set_null_allowed(cursor, table, column, coltype, null_allowed)
-
     # system source interface #################################################
 
     def eid_type_source(self, session, eid):
@@ -801,7 +871,6 @@
             cnx.commit()
             return eid
 
-
     def add_info(self, session, entity, source, extid, complete):
         """add type and source info for an eid into the system table"""
         # begin by inserting eid/type/source/extid into the entities table
@@ -1079,10 +1148,10 @@
                 entity[rtype] = unicode(value, session.encoding, 'replace')
             else:
                 entity[rtype] = value
-        entity.set_eid(eid)
+        entity.eid = eid
         session.repo.init_entity_caches(session, entity, self)
         entity.edited_attributes = set(entity)
-        entity.check()
+        entity._cw_check()
         self.repo.hm.call_hooks('before_add_entity', session, entity=entity)
         # restore the entity
         action.changes['cw_eid'] = eid
@@ -1149,7 +1218,7 @@
             return [session._(
                 "Can't undo creation of entity %(eid)s of type %(etype)s, type "
                 "no more supported" % {'eid': eid, 'etype': etype})]
-        entity.set_eid(eid)
+        entity.eid = eid
         # for proper eid/type cache update
         hook.set_operation(session, 'pendingeids', eid,
                            CleanupDeletedEidsCacheOp)
@@ -1237,7 +1306,8 @@
         try:
             # use cursor_index_object, not cursor_reindex_object since
             # unindexing done in the FTIndexEntityOp
-            self.dbhelper.cursor_index_object(entity.eid, entity,
+            self.dbhelper.cursor_index_object(entity.eid,
+                                              entity.cw_adapt_to('IFTIndexable'),
                                               session.pool['system'])
         except Exception: # let KeyboardInterrupt / SystemExit propagate
             self.exception('error while reindexing %s', entity)
@@ -1262,7 +1332,8 @@
                 # processed
                 return
             done.add(eid)
-            for container in session.entity_from_eid(eid).fti_containers():
+            iftindexable = session.entity_from_eid(eid).cw_adapt_to('IFTIndexable')
+            for container in iftindexable.fti_containers():
                 source.fti_unindex_entity(session, container.eid)
                 source.fti_index_entity(session, container)