server/sources/native.py
brancholdstable
changeset 6665 90f2f20367bc
parent 6359 0bff5a05385c
child 6366 1806148d6ce8
equal deleted inserted replaced
6018:f4d1d5d9ccbb 6665:90f2f20367bc
    32 from threading import Lock
    32 from threading import Lock
    33 from datetime import datetime
    33 from datetime import datetime
    34 from base64 import b64decode, b64encode
    34 from base64 import b64decode, b64encode
    35 from contextlib import contextmanager
    35 from contextlib import contextmanager
    36 from os.path import abspath
    36 from os.path import abspath
       
    37 import re
    37 
    38 
    38 from logilab.common.compat import any
    39 from logilab.common.compat import any
    39 from logilab.common.cache import Cache
    40 from logilab.common.cache import Cache
    40 from logilab.common.decorators import cached, clear_cache
    41 from logilab.common.decorators import cached, clear_cache
    41 from logilab.common.configuration import Method
    42 from logilab.common.configuration import Method
    42 from logilab.common.shellutils import getlogin
    43 from logilab.common.shellutils import getlogin
    43 from logilab.database import get_db_helper
    44 from logilab.database import get_db_helper
    44 
    45 
    45 from cubicweb import UnknownEid, AuthenticationError, ValidationError, Binary
    46 from yams import schema2sql as y2sql
       
    47 
       
    48 from cubicweb import UnknownEid, AuthenticationError, ValidationError, Binary, UniqueTogetherError
    46 from cubicweb import transaction as tx, server, neg_role
    49 from cubicweb import transaction as tx, server, neg_role
    47 from cubicweb.schema import VIRTUAL_RTYPES
    50 from cubicweb.schema import VIRTUAL_RTYPES
    48 from cubicweb.cwconfig import CubicWebNoAppConfiguration
    51 from cubicweb.cwconfig import CubicWebNoAppConfiguration
    49 from cubicweb.server import hook
    52 from cubicweb.server import hook
    50 from cubicweb.server.utils import crypt_password, eschema_eid
    53 from cubicweb.server.utils import crypt_password, eschema_eid
   125                                  ' OR '.join(clauses))
   128                                  ' OR '.join(clauses))
   126     else:
   129     else:
   127         restr = '(%s)' % ' OR '.join(clauses)
   130         restr = '(%s)' % ' OR '.join(clauses)
   128     return '%s WHERE %s' % (select, restr)
   131     return '%s WHERE %s' % (select, restr)
   129 
   132 
       
   133 def rdef_table_column(rdef):
       
   134     """return table and column used to store the given relation definition in
       
   135     the database
       
   136     """
       
   137     return (SQL_PREFIX + str(rdef.subject),
       
   138             SQL_PREFIX + str(rdef.rtype))
       
   139 
       
   140 def rdef_physical_info(dbhelper, rdef):
       
   141     """return backend type and a boolean flag if NULL values should be allowed
       
   142     for a given relation definition
       
   143     """
       
   144     if rdef.object.final:
       
   145         ttype = rdef.object
       
   146     else:
       
   147         ttype = 'Int' # eid type
       
   148     coltype = y2sql.type_from_constraints(dbhelper, ttype,
       
   149                                           rdef.constraints, creating=False)
       
   150     allownull = rdef.cardinality[0] != '1'
       
   151     return coltype, allownull
   130 
   152 
   131 class UndoException(Exception):
   153 class UndoException(Exception):
   132     """something went wrong during undoing"""
   154     """something went wrong during undoing"""
   133 
   155 
   134 
   156 
   191         ('db-driver',
   213         ('db-driver',
   192          {'type' : 'string',
   214          {'type' : 'string',
   193           'default': 'postgres',
   215           'default': 'postgres',
   194           # XXX use choice type
   216           # XXX use choice type
   195           'help': 'database driver (postgres, mysql, sqlite, sqlserver2005)',
   217           'help': 'database driver (postgres, mysql, sqlite, sqlserver2005)',
   196           'group': 'native-source', 'level': 1,
   218           'group': 'native-source', 'level': 0,
   197           }),
   219           }),
   198         ('db-host',
   220         ('db-host',
   199          {'type' : 'string',
   221          {'type' : 'string',
   200           'default': '',
   222           'default': '',
   201           'help': 'database host',
   223           'help': 'database host',
   486         self.doexec(session, query, self.merge_args(args, qargs))
   508         self.doexec(session, query, self.merge_args(args, qargs))
   487 
   509 
   488     def manual_insert(self, results, table, session):
   510     def manual_insert(self, results, table, session):
   489         """insert given result into a temporary table on the system source"""
   511         """insert given result into a temporary table on the system source"""
   490         if server.DEBUG & server.DBG_RQL:
   512         if server.DEBUG & server.DBG_RQL:
   491             print '  manual insertion of', results, 'into', table
   513             print '  manual insertion of', len(results), 'results into', table
   492         if not results:
   514         if not results:
   493             return
   515             return
   494         query_args = ['%%(%s)s' % i for i in xrange(len(results[0]))]
   516         query_args = ['%%(%s)s' % i for i in xrange(len(results[0]))]
   495         query = 'INSERT INTO %s VALUES(%s)' % (table, ','.join(query_args))
   517         query = 'INSERT INTO %s VALUES(%s)' % (table, ','.join(query_args))
   496         kwargs_list = []
   518         kwargs_list = []
   647                     session.pool.connection(self.uri).rollback()
   669                     session.pool.connection(self.uri).rollback()
   648                     if self.repo.config.mode != 'test':
   670                     if self.repo.config.mode != 'test':
   649                         self.critical('transaction has been rollbacked')
   671                         self.critical('transaction has been rollbacked')
   650                 except:
   672                 except:
   651                     pass
   673                     pass
       
   674             if ex.__class__.__name__ == 'IntegrityError':
       
   675                 # need string comparison because of various backends
       
   676                 for arg in ex.args:
       
   677                     mo = re.search('unique_cw_[^ ]+_idx', arg)
       
   678                     if mo is not None:
       
   679                         index_name = mo.group(0)
       
   680                         elements = index_name.rstrip('_idx').split('_cw_')[1:]
       
   681                         etype = elements[0]
       
   682                         rtypes = elements[1:]
       
   683                         raise UniqueTogetherError(etype, rtypes)
       
   684                     mo = re.search('columns (.*) are not unique', arg)
       
   685                     if mo is not None: # sqlite in use
       
   686                         rtypes = [c.strip().lstrip('cw_') for c in mo.group(1).split(',')]
       
   687                         etype = '???'
       
   688                         raise UniqueTogetherError(etype, rtypes)
   652             raise
   689             raise
   653         return cursor
   690         return cursor
   654 
   691 
   655     def doexecmany(self, session, query, args):
   692     def doexecmany(self, session, query, args):
   656         """Execute a query.
   693         """Execute a query.
   676                 pass
   713                 pass
   677             raise
   714             raise
   678 
   715 
   679     # short cut to method requiring advanced db helper usage ##################
   716     # short cut to method requiring advanced db helper usage ##################
   680 
   717 
       
   718     def update_rdef_column(self, session, rdef):
       
   719         """update physical column for a relation definition (final or inlined)
       
   720         """
       
   721         table, column = rdef_table_column(rdef)
       
   722         coltype, allownull = rdef_physical_info(self.dbhelper, rdef)
       
   723         if not self.dbhelper.alter_column_support:
       
   724             self.error("backend can't alter %s.%s to %s%s", table, column, coltype,
       
   725                        not allownull and 'NOT NULL' or '')
       
   726             return
       
   727         self.dbhelper.change_col_type(LogCursor(session.pool[self.uri]),
       
   728                                       table, column, coltype, allownull)
       
   729         self.info('altered %s.%s: now %s%s', table, column, coltype,
       
   730                   not allownull and 'NOT NULL' or '')
       
   731 
       
   732     def update_rdef_null_allowed(self, session, rdef):
       
   733         """update NULL / NOT NULL of physical column for a relation definition
       
   734         (final or inlined)
       
   735         """
       
   736         if not self.dbhelper.alter_column_support:
       
   737             # not supported (and NOT NULL not set by yams in that case, so no
       
   738             # worry)
       
   739             return
       
   740         table, column = rdef_table_column(rdef)
       
   741         coltype, allownull = rdef_physical_info(self.dbhelper, rdef)
       
   742         self.dbhelper.set_null_allowed(LogCursor(session.pool[self.uri]),
       
   743                                        table, column, coltype, allownull)
       
   744 
       
   745     def update_rdef_indexed(self, session, rdef):
       
   746         table, column = rdef_table_column(rdef)
       
   747         if rdef.indexed:
       
   748             self.create_index(session, table, column)
       
   749         else:
       
   750             self.drop_index(session, table, column)
       
   751 
       
   752     def update_rdef_unique(self, session, rdef):
       
   753         table, column = rdef_table_column(rdef)
       
   754         if rdef.constraint_by_type('UniqueConstraint'):
       
   755             self.create_index(session, table, column, unique=True)
       
   756         else:
       
   757             self.drop_index(session, table, column, unique=True)
       
   758 
   681     def create_index(self, session, table, column, unique=False):
   759     def create_index(self, session, table, column, unique=False):
   682         cursor = LogCursor(session.pool[self.uri])
   760         cursor = LogCursor(session.pool[self.uri])
   683         self.dbhelper.create_index(cursor, table, column, unique)
   761         self.dbhelper.create_index(cursor, table, column, unique)
   684 
   762 
   685     def drop_index(self, session, table, column, unique=False):
   763     def drop_index(self, session, table, column, unique=False):
   686         cursor = LogCursor(session.pool[self.uri])
   764         cursor = LogCursor(session.pool[self.uri])
   687         self.dbhelper.drop_index(cursor, table, column, unique)
   765         self.dbhelper.drop_index(cursor, table, column, unique)
   688 
       
   689     def change_col_type(self, session, table, column, coltype, null_allowed):
       
   690         cursor = LogCursor(session.pool[self.uri])
       
   691         self.dbhelper.change_col_type(cursor, table, column, coltype, null_allowed)
       
   692 
       
   693     def set_null_allowed(self, session, table, column, coltype, null_allowed):
       
   694         cursor = LogCursor(session.pool[self.uri])
       
   695         self.dbhelper.set_null_allowed(cursor, table, column, coltype, null_allowed)
       
   696 
   766 
   697     # system source interface #################################################
   767     # system source interface #################################################
   698 
   768 
   699     def eid_type_source(self, session, eid):
   769     def eid_type_source(self, session, eid):
   700         """return a tuple (type, source, extid) for the entity with id <eid>"""
   770         """return a tuple (type, source, extid) for the entity with id <eid>"""
   798             self.exception('create eid failed in an unforeseen way on SQL statement %s', sql)
   868             self.exception('create eid failed in an unforeseen way on SQL statement %s', sql)
   799             raise
   869             raise
   800         else:
   870         else:
   801             cnx.commit()
   871             cnx.commit()
   802             return eid
   872             return eid
   803 
       
   804 
   873 
   805     def add_info(self, session, entity, source, extid, complete):
   874     def add_info(self, session, entity, source, extid, complete):
   806         """add type and source info for an eid into the system table"""
   875         """add type and source info for an eid into the system table"""
   807         # begin by inserting eid/type/source/extid into the entities table
   876         # begin by inserting eid/type/source/extid into the entities table
   808         if extid is not None:
   877         if extid is not None:
  1077                 entity[rtype] = Binary(value)
  1146                 entity[rtype] = Binary(value)
  1078             elif isinstance(value, str):
  1147             elif isinstance(value, str):
  1079                 entity[rtype] = unicode(value, session.encoding, 'replace')
  1148                 entity[rtype] = unicode(value, session.encoding, 'replace')
  1080             else:
  1149             else:
  1081                 entity[rtype] = value
  1150                 entity[rtype] = value
  1082         entity.set_eid(eid)
  1151         entity.eid = eid
  1083         session.repo.init_entity_caches(session, entity, self)
  1152         session.repo.init_entity_caches(session, entity, self)
  1084         entity.edited_attributes = set(entity)
  1153         entity.edited_attributes = set(entity)
  1085         entity.check()
  1154         entity._cw_check()
  1086         self.repo.hm.call_hooks('before_add_entity', session, entity=entity)
  1155         self.repo.hm.call_hooks('before_add_entity', session, entity=entity)
  1087         # restore the entity
  1156         # restore the entity
  1088         action.changes['cw_eid'] = eid
  1157         action.changes['cw_eid'] = eid
  1089         sql = self.sqlgen.insert(SQL_PREFIX + etype, action.changes)
  1158         sql = self.sqlgen.insert(SQL_PREFIX + etype, action.changes)
  1090         self.doexec(session, sql, action.changes)
  1159         self.doexec(session, sql, action.changes)
  1147             entity = self.repo.vreg['etypes'].etype_class(etype)(session)
  1216             entity = self.repo.vreg['etypes'].etype_class(etype)(session)
  1148         except Exception:
  1217         except Exception:
  1149             return [session._(
  1218             return [session._(
  1150                 "Can't undo creation of entity %(eid)s of type %(etype)s, type "
  1219                 "Can't undo creation of entity %(eid)s of type %(etype)s, type "
  1151                 "no more supported" % {'eid': eid, 'etype': etype})]
  1220                 "no more supported" % {'eid': eid, 'etype': etype})]
  1152         entity.set_eid(eid)
  1221         entity.eid = eid
  1153         # for proper eid/type cache update
  1222         # for proper eid/type cache update
  1154         hook.set_operation(session, 'pendingeids', eid,
  1223         hook.set_operation(session, 'pendingeids', eid,
  1155                            CleanupDeletedEidsCacheOp)
  1224                            CleanupDeletedEidsCacheOp)
  1156         self.repo.hm.call_hooks('before_delete_entity', session, entity=entity)
  1225         self.repo.hm.call_hooks('before_delete_entity', session, entity=entity)
  1157         # remove is / is_instance_of which are added using sql by hooks, hence
  1226         # remove is / is_instance_of which are added using sql by hooks, hence
  1235         """
  1304         """
  1236         self.debug('reindexing %r', entity.eid)
  1305         self.debug('reindexing %r', entity.eid)
  1237         try:
  1306         try:
  1238             # use cursor_index_object, not cursor_reindex_object since
  1307             # use cursor_index_object, not cursor_reindex_object since
  1239             # unindexing done in the FTIndexEntityOp
  1308             # unindexing done in the FTIndexEntityOp
  1240             self.dbhelper.cursor_index_object(entity.eid, entity,
  1309             self.dbhelper.cursor_index_object(entity.eid,
       
  1310                                               entity.cw_adapt_to('IFTIndexable'),
  1241                                               session.pool['system'])
  1311                                               session.pool['system'])
  1242         except Exception: # let KeyboardInterrupt / SystemExit propagate
  1312         except Exception: # let KeyboardInterrupt / SystemExit propagate
  1243             self.exception('error while reindexing %s', entity)
  1313             self.exception('error while reindexing %s', entity)
  1244 
  1314 
  1245 
  1315 
  1260             if eid in pendingeids or eid in done:
  1330             if eid in pendingeids or eid in done:
  1261                 # entity added and deleted in the same transaction or already
  1331                 # entity added and deleted in the same transaction or already
  1262                 # processed
  1332                 # processed
  1263                 return
  1333                 return
  1264             done.add(eid)
  1334             done.add(eid)
  1265             for container in session.entity_from_eid(eid).fti_containers():
  1335             iftindexable = session.entity_from_eid(eid).cw_adapt_to('IFTIndexable')
       
  1336             for container in iftindexable.fti_containers():
  1266                 source.fti_unindex_entity(session, container.eid)
  1337                 source.fti_unindex_entity(session, container.eid)
  1267                 source.fti_index_entity(session, container)
  1338                 source.fti_index_entity(session, container)
  1268 
  1339 
  1269 
  1340 
  1270 def sql_schema(driver):
  1341 def sql_schema(driver):