From 4520afedd3e2582e95edcf9a15d9cfd8b8ff716d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 12 May 2017 12:00:44 +0200 Subject: [PATCH] Convert into hash too long data name Some back-ends have constraint on the length of data name, they usually silently truncate them, but this can create collision. The solution is to hash the name when it is too long. issue4620 issue6494 review37381003 --- trytond/backend/mysql/table.py | 18 +++++++------- trytond/backend/postgresql/table.py | 37 +++++++++++++---------------- trytond/backend/sqlite/table.py | 3 ++- trytond/backend/table.py | 13 ++++++++++ trytond/model/modelsql.py | 5 ++-- 5 files changed, 44 insertions(+), 32 deletions(-) diff --git a/trytond/backend/mysql/table.py b/trytond/backend/mysql/table.py index a16f40945d..b0f81fa0a4 100644 --- a/trytond/backend/mysql/table.py +++ b/trytond/backend/mysql/table.py @@ -11,6 +11,7 @@ VARCHAR_SIZE_RE = re.compile('VARCHAR\(([0-9]+)\)') class TableHandler(TableHandlerInterface): + namedatalen = 64 def __init__(self, model, module_name=None, history=False): super(TableHandler, self).__init__(model, @@ -247,7 +248,8 @@ class TableHandler(TableHandlerInterface): def add_fk(self, column_name, reference, on_delete=None): if on_delete is None: on_delete = 'SET NULL' - conname = '%s_%s_fkey' % (self.table_name, column_name) + conname = self.convert_name( + '%s_%s_fkey' % (self.table_name, column_name)) if conname in self._fkeys: self.drop_fk(column_name) cursor = Transaction().connection.cursor() @@ -259,7 +261,8 @@ class TableHandler(TableHandlerInterface): self._update_definitions(constraints=True) def drop_fk(self, column_name, table=None): - conname = '%s_%s_fkey' % (self.table_name, column_name) + conname = self.convert_name( + '%s_%s_fkey' % (self.table_name, column_name)) if conname not in self._fkeys: return cursor = Transaction().connection.cursor() @@ -270,10 +273,9 @@ class TableHandler(TableHandlerInterface): def index_action(self, column_name, action='add', table=None): if isinstance(column_name, basestring): column_name = [column_name] - index_name = ((table or self.table_name) + "_" + '_'.join(column_name) - + "_index") - # Index name length is limited to 64 - index_name = index_name[:64] + index_name = self.convert_name( + ((table or self.table_name) + "_" + '_'.join(column_name) + + "_index")) for k in column_name: if k in self._columns: @@ -348,7 +350,7 @@ class TableHandler(TableHandlerInterface): raise Exception('Not null action not supported!') def add_constraint(self, ident, constraint, exception=False): - ident = self.table_name + "_" + ident + ident = self.convert_name(self.table_name + "_" + ident) if ident in self._constraints: # This constrain already exists return @@ -371,7 +373,7 @@ class TableHandler(TableHandlerInterface): self._update_definitions(constraints=True) def drop_constraint(self, ident, exception=False, table=None): - ident = (table or self.table_name) + "_" + ident + ident = self.convert_name((table or self.table_name) + "_" + ident) if ident not in self._constraints: return diff --git a/trytond/backend/postgresql/table.py b/trytond/backend/postgresql/table.py index f2cacb8a30..f09172d379 100644 --- a/trytond/backend/postgresql/table.py +++ b/trytond/backend/postgresql/table.py @@ -13,6 +13,7 @@ VARCHAR_SIZE_RE = re.compile('VARCHAR\(([0-9]+)\)') class TableHandler(TableHandlerInterface): + namedatalen = 64 def __init__(self, model, module_name=None, history=False): super(TableHandler, self).__init__(model, @@ -314,17 +315,14 @@ class TableHandler(TableHandlerInterface): on_delete = 'SET NULL' cursor = Transaction().connection.cursor() - name = self.table_name + '_' + column_name + '_fkey' - cursor.execute('SELECT 1 ' - 'FROM information_schema.key_column_usage ' - 'WHERE table_name = %s AND table_schema = %s ' - 'AND constraint_name = %s', - (self.table_name, self.table_schema, name)) - add = False - if not cursor.rowcount: - add = True - elif self._fk_deltypes.get(column_name) != on_delete: - self.drop_fk(column_name) + name = self.convert_name(self.table_name + '_' + column_name + '_fkey') + if name in self._constraints: + if self._fk_deltypes.get(column_name) != on_delete: + self.drop_fk(column_name) + add = True + else: + add = False + else: add = True if add: cursor.execute('ALTER TABLE "' + self.table_name + '" ' @@ -340,16 +338,13 @@ class TableHandler(TableHandlerInterface): def index_action(self, column_name, action='add', table=None): if isinstance(column_name, basestring): column_name = [column_name] - index_name = ((table or self.table_name) + "_" + '_'.join(column_name) - + "_index") - if self._indexes: - test_index_name = index_name[:max(map(len, self._indexes))] - else: - test_index_name = index_name + index_name = self.convert_name( + ((table or self.table_name) + "_" + '_'.join(column_name) + + "_index")) with Transaction().connection.cursor() as cursor: if action == 'add': - if test_index_name in self._indexes: + if index_name in self._indexes: return cursor.execute('CREATE INDEX "' + index_name + '" ' 'ON "' + self.table_name + '" ( ' @@ -361,7 +356,7 @@ class TableHandler(TableHandlerInterface): self.module_name) != self.module_name): return - if test_index_name in self._indexes: + if index_name in self._indexes: cursor.execute('DROP INDEX "%s" ' % (index_name,)) self._update_definitions(indexes=True) else: @@ -407,7 +402,7 @@ class TableHandler(TableHandlerInterface): raise Exception('Not null action not supported!') def add_constraint(self, ident, constraint, exception=False): - ident = self.table_name + "_" + ident + ident = self.convert_name(self.table_name + "_" + ident) if ident in self._constraints: # This constrain already exist return @@ -429,7 +424,7 @@ class TableHandler(TableHandlerInterface): self._update_definitions(constraints=True) def drop_constraint(self, ident, exception=False, table=None): - ident = (table or self.table_name) + "_" + ident + ident = self.convert_name((table or self.table_name) + "_" + ident) if ident not in self._constraints: return cursor = Transaction().connection.cursor() diff --git a/trytond/backend/sqlite/table.py b/trytond/backend/sqlite/table.py index c45502cf86..a4190372d2 100644 --- a/trytond/backend/sqlite/table.py +++ b/trytond/backend/sqlite/table.py @@ -248,7 +248,8 @@ class TableHandler(TableHandlerInterface): def index_action(self, column_name, action='add', table=None): if isinstance(column_name, basestring): column_name = [column_name] - index_name = self.table_name + "_" + '_'.join(column_name) + "_index" + index_name = self.convert_name( + self.table_name + "_" + '_'.join(column_name) + "_index") cursor = Transaction().connection.cursor() if action == 'add': diff --git a/trytond/backend/table.py b/trytond/backend/table.py index 53261f6e00..093d2a7b13 100644 --- a/trytond/backend/table.py +++ b/trytond/backend/table.py @@ -1,11 +1,13 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. +import hashlib class TableHandlerInterface(object): ''' Define generic interface to handle database table ''' + namedatalen = None def __init__(self, model, module_name=None, history=False): ''' @@ -204,3 +206,14 @@ class TableHandlerInterface(object): :param cascade: a boolean to add "CASCADE" to the delete query ''' raise NotImplementedError + + @classmethod + def convert_name(cls, name): + ''' + Convert data name in respect of namedatalen. + + :param name: the data name + ''' + if cls.namedatalen and len(name) >= cls.namedatalen: + name = hashlib.sha256(name).hexdigest()[:cls.namedatalen - 1] + return name diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index 11e38707c8..9bb8c33469 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -261,6 +261,7 @@ class ModelSQL(ModelStorage): def __raise_integrity_error( cls, exception, values, field_names=None, transaction=None): pool = Pool() + TableHandler = backend.get('TableHandler') if field_names is None: field_names = cls._fields.keys() if transaction is None: @@ -293,10 +294,10 @@ class ModelSQL(ModelStorage): cls.raise_user_error('foreign_model_missing', error_args=error_args) for name, _, error in cls._sql_constraints: - if name in str(exception): + if TableHandler.convert_name(name) in str(exception): cls.raise_user_error(error) for name, error in cls._sql_error_messages.iteritems(): - if name in str(exception): + if TableHandler.convert_name(name) in str(exception): cls.raise_user_error(error) @classmethod