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 |