server/sources/native.py
changeset 9450 af4b93bc38a5
parent 9448 3e7cad3967c5
child 9451 c83a8ecb9bf5
equal deleted inserted replaced
9449:287a05ec7ab1 9450:af4b93bc38a5
   110             # assert not schema(ttype).final
   110             # assert not schema(ttype).final
   111             sql.append('%s %s' % (name, typemap['Int']))
   111             sql.append('%s %s' % (name, typemap['Int']))
   112     return ','.join(sql), varmap
   112     return ','.join(sql), varmap
   113 
   113 
   114 
   114 
   115 def _modified_sql(table, etypes):
       
   116     # XXX protect against sql injection
       
   117     if len(etypes) > 1:
       
   118         restr = 'type IN (%s)' % ','.join("'%s'" % etype for etype in etypes)
       
   119     else:
       
   120         restr = "type='%s'" % etypes[0]
       
   121     if table == 'entities':
       
   122         attr = 'mtime'
       
   123     else:
       
   124         attr = 'dtime'
       
   125     return 'SELECT type, eid FROM %s WHERE %s AND %s > %%(time)s' % (
       
   126         table, restr, attr)
       
   127 
       
   128 
       
   129 def sql_or_clauses(sql, clauses):
   115 def sql_or_clauses(sql, clauses):
   130     select, restr = sql.split(' WHERE ', 1)
   116     select, restr = sql.split(' WHERE ', 1)
   131     restrclauses = restr.split(' AND ')
   117     restrclauses = restr.split(' AND ')
   132     for clause in clauses:
   118     for clause in clauses:
   133         restrclauses.remove(clause)
   119         restrclauses.remove(clause)
   136                                  ' OR '.join(clauses))
   122                                  ' OR '.join(clauses))
   137     else:
   123     else:
   138         restr = '(%s)' % ' OR '.join(clauses)
   124         restr = '(%s)' % ' OR '.join(clauses)
   139     return '%s WHERE %s' % (select, restr)
   125     return '%s WHERE %s' % (select, restr)
   140 
   126 
       
   127 
   141 def rdef_table_column(rdef):
   128 def rdef_table_column(rdef):
   142     """return table and column used to store the given relation definition in
   129     """return table and column used to store the given relation definition in
   143     the database
   130     the database
   144     """
   131     """
   145     return (SQL_PREFIX + str(rdef.subject),
   132     return (SQL_PREFIX + str(rdef.subject),
   146             SQL_PREFIX + str(rdef.rtype))
   133             SQL_PREFIX + str(rdef.rtype))
       
   134 
   147 
   135 
   148 def rdef_physical_info(dbhelper, rdef):
   136 def rdef_physical_info(dbhelper, rdef):
   149     """return backend type and a boolean flag if NULL values should be allowed
   137     """return backend type and a boolean flag if NULL values should be allowed
   150     for a given relation definition
   138     for a given relation definition
   151     """
   139     """
   297         # explain)
   285         # explain)
   298         self._eid_cnx_lock = Lock()
   286         self._eid_cnx_lock = Lock()
   299         self._eid_creation_cnx = None
   287         self._eid_creation_cnx = None
   300         # (etype, attr) / storage mapping
   288         # (etype, attr) / storage mapping
   301         self._storages = {}
   289         self._storages = {}
   302         # entity types that may be used by other multi-sources instances
       
   303         self.multisources_etypes = set(repo.config['multi-sources-etypes'])
       
   304         # XXX no_sqlite_wrap trick since we've a sqlite locking pb when
   290         # XXX no_sqlite_wrap trick since we've a sqlite locking pb when
   305         # running unittest_multisources with the wrapping below
   291         # running unittest_multisources with the wrapping below
   306         if self.dbdriver == 'sqlite' and \
   292         if self.dbdriver == 'sqlite' and \
   307                not getattr(repo.config, 'no_sqlite_wrap', False):
   293                not getattr(repo.config, 'no_sqlite_wrap', False):
   308             self.dbhelper.dbname = abspath(self.dbhelper.dbname)
   294             self.dbhelper.dbname = abspath(self.dbhelper.dbname)
   966         with session.ensure_cnx_set:
   952         with session.ensure_cnx_set:
   967             # begin by inserting eid/type/source/extid into the entities table
   953             # begin by inserting eid/type/source/extid into the entities table
   968             if extid is not None:
   954             if extid is not None:
   969                 assert isinstance(extid, str)
   955                 assert isinstance(extid, str)
   970                 extid = b64encode(extid)
   956                 extid = b64encode(extid)
   971             uri = 'system'
       
   972             attrs = {'type': entity.cw_etype, 'eid': entity.eid, 'extid': extid,
   957             attrs = {'type': entity.cw_etype, 'eid': entity.eid, 'extid': extid,
   973                      'source': uri, 'asource': source.uri, 'mtime': datetime.utcnow()}
   958                      'source': 'system', 'asource': source.uri}
   974             self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
   959             self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
   975             # insert core relations: is, is_instance_of and cw_source
   960             # insert core relations: is, is_instance_of and cw_source
   976             try:
   961             try:
   977                 self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
   962                 self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
   978                                              (entity.eid, eschema_eid(session, entity.e_schema)))
   963                                              (entity.eid, eschema_eid(session, entity.e_schema)))
   997         """mark entity as being modified, fulltext reindex if needed"""
   982         """mark entity as being modified, fulltext reindex if needed"""
   998         if self.do_fti and need_fti_update:
   983         if self.do_fti and need_fti_update:
   999             # reindex the entity only if this query is updating at least
   984             # reindex the entity only if this query is updating at least
  1000             # one indexable attribute
   985             # one indexable attribute
  1001             self.index_entity(session, entity=entity)
   986             self.index_entity(session, entity=entity)
  1002         # update entities.mtime.
       
  1003         # XXX Only if entity.cw_etype in self.multisources_etypes?
       
  1004         attrs = {'eid': entity.eid, 'mtime': datetime.utcnow()}
       
  1005         self.doexec(session, self.sqlgen.update('entities', attrs, ['eid']), attrs)
       
  1006 
   987 
  1007     def delete_info_multi(self, session, entities, uri):
   988     def delete_info_multi(self, session, entities, uri):
  1008         """delete system information on deletion of a list of entities with the
   989         """delete system information on deletion of a list of entities with the
  1009         same etype and belinging to the same source
   990         same etype and belinging to the same source
  1010 
   991 
  1011         * update the fti
   992         * update the fti
  1012         * remove record from the `entities` table
   993         * remove record from the `entities` table
  1013         * transfer it to the `deleted_entities`
       
  1014         """
   994         """
  1015         self.fti_unindex_entities(session, entities)
   995         self.fti_unindex_entities(session, entities)
  1016         attrs = {'eid': '(%s)' % ','.join([str(_e.eid) for _e in entities])}
   996         attrs = {'eid': '(%s)' % ','.join([str(_e.eid) for _e in entities])}
  1017         self.doexec(session, self.sqlgen.delete_many('entities', attrs), attrs)
   997         self.doexec(session, self.sqlgen.delete_many('entities', attrs), attrs)
  1018         if entities[0].__regid__ not in self.multisources_etypes:
       
  1019             return
       
  1020         attrs = {'type': entities[0].__regid__,
       
  1021                  'source': uri, 'dtime': datetime.utcnow()}
       
  1022         for entity in entities:
       
  1023             extid = entity.cw_metainformation()['extid']
       
  1024             if extid is not None:
       
  1025                 assert isinstance(extid, str), type(extid)
       
  1026                 extid = b64encode(extid)
       
  1027             attrs.update({'eid': entity.eid, 'extid': extid})
       
  1028             self.doexec(session, self.sqlgen.insert('deleted_entities', attrs), attrs)
       
  1029 
       
  1030     def modified_entities(self, session, etypes, mtime):
       
  1031         """return a 2-uple:
       
  1032         * list of (etype, eid) of entities of the given types which have been
       
  1033           modified since the given timestamp (actually entities whose full text
       
  1034           index content has changed)
       
  1035         * list of (etype, eid) of entities of the given types which have been
       
  1036           deleted since the given timestamp
       
  1037         """
       
  1038         for etype in etypes:
       
  1039             if not etype in self.multisources_etypes:
       
  1040                 self.error('%s not listed as a multi-sources entity types. '
       
  1041                               'Modify your configuration' % etype)
       
  1042                 self.multisources_etypes.add(etype)
       
  1043         modsql = _modified_sql('entities', etypes)
       
  1044         cursor = self.doexec(session, modsql, {'time': mtime})
       
  1045         modentities = cursor.fetchall()
       
  1046         delsql = _modified_sql('deleted_entities', etypes)
       
  1047         cursor = self.doexec(session, delsql, {'time': mtime})
       
  1048         delentities = cursor.fetchall()
       
  1049         return modentities, delentities
       
  1050 
   998 
  1051     # undo support #############################################################
   999     # undo support #############################################################
  1052 
  1000 
  1053     def undoable_transactions(self, session, ueid=None, **actionfilters):
  1001     def undoable_transactions(self, session, ueid=None, **actionfilters):
  1054         """See :class:`cubicweb.dbapi.Connection.undoable_transactions`"""
  1002         """See :class:`cubicweb.dbapi.Connection.undoable_transactions`"""
  1292         action.changes['cw_eid'] = eid
  1240         action.changes['cw_eid'] = eid
  1293         sql = self.sqlgen.insert(SQL_PREFIX + etype, action.changes)
  1241         sql = self.sqlgen.insert(SQL_PREFIX + etype, action.changes)
  1294         self.doexec(session, sql, action.changes)
  1242         self.doexec(session, sql, action.changes)
  1295         # restore record in entities (will update fti if needed)
  1243         # restore record in entities (will update fti if needed)
  1296         self.add_info(session, entity, self, None, True)
  1244         self.add_info(session, entity, self, None, True)
  1297         # remove record from deleted_entities if entity's type is multi-sources
       
  1298         if entity.cw_etype in self.multisources_etypes:
       
  1299             self.doexec(session,
       
  1300                         'DELETE FROM deleted_entities WHERE eid=%s' % eid)
       
  1301         self.repo.hm.call_hooks('after_add_entity', session, entity=entity)
  1245         self.repo.hm.call_hooks('after_add_entity', session, entity=entity)
  1302         return errors
  1246         return errors
  1303 
  1247 
  1304     def _undo_r(self, session, action):
  1248     def _undo_r(self, session, action):
  1305         """undo a relation removal"""
  1249         """undo a relation removal"""
  1497 CREATE TABLE entities (
  1441 CREATE TABLE entities (
  1498   eid INTEGER PRIMARY KEY NOT NULL,
  1442   eid INTEGER PRIMARY KEY NOT NULL,
  1499   type VARCHAR(64) NOT NULL,
  1443   type VARCHAR(64) NOT NULL,
  1500   source VARCHAR(128) NOT NULL,
  1444   source VARCHAR(128) NOT NULL,
  1501   asource VARCHAR(128) NOT NULL,
  1445   asource VARCHAR(128) NOT NULL,
  1502   mtime %s NOT NULL,
       
  1503   extid VARCHAR(256)
  1446   extid VARCHAR(256)
  1504 );;
  1447 );;
  1505 CREATE INDEX entities_type_idx ON entities(type);;
  1448 CREATE INDEX entities_type_idx ON entities(type);;
  1506 CREATE INDEX entities_mtime_idx ON entities(mtime);;
       
  1507 CREATE INDEX entities_extid_idx ON entities(extid);;
  1449 CREATE INDEX entities_extid_idx ON entities(extid);;
  1508 
       
  1509 CREATE TABLE deleted_entities (
       
  1510   eid INTEGER PRIMARY KEY NOT NULL,
       
  1511   type VARCHAR(64) NOT NULL,
       
  1512   source VARCHAR(128) NOT NULL,
       
  1513   dtime %s NOT NULL,
       
  1514   extid VARCHAR(256)
       
  1515 );;
       
  1516 CREATE INDEX deleted_entities_type_idx ON deleted_entities(type);;
       
  1517 CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime);;
       
  1518 CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid);;
       
  1519 
  1450 
  1520 CREATE TABLE transactions (
  1451 CREATE TABLE transactions (
  1521   tx_uuid CHAR(32) PRIMARY KEY NOT NULL,
  1452   tx_uuid CHAR(32) PRIMARY KEY NOT NULL,
  1522   tx_user INTEGER NOT NULL,
  1453   tx_user INTEGER NOT NULL,
  1523   tx_time %s NOT NULL
  1454   tx_time %s NOT NULL
  1553 CREATE INDEX tx_relation_actions_txa_public_idx ON tx_relation_actions(txa_public);;
  1484 CREATE INDEX tx_relation_actions_txa_public_idx ON tx_relation_actions(txa_public);;
  1554 CREATE INDEX tx_relation_actions_eid_from_idx ON tx_relation_actions(eid_from);;
  1485 CREATE INDEX tx_relation_actions_eid_from_idx ON tx_relation_actions(eid_from);;
  1555 CREATE INDEX tx_relation_actions_eid_to_idx ON tx_relation_actions(eid_to);;
  1486 CREATE INDEX tx_relation_actions_eid_to_idx ON tx_relation_actions(eid_to);;
  1556 CREATE INDEX tx_relation_actions_tx_uuid_idx ON tx_relation_actions(tx_uuid);;
  1487 CREATE INDEX tx_relation_actions_tx_uuid_idx ON tx_relation_actions(tx_uuid);;
  1557 """ % (helper.sql_create_sequence('entities_id_seq').replace(';', ';;'),
  1488 """ % (helper.sql_create_sequence('entities_id_seq').replace(';', ';;'),
  1558        typemap['Datetime'], typemap['Datetime'], typemap['Datetime'],
  1489        typemap['Datetime'],
  1559        typemap['Boolean'], typemap['Bytes'], typemap['Boolean'])
  1490        typemap['Boolean'], typemap['Bytes'], typemap['Boolean'])
  1560     if helper.backend_name == 'sqlite':
  1491     if helper.backend_name == 'sqlite':
  1561         # sqlite support the ON DELETE CASCADE syntax but do nothing
  1492         # sqlite support the ON DELETE CASCADE syntax but do nothing
  1562         schema += '''
  1493         schema += '''
  1563 CREATE TRIGGER fkd_transactions
  1494 CREATE TRIGGER fkd_transactions
  1573 def sql_drop_schema(driver):
  1504 def sql_drop_schema(driver):
  1574     helper = get_db_helper(driver)
  1505     helper = get_db_helper(driver)
  1575     return """
  1506     return """
  1576 %s
  1507 %s
  1577 DROP TABLE entities;
  1508 DROP TABLE entities;
  1578 DROP TABLE deleted_entities;
       
  1579 DROP TABLE tx_entity_actions;
  1509 DROP TABLE tx_entity_actions;
  1580 DROP TABLE tx_relation_actions;
  1510 DROP TABLE tx_relation_actions;
  1581 DROP TABLE transactions;
  1511 DROP TABLE transactions;
  1582 """ % helper.sql_drop_sequence('entities_id_seq')
  1512 """ % helper.sql_drop_sequence('entities_id_seq')
  1583 
  1513 
  1584 
  1514 
  1585 def grant_schema(user, set_owner=True):
  1515 def grant_schema(user, set_owner=True):
  1586     result = ''
  1516     result = ''
  1587     for table in ('entities', 'deleted_entities', 'entities_id_seq',
  1517     for table in ('entities', 'entities_id_seq',
  1588                   'transactions', 'tx_entity_actions', 'tx_relation_actions'):
  1518                   'transactions', 'tx_entity_actions', 'tx_relation_actions'):
  1589         if set_owner:
  1519         if set_owner:
  1590             result = 'ALTER TABLE %s OWNER TO %s;\n' % (table, user)
  1520             result = 'ALTER TABLE %s OWNER TO %s;\n' % (table, user)
  1591         result += 'GRANT ALL ON %s TO %s;\n' % (table, user)
  1521         result += 'GRANT ALL ON %s TO %s;\n' % (table, user)
  1592     return result
  1522     return result
  1729             self.cnx.close()
  1659             self.cnx.close()
  1730         self.logger.info('done')
  1660         self.logger.info('done')
  1731 
  1661 
  1732     def get_tables(self):
  1662     def get_tables(self):
  1733         non_entity_tables = ['entities',
  1663         non_entity_tables = ['entities',
  1734                              'deleted_entities',
       
  1735                              'transactions',
  1664                              'transactions',
  1736                              'tx_entity_actions',
  1665                              'tx_entity_actions',
  1737                              'tx_relation_actions',
  1666                              'tx_relation_actions',
  1738                              ]
  1667                              ]
  1739         etype_tables = []
  1668         etype_tables = []