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 |
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>""" |
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 |