140 |
141 |
141 def _undo_check_relation_target(tentity, rdef, role): |
142 def _undo_check_relation_target(tentity, rdef, role): |
142 """check linked entity has not been redirected for this relation""" |
143 """check linked entity has not been redirected for this relation""" |
143 card = rdef.role_cardinality(role) |
144 card = rdef.role_cardinality(role) |
144 if card in '?1' and tentity.related(rdef.rtype, role): |
145 if card in '?1' and tentity.related(rdef.rtype, role): |
145 raise _UndoException(tentity._cw._( |
146 msg = tentity._cw._( |
146 "Can't restore %(role)s relation %(rtype)s to entity %(eid)s which " |
147 "Can't restore %(role)s relation %(rtype)s to entity %(eid)s which " |
147 "is already linked using this relation.") |
148 "is already linked using this relation.") |
148 % {'role': neg_role(role), |
149 raise _UndoException(msg % {'role': neg_role(role), |
149 'rtype': rdef.rtype, |
150 'rtype': rdef.rtype, |
150 'eid': tentity.eid}) |
151 'eid': tentity.eid}) |
|
152 |
151 |
153 |
152 def _undo_rel_info(cnx, subj, rtype, obj): |
154 def _undo_rel_info(cnx, subj, rtype, obj): |
153 entities = [] |
155 entities = [] |
154 for role, eid in (('subject', subj), ('object', obj)): |
156 for role, eid in (('subject', subj), ('object', obj)): |
155 try: |
157 try: |
156 entities.append(cnx.entity_from_eid(eid)) |
158 entities.append(cnx.entity_from_eid(eid)) |
157 except UnknownEid: |
159 except UnknownEid: |
158 raise _UndoException(cnx._( |
160 msg = cnx._( |
159 "Can't restore relation %(rtype)s, %(role)s entity %(eid)s" |
161 "Can't restore relation %(rtype)s, %(role)s entity %(eid)s" |
160 " doesn't exist anymore.") |
162 " doesn't exist anymore.") |
161 % {'role': cnx._(role), |
163 raise _UndoException(msg % {'role': cnx._(role), |
162 'rtype': cnx._(rtype), |
164 'rtype': cnx._(rtype), |
163 'eid': eid}) |
165 'eid': eid}) |
164 sentity, oentity = entities |
166 sentity, oentity = entities |
165 try: |
167 try: |
166 rschema = cnx.vreg.schema.rschema(rtype) |
168 rschema = cnx.vreg.schema.rschema(rtype) |
167 rdef = rschema.rdefs[(sentity.cw_etype, oentity.cw_etype)] |
169 rdef = rschema.rdefs[(sentity.cw_etype, oentity.cw_etype)] |
168 except KeyError: |
170 except KeyError: |
169 raise _UndoException(cnx._( |
171 msg = cnx._( |
170 "Can't restore relation %(rtype)s between %(subj)s and " |
172 "Can't restore relation %(rtype)s between %(subj)s and " |
171 "%(obj)s, that relation does not exists anymore in the " |
173 "%(obj)s, that relation does not exists anymore in the " |
172 "schema.") |
174 "schema.") |
173 % {'rtype': cnx._(rtype), |
175 raise _UndoException(msg % {'rtype': cnx._(rtype), |
174 'subj': subj, |
176 'subj': subj, |
175 'obj': obj}) |
177 'obj': obj}) |
176 return sentity, oentity, rdef |
178 return sentity, oentity, rdef |
|
179 |
177 |
180 |
178 def _undo_has_later_transaction(cnx, eid): |
181 def _undo_has_later_transaction(cnx, eid): |
179 return cnx.system_sql('''\ |
182 return cnx.system_sql('''\ |
180 SELECT T.tx_uuid FROM transactions AS TREF, transactions AS T |
183 SELECT T.tx_uuid FROM transactions AS TREF, transactions AS T |
181 WHERE TREF.tx_uuid='%(txuuid)s' AND T.tx_uuid!='%(txuuid)s' |
184 WHERE TREF.tx_uuid='%(txuuid)s' AND T.tx_uuid!='%(txuuid)s' |
268 """adapter for source using the native cubicweb schema (see below) |
271 """adapter for source using the native cubicweb schema (see below) |
269 """ |
272 """ |
270 sqlgen_class = SQLGenerator |
273 sqlgen_class = SQLGenerator |
271 options = ( |
274 options = ( |
272 ('db-driver', |
275 ('db-driver', |
273 {'type' : 'string', |
276 {'type': 'string', |
274 'default': 'postgres', |
277 'default': 'postgres', |
275 # XXX use choice type |
278 # XXX use choice type |
276 'help': 'database driver (postgres, sqlite, sqlserver2005)', |
279 'help': 'database driver (postgres, sqlite, sqlserver2005)', |
277 'group': 'native-source', 'level': 0, |
280 'group': 'native-source', 'level': 0, |
278 }), |
281 }), |
279 ('db-host', |
282 ('db-host', |
280 {'type' : 'string', |
283 {'type': 'string', |
281 'default': '', |
284 'default': '', |
282 'help': 'database host', |
285 'help': 'database host', |
283 'group': 'native-source', 'level': 1, |
286 'group': 'native-source', 'level': 1, |
284 }), |
287 }), |
285 ('db-port', |
288 ('db-port', |
286 {'type' : 'string', |
289 {'type': 'string', |
287 'default': '', |
290 'default': '', |
288 'help': 'database port', |
291 'help': 'database port', |
289 'group': 'native-source', 'level': 1, |
292 'group': 'native-source', 'level': 1, |
290 }), |
293 }), |
291 ('db-name', |
294 ('db-name', |
292 {'type' : 'string', |
295 {'type': 'string', |
293 'default': Method('default_instance_id'), |
296 'default': Method('default_instance_id'), |
294 'help': 'database name', |
297 'help': 'database name', |
295 'group': 'native-source', 'level': 0, |
298 'group': 'native-source', 'level': 0, |
296 }), |
299 }), |
297 ('db-namespace', |
300 ('db-namespace', |
298 {'type' : 'string', |
301 {'type': 'string', |
299 'default': '', |
302 'default': '', |
300 'help': 'database namespace (schema) name', |
303 'help': 'database namespace (schema) name', |
301 'group': 'native-source', 'level': 1, |
304 'group': 'native-source', 'level': 1, |
302 }), |
305 }), |
303 ('db-user', |
306 ('db-user', |
304 {'type' : 'string', |
307 {'type': 'string', |
305 'default': CubicWebNoAppConfiguration.mode == 'user' and getlogin() or 'cubicweb', |
308 'default': CubicWebNoAppConfiguration.mode == 'user' and getlogin() or 'cubicweb', |
306 'help': 'database user', |
309 'help': 'database user', |
307 'group': 'native-source', 'level': 0, |
310 'group': 'native-source', 'level': 0, |
308 }), |
311 }), |
309 ('db-password', |
312 ('db-password', |
310 {'type' : 'password', |
313 {'type': 'password', |
311 'default': '', |
314 'default': '', |
312 'help': 'database password', |
315 'help': 'database password', |
313 'group': 'native-source', 'level': 0, |
316 'group': 'native-source', 'level': 0, |
314 }), |
317 }), |
315 ('db-encoding', |
318 ('db-encoding', |
316 {'type' : 'string', |
319 {'type': 'string', |
317 'default': 'utf8', |
320 'default': 'utf8', |
318 'help': 'database encoding', |
321 'help': 'database encoding', |
319 'group': 'native-source', 'level': 1, |
322 'group': 'native-source', 'level': 1, |
320 }), |
323 }), |
321 ('db-extra-arguments', |
324 ('db-extra-arguments', |
322 {'type' : 'string', |
325 {'type': 'string', |
323 'default': '', |
326 'default': '', |
324 'help': 'set to "Trusted_Connection" if you are using SQLServer and ' |
327 'help': 'set to "Trusted_Connection" if you are using SQLServer and ' |
325 'want trusted authentication for the database connection', |
328 'want trusted authentication for the database connection', |
326 'group': 'native-source', 'level': 2, |
329 'group': 'native-source', 'level': 2, |
327 }), |
330 }), |
436 raise ValueError('Unknown format %r' % format) |
438 raise ValueError('Unknown format %r' % format) |
437 finally: |
439 finally: |
438 if self.repo.config.init_cnxset_pool: |
440 if self.repo.config.init_cnxset_pool: |
439 self.open_source_connections() |
441 self.open_source_connections() |
440 |
442 |
441 |
|
442 def init(self, activated, source_entity): |
443 def init(self, activated, source_entity): |
443 try: |
444 try: |
444 # test if 'asource' column exists |
445 # test if 'asource' column exists |
445 query = self.dbhelper.sql_add_limit_offset('SELECT asource FROM entities', 1) |
446 query = self.dbhelper.sql_add_limit_offset('SELECT asource FROM entities', 1) |
446 source_entity._cw.system_sql(query) |
447 source_entity._cw.system_sql(query) |
447 except Exception as ex: |
448 except Exception: |
448 self.eid_type_source = self.eid_type_source_pre_131 |
449 self.eid_type_source = self.eid_type_source_pre_131 |
449 super(NativeSQLSource, self).init(activated, source_entity) |
450 super(NativeSQLSource, self).init(activated, source_entity) |
450 self.init_creating(source_entity._cw.cnxset) |
451 self.init_creating(source_entity._cw.cnxset) |
451 |
452 |
452 def shutdown(self): |
453 def shutdown(self): |
497 self.cache_hit, self.cache_miss, self.no_cache = 0, 0, 0 |
498 self.cache_hit, self.cache_miss, self.no_cache = 0, 0, 0 |
498 self.schema = schema |
499 self.schema = schema |
499 try: |
500 try: |
500 self._rql_sqlgen.schema = schema |
501 self._rql_sqlgen.schema = schema |
501 except AttributeError: |
502 except AttributeError: |
502 pass # __init__ |
503 pass # __init__ |
503 for authentifier in self.authentifiers: |
504 for authentifier in self.authentifiers: |
504 authentifier.set_schema(self.schema) |
505 authentifier.set_schema(self.schema) |
505 clear_cache(self, 'need_fti_indexation') |
506 clear_cache(self, 'need_fti_indexation') |
506 |
507 |
507 def support_entity(self, etype, write=False): |
508 def support_entity(self, etype, write=False): |
508 """return true if the given entity's type is handled by this adapter |
509 """return true if the given entity's type is handled by this adapter |
509 if write is true, return true only if it's a RW support |
510 if write is true, return true only if it's a RW support |
510 """ |
511 """ |
511 return not etype in NONSYSTEM_ETYPES |
512 return etype not in NONSYSTEM_ETYPES |
512 |
513 |
513 def support_relation(self, rtype, write=False): |
514 def support_relation(self, rtype, write=False): |
514 """return true if the given relation's type is handled by this adapter |
515 """return true if the given relation's type is handled by this adapter |
515 if write is true, return true only if it's a RW support |
516 if write is true, return true only if it's a RW support |
516 """ |
517 """ |
517 if write: |
518 if write: |
518 return not rtype in NONSYSTEM_RELATIONS |
519 return rtype not in NONSYSTEM_RELATIONS |
519 # due to current multi-sources implementation, the system source |
520 # due to current multi-sources implementation, the system source |
520 # can't claim not supporting a relation |
521 # can't claim not supporting a relation |
521 return True #not rtype == 'content_for' |
522 return True #not rtype == 'content_for' |
522 |
523 |
523 @statsd_timeit |
524 @statsd_timeit |
524 def authenticate(self, cnx, login, **kwargs): |
525 def authenticate(self, cnx, login, **kwargs): |
525 """return CWUser eid for the given login and other authentication |
526 """return CWUser eid for the given login and other authentication |
526 information found in kwargs, else raise `AuthenticationError` |
527 information found in kwargs, else raise `AuthenticationError` |
594 if attr in edited: |
595 if attr in edited: |
595 handler = getattr(storage, 'entity_%s' % event) |
596 handler = getattr(storage, 'entity_%s' % event) |
596 to_restore = handler(entity, attr) |
597 to_restore = handler(entity, attr) |
597 restore_values.append((entity, attr, to_restore)) |
598 restore_values.append((entity, attr, to_restore)) |
598 try: |
599 try: |
599 yield # 2/ execute the source's instructions |
600 yield # 2/ execute the source's instructions |
600 finally: |
601 finally: |
601 # 3/ restore original values |
602 # 3/ restore original values |
602 for entity, attr, value in restore_values: |
603 for entity, attr, value in restore_values: |
603 entity.cw_edited.edited_attribute(attr, value) |
604 entity.cw_edited.edited_attribute(attr, value) |
604 |
605 |
629 """delete an entity from the source""" |
630 """delete an entity from the source""" |
630 with self._storage_handler(cnx, entity, 'deleted'): |
631 with self._storage_handler(cnx, entity, 'deleted'): |
631 if cnx.ertype_supports_undo(entity.cw_etype): |
632 if cnx.ertype_supports_undo(entity.cw_etype): |
632 attrs = [SQL_PREFIX + r.type |
633 attrs = [SQL_PREFIX + r.type |
633 for r in entity.e_schema.subject_relations() |
634 for r in entity.e_schema.subject_relations() |
634 if (r.final or r.inlined) and not r in VIRTUAL_RTYPES] |
635 if (r.final or r.inlined) and r not in VIRTUAL_RTYPES] |
635 changes = self._save_attrs(cnx, entity, attrs) |
636 changes = self._save_attrs(cnx, entity, attrs) |
636 self._record_tx_action(cnx, 'tx_entity_actions', u'D', |
637 self._record_tx_action(cnx, 'tx_entity_actions', u'D', |
637 etype=text_type(entity.cw_etype), eid=entity.eid, |
638 etype=text_type(entity.cw_etype), eid=entity.eid, |
638 changes=self._binary(pickle.dumps(changes))) |
639 changes=self._binary(pickle.dumps(changes))) |
639 attrs = {'cw_eid': entity.eid} |
640 attrs = {'cw_eid': entity.eid} |
640 sql = self.sqlgen.delete(SQL_PREFIX + entity.cw_etype, attrs) |
641 sql = self.sqlgen.delete(SQL_PREFIX + entity.cw_etype, attrs) |
641 self.doexec(cnx, sql, attrs) |
642 self.doexec(cnx, sql, attrs) |
642 |
643 |
643 def add_relation(self, cnx, subject, rtype, object, inlined=False): |
644 def add_relation(self, cnx, subject, rtype, object, inlined=False): |
644 """add a relation to the source""" |
645 """add a relation to the source""" |
645 self._add_relations(cnx, rtype, [(subject, object)], inlined) |
646 self._add_relations(cnx, rtype, [(subject, object)], inlined) |
646 if cnx.ertype_supports_undo(rtype): |
647 if cnx.ertype_supports_undo(rtype): |
647 self._record_tx_action(cnx, 'tx_relation_actions', u'A', |
648 self._record_tx_action(cnx, 'tx_relation_actions', u'A', |
648 eid_from=subject, rtype=text_type(rtype), eid_to=object) |
649 eid_from=subject, rtype=text_type(rtype), eid_to=object) |
649 |
650 |
650 def add_relations(self, cnx, rtype, subj_obj_list, inlined=False): |
651 def add_relations(self, cnx, rtype, subj_obj_list, inlined=False): |
651 """add a relations to the source""" |
652 """add a relations to the source""" |
652 self._add_relations(cnx, rtype, subj_obj_list, inlined) |
653 self._add_relations(cnx, rtype, subj_obj_list, inlined) |
653 if cnx.ertype_supports_undo(rtype): |
654 if cnx.ertype_supports_undo(rtype): |
654 for subject, object in subj_obj_list: |
655 for subject, object in subj_obj_list: |
655 self._record_tx_action(cnx, 'tx_relation_actions', u'A', |
656 self._record_tx_action(cnx, 'tx_relation_actions', u'A', |
660 sql = [] |
661 sql = [] |
661 if inlined is False: |
662 if inlined is False: |
662 attrs = [{'eid_from': subject, 'eid_to': object} |
663 attrs = [{'eid_from': subject, 'eid_to': object} |
663 for subject, object in subj_obj_list] |
664 for subject, object in subj_obj_list] |
664 sql.append((self.sqlgen.insert('%s_relation' % rtype, attrs[0]), attrs)) |
665 sql.append((self.sqlgen.insert('%s_relation' % rtype, attrs[0]), attrs)) |
665 else: # used by data import |
666 else: # used by data import |
666 etypes = {} |
667 etypes = {} |
667 for subject, object in subj_obj_list: |
668 for subject, object in subj_obj_list: |
668 etype = cnx.entity_metas(subject)['type'] |
669 etype = cnx.entity_metas(subject)['type'] |
669 if etype in etypes: |
670 if etype in etypes: |
670 etypes[etype].append((subject, object)) |
671 etypes[etype].append((subject, object)) |
672 etypes[etype] = [(subject, object)] |
673 etypes[etype] = [(subject, object)] |
673 for subj_etype, subj_obj_list in etypes.items(): |
674 for subj_etype, subj_obj_list in etypes.items(): |
674 attrs = [{'cw_eid': subject, SQL_PREFIX + rtype: object} |
675 attrs = [{'cw_eid': subject, SQL_PREFIX + rtype: object} |
675 for subject, object in subj_obj_list] |
676 for subject, object in subj_obj_list] |
676 sql.append((self.sqlgen.update(SQL_PREFIX + etype, attrs[0], |
677 sql.append((self.sqlgen.update(SQL_PREFIX + etype, attrs[0], |
677 ['cw_eid']), |
678 ['cw_eid']), |
678 attrs)) |
679 attrs)) |
679 for statement, attrs in sql: |
680 for statement, attrs in sql: |
680 self.doexecmany(cnx, statement, attrs) |
681 self.doexecmany(cnx, statement, attrs) |
681 |
682 |
682 def delete_relation(self, cnx, subject, rtype, object): |
683 def delete_relation(self, cnx, subject, rtype, object): |
692 if inlined: |
693 if inlined: |
693 table = SQL_PREFIX + cnx.entity_metas(subject)['type'] |
694 table = SQL_PREFIX + cnx.entity_metas(subject)['type'] |
694 column = SQL_PREFIX + rtype |
695 column = SQL_PREFIX + rtype |
695 sql = 'UPDATE %s SET %s=NULL WHERE %seid=%%(eid)s' % (table, column, |
696 sql = 'UPDATE %s SET %s=NULL WHERE %seid=%%(eid)s' % (table, column, |
696 SQL_PREFIX) |
697 SQL_PREFIX) |
697 attrs = {'eid' : subject} |
698 attrs = {'eid': subject} |
698 else: |
699 else: |
699 attrs = {'eid_from': subject, 'eid_to': object} |
700 attrs = {'eid_from': subject, 'eid_to': object} |
700 sql = self.sqlgen.delete('%s_relation' % rtype, attrs) |
701 sql = self.sqlgen.delete('%s_relation' % rtype, attrs) |
701 self.doexec(cnx, sql, attrs) |
702 self.doexec(cnx, sql, attrs) |
702 |
703 |
714 except Exception as ex: |
715 except Exception as ex: |
715 if self.repo.config.mode != 'test': |
716 if self.repo.config.mode != 'test': |
716 # during test we get those message when trying to alter sqlite |
717 # during test we get those message when trying to alter sqlite |
717 # db schema |
718 # db schema |
718 self.info("sql: %r\n args: %s\ndbms message: %r", |
719 self.info("sql: %r\n args: %s\ndbms message: %r", |
719 query, args, ex.args[0]) |
720 query, args, ex.args[0]) |
720 if rollback: |
721 if rollback: |
721 try: |
722 try: |
722 cnx.cnxset.rollback() |
723 cnx.cnxset.rollback() |
723 if self.repo.config.mode != 'test': |
724 if self.repo.config.mode != 'test': |
724 self.debug('transaction has been rolled back') |
725 self.debug('transaction has been rolled back') |
845 return res |
846 return res |
846 except Exception: |
847 except Exception: |
847 self.exception('failed to query entities table for eid %s', eid) |
848 self.exception('failed to query entities table for eid %s', eid) |
848 raise UnknownEid(eid) |
849 raise UnknownEid(eid) |
849 |
850 |
850 def eid_type_source(self, cnx, eid): # pylint: disable=E0202 |
851 def eid_type_source(self, cnx, eid): # pylint: disable=E0202 |
851 """return a tuple (type, extid, source) for the entity with id <eid>""" |
852 """return a tuple (type, extid, source) for the entity with id <eid>""" |
852 sql = 'SELECT type, extid, asource FROM entities WHERE eid=%s' % eid |
853 sql = 'SELECT type, extid, asource FROM entities WHERE eid=%s' % eid |
853 res = self._eid_type_source(cnx, eid, sql) |
854 res = self._eid_type_source(cnx, eid, sql) |
854 if not isinstance(res, list): |
855 if not isinstance(res, list): |
855 res = list(res) |
856 res = list(res) |
914 'asource': text_type(source.uri)} |
915 'asource': text_type(source.uri)} |
915 self._handle_insert_entity_sql(cnx, self.sqlgen.insert('entities', attrs), attrs) |
916 self._handle_insert_entity_sql(cnx, self.sqlgen.insert('entities', attrs), attrs) |
916 # insert core relations: is, is_instance_of and cw_source |
917 # insert core relations: is, is_instance_of and cw_source |
917 |
918 |
918 if entity.e_schema.eid is not None: # else schema has not yet been serialized |
919 if entity.e_schema.eid is not None: # else schema has not yet been serialized |
919 self._handle_is_relation_sql(cnx, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)', |
920 self._handle_is_relation_sql( |
920 (entity.eid, entity.e_schema.eid)) |
921 cnx, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)', |
|
922 (entity.eid, entity.e_schema.eid)) |
921 for eschema in entity.e_schema.ancestors() + [entity.e_schema]: |
923 for eschema in entity.e_schema.ancestors() + [entity.e_schema]: |
922 self._handle_is_relation_sql(cnx, |
924 self._handle_is_relation_sql( |
923 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)', |
925 cnx, |
924 (entity.eid, eschema.eid)) |
926 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)', |
|
927 (entity.eid, eschema.eid)) |
925 if source.eid is not None: # else the source has not yet been inserted |
928 if source.eid is not None: # else the source has not yet been inserted |
926 self._handle_is_relation_sql(cnx, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)', |
929 self._handle_is_relation_sql( |
927 (entity.eid, source.eid)) |
930 cnx, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)', |
|
931 (entity.eid, source.eid)) |
928 # now we can update the full text index |
932 # now we can update the full text index |
929 if self.need_fti_indexation(entity.cw_etype): |
933 if self.need_fti_indexation(entity.cw_etype): |
930 self.index_entity(cnx, entity=entity) |
934 self.index_entity(cnx, entity=entity) |
931 |
935 |
932 def update_info(self, cnx, entity, need_fti_update): |
936 def update_info(self, cnx, entity, need_fti_update): |
967 restr['tx_user'] = ueid |
971 restr['tx_user'] = ueid |
968 sql = self.sqlgen.select('transactions', restr, ('tx_uuid', 'tx_time', 'tx_user')) |
972 sql = self.sqlgen.select('transactions', restr, ('tx_uuid', 'tx_time', 'tx_user')) |
969 if actionfilters: |
973 if actionfilters: |
970 # we will need subqueries to filter transactions according to |
974 # we will need subqueries to filter transactions according to |
971 # actions done |
975 # actions done |
972 tearestr = {} # filters on the tx_entity_actions table |
976 tearestr = {} # filters on the tx_entity_actions table |
973 trarestr = {} # filters on the tx_relation_actions table |
977 trarestr = {} # filters on the tx_relation_actions table |
974 genrestr = {} # generic filters, appliyable to both table |
978 genrestr = {} # generic filters, appliyable to both table |
975 # unless public explicitly set to false, we only consider public |
979 # unless public explicitly set to false, we only consider public |
976 # actions |
980 # actions |
977 if actionfilters.pop('public', True): |
981 if actionfilters.pop('public', True): |
978 genrestr['txa_public'] = True |
982 genrestr['txa_public'] = True |
979 # put additional filters in trarestr and/or tearestr |
983 # put additional filters in trarestr and/or tearestr |
980 for key, val in actionfilters.items(): |
984 for key, val in actionfilters.items(): |
981 if key == 'etype': |
985 if key == 'etype': |
982 # filtering on etype implies filtering on entity actions |
986 # filtering on etype implies filtering on entity actions |
983 # only, and with no eid specified |
987 # only, and with no eid specified |
984 assert actionfilters.get('action', 'C') in 'CUD' |
988 assert actionfilters.get('action', 'C') in 'CUD' |
985 assert not 'eid' in actionfilters |
989 assert 'eid' not in actionfilters |
986 tearestr['etype'] = text_type(val) |
990 tearestr['etype'] = text_type(val) |
987 elif key == 'eid': |
991 elif key == 'eid': |
988 # eid filter may apply to 'eid' of tx_entity_actions or to |
992 # eid filter may apply to 'eid' of tx_entity_actions or to |
989 # 'eid_from' OR 'eid_to' of tx_relation_actions |
993 # 'eid_from' OR 'eid_to' of tx_relation_actions |
990 if actionfilters.get('action', 'C') in 'CUD': |
994 if actionfilters.get('action', 'C') in 'CUD': |
1044 sql = self.sqlgen.select('tx_entity_actions', restr, |
1048 sql = self.sqlgen.select('tx_entity_actions', restr, |
1045 ('txa_action', 'txa_public', 'txa_order', |
1049 ('txa_action', 'txa_public', 'txa_order', |
1046 'etype', 'eid', 'changes')) |
1050 'etype', 'eid', 'changes')) |
1047 with cnx.ensure_cnx_set: |
1051 with cnx.ensure_cnx_set: |
1048 cu = self.doexec(cnx, sql, restr) |
1052 cu = self.doexec(cnx, sql, restr) |
1049 actions = [tx.EntityAction(a,p,o,et,e,c and pickle.loads(self.binary_to_str(c))) |
1053 actions = [tx.EntityAction(a, p, o, et, e, c and pickle.loads(self.binary_to_str(c))) |
1050 for a,p,o,et,e,c in cu.fetchall()] |
1054 for a, p, o, et, e, c in cu.fetchall()] |
1051 sql = self.sqlgen.select('tx_relation_actions', restr, |
1055 sql = self.sqlgen.select('tx_relation_actions', restr, |
1052 ('txa_action', 'txa_public', 'txa_order', |
1056 ('txa_action', 'txa_public', 'txa_order', |
1053 'rtype', 'eid_from', 'eid_to')) |
1057 'rtype', 'eid_from', 'eid_to')) |
1054 with cnx.ensure_cnx_set: |
1058 with cnx.ensure_cnx_set: |
1055 cu = self.doexec(cnx, sql, restr) |
1059 cu = self.doexec(cnx, sql, restr) |
1144 eschema = entity.e_schema |
1148 eschema = entity.e_schema |
1145 getrschema = eschema.subjrels |
1149 getrschema = eschema.subjrels |
1146 for column, value in changes.items(): |
1150 for column, value in changes.items(): |
1147 rtype = column[len(SQL_PREFIX):] |
1151 rtype = column[len(SQL_PREFIX):] |
1148 if rtype == "eid": |
1152 if rtype == "eid": |
1149 continue # XXX should even `eid` be stored in action changes? |
1153 continue # XXX should even `eid` be stored in action changes? |
1150 try: |
1154 try: |
1151 rschema = getrschema[rtype] |
1155 rschema = getrschema[rtype] |
1152 except KeyError: |
1156 except KeyError: |
1153 err(cnx._("can't restore relation %(rtype)s of entity %(eid)s, " |
1157 err(cnx._("can't restore relation %(rtype)s of entity %(eid)s, " |
1154 "this relation does not exist in the schema anymore.") |
1158 "this relation does not exist in the schema anymore.") |
1155 % {'rtype': rtype, 'eid': eid}) |
1159 % {'rtype': rtype, 'eid': eid}) |
1156 if not rschema.final: |
1160 if not rschema.final: |
1157 if not rschema.inlined: |
1161 if not rschema.inlined: |
1158 assert value is None |
1162 assert value is None |
1159 # rschema is an inlined relation |
1163 # rschema is an inlined relation |
1160 elif value is not None: |
1164 elif value is not None: |
1161 # not a deletion: we must put something in edited |
1165 # not a deletion: we must put something in edited |
1162 try: |
1166 try: |
1163 entity._cw.entity_from_eid(value) # check target exists |
1167 entity._cw.entity_from_eid(value) # check target exists |
1164 edited[rtype] = value |
1168 edited[rtype] = value |
1165 except UnknownEid: |
1169 except UnknownEid: |
1166 err(cnx._("can't restore entity %(eid)s of type %(eschema)s, " |
1170 err(cnx._("can't restore entity %(eid)s of type %(eschema)s, " |
1167 "target of %(rtype)s (eid %(value)s) does not exist any longer") |
1171 "target of %(rtype)s (eid %(value)s) does not exist any longer") |
1168 % locals()) |
1172 % locals()) |
1169 changes[column] = None |
1173 changes[column] = None |
1170 elif eschema.destination(rtype) in ('Bytes', 'Password'): |
1174 elif eschema.destination(rtype) in ('Bytes', 'Password'): |
1171 changes[column] = self._binary(value) |
1175 changes[column] = self._binary(value) |
1172 edited[rtype] = Binary(value) |
1176 edited[rtype] = Binary(value) |
1237 eid = action.eid |
1240 eid = action.eid |
1238 # XXX done to avoid fetching all remaining relation for the entity |
1241 # XXX done to avoid fetching all remaining relation for the entity |
1239 # we should find an efficient way to do this (keeping current veolidf |
1242 # we should find an efficient way to do this (keeping current veolidf |
1240 # massive deletion performance) |
1243 # massive deletion performance) |
1241 if _undo_has_later_transaction(cnx, eid): |
1244 if _undo_has_later_transaction(cnx, eid): |
1242 msg = cnx._('some later transaction(s) touch entity, undo them ' |
1245 msg = cnx._('some later transaction(s) touch entity, undo them first') |
1243 'first') |
|
1244 raise ValidationError(eid, {None: msg}) |
1246 raise ValidationError(eid, {None: msg}) |
1245 etype = action.etype |
1247 etype = action.etype |
1246 # get an entity instance |
1248 # get an entity instance |
1247 try: |
1249 try: |
1248 entity = self.repo.vreg['etypes'].etype_class(etype)(cnx) |
1250 entity = self.repo.vreg['etypes'].etype_class(etype)(cnx) |
1275 err = errors.append |
1277 err = errors.append |
1276 try: |
1278 try: |
1277 entity = cnx.entity_from_eid(action.eid) |
1279 entity = cnx.entity_from_eid(action.eid) |
1278 except UnknownEid: |
1280 except UnknownEid: |
1279 err(cnx._("can't restore state of entity %s, it has been " |
1281 err(cnx._("can't restore state of entity %s, it has been " |
1280 "deleted inbetween") % action.eid) |
1282 "deleted inbetween") % action.eid) |
1281 return errors |
1283 return errors |
1282 self._reedit_entity(entity, action.changes, err) |
1284 self._reedit_entity(entity, action.changes, err) |
1283 entity.cw_edited.check() |
1285 entity.cw_edited.check() |
1284 self.repo.hm.call_hooks('before_update_entity', cnx, entity=entity) |
1286 self.repo.hm.call_hooks('before_update_entity', cnx, entity=entity) |
1285 sql = self.sqlgen.update(SQL_PREFIX + entity.cw_etype, action.changes, |
1287 sql = self.sqlgen.update(SQL_PREFIX + entity.cw_etype, action.changes, |
1344 cursor = cnx.cnxset.cu |
1346 cursor = cnx.cnxset.cu |
1345 cursor_unindex_object = self.dbhelper.cursor_unindex_object |
1347 cursor_unindex_object = self.dbhelper.cursor_unindex_object |
1346 try: |
1348 try: |
1347 for entity in entities: |
1349 for entity in entities: |
1348 cursor_unindex_object(entity.eid, cursor) |
1350 cursor_unindex_object(entity.eid, cursor) |
1349 except Exception: # let KeyboardInterrupt / SystemExit propagate |
1351 except Exception: # let KeyboardInterrupt / SystemExit propagate |
1350 self.exception('error while unindexing %s', entity) |
1352 self.exception('error while unindexing %s', entity) |
1351 |
|
1352 |
1353 |
1353 def fti_index_entities(self, cnx, entities): |
1354 def fti_index_entities(self, cnx, entities): |
1354 """add text content of created/modified entities to the full text index |
1355 """add text content of created/modified entities to the full text index |
1355 """ |
1356 """ |
1356 cursor_index_object = self.dbhelper.cursor_index_object |
1357 cursor_index_object = self.dbhelper.cursor_index_object |
1360 # unindexing done in the FTIndexEntityOp |
1361 # unindexing done in the FTIndexEntityOp |
1361 for entity in entities: |
1362 for entity in entities: |
1362 cursor_index_object(entity.eid, |
1363 cursor_index_object(entity.eid, |
1363 entity.cw_adapt_to('IFTIndexable'), |
1364 entity.cw_adapt_to('IFTIndexable'), |
1364 cursor) |
1365 cursor) |
1365 except Exception: # let KeyboardInterrupt / SystemExit propagate |
1366 except Exception: # let KeyboardInterrupt / SystemExit propagate |
1366 self.exception('error while indexing %s', entity) |
1367 self.exception('error while indexing %s', entity) |
1367 |
1368 |
1368 |
1369 |
1369 class FTIndexEntityOp(hook.DataOperationMixIn, hook.LateOperation): |
1370 class FTIndexEntityOp(hook.DataOperationMixIn, hook.LateOperation): |
1370 """operation to delay entity full text indexation to commit |
1371 """operation to delay entity full text indexation to commit |
1388 done.add(eid) |
1389 done.add(eid) |
1389 iftindexable = cnx.entity_from_eid(eid).cw_adapt_to('IFTIndexable') |
1390 iftindexable = cnx.entity_from_eid(eid).cw_adapt_to('IFTIndexable') |
1390 to_reindex |= set(iftindexable.fti_containers()) |
1391 to_reindex |= set(iftindexable.fti_containers()) |
1391 source.fti_unindex_entities(cnx, to_reindex) |
1392 source.fti_unindex_entities(cnx, to_reindex) |
1392 source.fti_index_entities(cnx, to_reindex) |
1393 source.fti_index_entities(cnx, to_reindex) |
|
1394 |
1393 |
1395 |
1394 def sql_schema(driver): |
1396 def sql_schema(driver): |
1395 """Yield SQL statements to create system tables in the database.""" |
1397 """Yield SQL statements to create system tables in the database.""" |
1396 helper = get_db_helper(driver) |
1398 helper = get_db_helper(driver) |
1397 typemap = helper.TYPE_MAPPING |
1399 typemap = helper.TYPE_MAPPING |
1486 |
1488 |
1487 def set_schema(self, schema): |
1489 def set_schema(self, schema): |
1488 """set the instance'schema""" |
1490 """set the instance'schema""" |
1489 pass |
1491 pass |
1490 |
1492 |
|
1493 |
1491 class LoginPasswordAuthentifier(BaseAuthentifier): |
1494 class LoginPasswordAuthentifier(BaseAuthentifier): |
1492 passwd_rql = 'Any P WHERE X is CWUser, X login %(login)s, X upassword P' |
1495 passwd_rql = 'Any P WHERE X is CWUser, X login %(login)s, X upassword P' |
1493 auth_rql = (u'Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s, ' |
1496 auth_rql = (u'Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s, ' |
1494 'X cw_source S, S name "system"') |
1497 'X cw_source S, S name "system"') |
1495 _sols = ({'X': 'CWUser', 'P': 'Password', 'S': 'CWSource'},) |
1498 _sols = ({'X': 'CWUser', 'P': 'Password', 'S': 'CWSource'},) |
1496 |
1499 |
1497 def set_schema(self, schema): |
1500 def set_schema(self, schema): |
1498 """set the instance'schema""" |
1501 """set the instance'schema""" |
1499 if 'CWUser' in schema: # probably an empty schema if not true... |
1502 if 'CWUser' in schema: # probably an empty schema if not true... |
1500 # rql syntax trees used to authenticate users |
1503 # rql syntax trees used to authenticate users |
1501 self._passwd_rqlst = self.source.compile_rql(self.passwd_rql, self._sols) |
1504 self._passwd_rqlst = self.source.compile_rql(self.passwd_rql, self._sols) |
1502 self._auth_rqlst = self.source.compile_rql(self.auth_rql, self._sols) |
1505 self._auth_rqlst = self.source.compile_rql(self.auth_rql, self._sols) |
1503 |
1506 |
1504 def authenticate(self, cnx, login, password=None, **kwargs): |
1507 def authenticate(self, cnx, login, password=None, **kwargs): |
1527 user = rset[0][0] |
1530 user = rset[0][0] |
1528 # If the stored hash uses a deprecated scheme (e.g. DES or MD5 used |
1531 # If the stored hash uses a deprecated scheme (e.g. DES or MD5 used |
1529 # before 3.14.7), update with a fresh one |
1532 # before 3.14.7), update with a fresh one |
1530 if pwd is not None and pwd.getvalue(): |
1533 if pwd is not None and pwd.getvalue(): |
1531 verify, newhash = verify_and_update(password, pwd.getvalue()) |
1534 verify, newhash = verify_and_update(password, pwd.getvalue()) |
1532 if not verify: # should not happen, but... |
1535 if not verify: # should not happen, but... |
1533 raise AuthenticationError('bad password') |
1536 raise AuthenticationError('bad password') |
1534 if newhash: |
1537 if newhash: |
1535 cnx.system_sql("UPDATE %s SET %s=%%(newhash)s WHERE %s=%%(login)s" % ( |
1538 cnx.system_sql("UPDATE %s SET %s=%%(newhash)s WHERE %s=%%(login)s" |
1536 SQL_PREFIX + 'CWUser', |
1539 % (SQL_PREFIX + 'CWUser', |
1537 SQL_PREFIX + 'upassword', |
1540 SQL_PREFIX + 'upassword', |
1538 SQL_PREFIX + 'login'), |
1541 SQL_PREFIX + 'login'), |
1539 {'newhash': self.source._binary(newhash.encode('ascii')), |
1542 {'newhash': self.source._binary(newhash.encode('ascii')), |
1540 'login': login}) |
1543 'login': login}) |
1541 cnx.commit() |
1544 cnx.commit() |
1542 return user |
1545 return user |
1543 except IndexError: |
1546 except IndexError: |
1544 raise AuthenticationError('bad password') |
1547 raise AuthenticationError('bad password') |
1545 |
1548 |
1546 |
1549 |
1547 class EmailPasswordAuthentifier(BaseAuthentifier): |
1550 class EmailPasswordAuthentifier(BaseAuthentifier): |
1548 def authenticate(self, cnx, login, **authinfo): |
1551 def authenticate(self, cnx, login, **authinfo): |
1549 # email_auth flag prevent from infinite recursion (call to |
1552 # email_auth flag prevent from infinite recursion (call to |
1550 # repo.check_auth_info at the end of this method may lead us here again) |
1553 # repo.check_auth_info at the end of this method may lead us here again) |
1551 if not '@' in login or authinfo.pop('email_auth', None): |
1554 if '@' not in login or authinfo.pop('email_auth', None): |
1552 raise AuthenticationError('not an email') |
1555 raise AuthenticationError('not an email') |
1553 rset = cnx.execute('Any L WHERE U login L, U primary_email M, ' |
1556 rset = cnx.execute('Any L WHERE U login L, U primary_email M, ' |
1554 'M address %(login)s', {'login': login}, |
1557 'M address %(login)s', {'login': login}, |
1555 build_descr=False) |
1558 build_descr=False) |
1556 if rset.rowcount != 1: |
1559 if rset.rowcount != 1: |
1557 raise AuthenticationError('unexisting email') |
1560 raise AuthenticationError('unexisting email') |
1558 login = rset.rows[0][0] |
1561 login = rset.rows[0][0] |
1559 authinfo['email_auth'] = True |
1562 authinfo['email_auth'] = True |
1560 return self.source.repo.check_auth_info(cnx, login, authinfo) |
1563 return self.source.repo.check_auth_info(cnx, login, authinfo) |
1687 for i, start in enumerate(range(0, rowcount, blocksize)): |
1690 for i, start in enumerate(range(0, rowcount, blocksize)): |
1688 rows = list(itertools.islice(rows_iterator, blocksize)) |
1691 rows = list(itertools.islice(rows_iterator, blocksize)) |
1689 serialized = self._serialize(table, columns, rows) |
1692 serialized = self._serialize(table, columns, rows) |
1690 archive.writestr('tables/%s.%04d' % (table, i), serialized) |
1693 archive.writestr('tables/%s.%04d' % (table, i), serialized) |
1691 self.logger.debug('wrote rows %d to %d (out of %d) to %s.%04d', |
1694 self.logger.debug('wrote rows %d to %d (out of %d) to %s.%04d', |
1692 start, start+len(rows)-1, |
1695 start, start + len(rows) - 1, |
1693 rowcount, |
1696 rowcount, |
1694 table, i) |
1697 table, i) |
1695 else: |
1698 else: |
1696 rows = [] |
1699 rows = [] |
1697 serialized = self._serialize(table, columns, rows) |
1700 serialized = self._serialize(table, columns, rows) |