# Copyright (c) 2018 Mathias Behrle diff --git a/trytond/backend/database.py b/trytond/backend/database.py index 22567cd61c7683ee88cb0e38c732a3a5ffb6b887..bff64d62fb45d0dbb45de5972375cb76414f1be4 100644 --- a/trytond/backend/database.py +++ b/trytond/backend/database.py @@ -172,3 +172,46 @@ class DatabaseInterface(object): def has_multirow_insert(self): 'Return True if database supports multirow insert' return False + + @classmethod + def has_sequence(cls): + "Return if database supports sequence querying and assignation" + return False + + def sequence_exist(self, connection, name): + "Return if a sequence exists" + if not self.has_sequence(): + return + raise NotImplementedError + + def sequence_create( + self, connection, name, number_increment=1, start_value=1): + "Creates a sequence" + if not self.has_sequence(): + return + raise NotImplementedError + + def sequence_update( + self, connection, name, number_increment=1, start_value=1): + "Modifies a sequence" + if not self.has_sequence(): + return + raise NotImplementedError + + def sequence_rename(self, connection, old_name, new_name): + "Renames a sequence" + if not self.has_sequence(): + return + raise NotImplementedError + + def sequence_delete(self, connection, name): + "Removes a sequence" + if not self.has_sequence(): + return + raise NotImplementedError + + def sequence_next_number(self, connection, name): + "Gets the next number of a sequence" + if not self.has_sequence(): + return + raise NotImplementedError diff --git a/trytond/backend/mysql/table.py b/trytond/backend/mysql/table.py index bfe6031250787a0ed0d593dd4822ec25bb596b55..c09065f37ccf2c242007e69a44bf111e4e1e8632 100644 --- a/trytond/backend/mysql/table.py +++ b/trytond/backend/mysql/table.py @@ -75,14 +75,6 @@ class TableHandler(TableHandlerInterface): cursor.execute('ALTER TABLE `%s` RENAME TO `%s`' % (old_history, new_history)) - @staticmethod - def sequence_exist(sequence_name): - return True - - @staticmethod - def sequence_rename(old_name, new_name): - pass - def column_exist(self, column_name): return column_name in self._columns diff --git a/trytond/backend/postgresql/database.py b/trytond/backend/postgresql/database.py index 505973a8785ca5c11889aa757222a9242acb182a..6967b7d28c73d1968b216d57814a604f5af10199 100644 --- a/trytond/backend/postgresql/database.py +++ b/trytond/backend/postgresql/database.py @@ -2,7 +2,6 @@ # this repository contains the full copyright notices and license terms. import time import logging -import re import os import urllib from decimal import Decimal @@ -34,8 +33,6 @@ __all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError'] logger = logging.getLogger(__name__) -RE_VERSION = re.compile(r'\S+ (\d+)\.(\d+)') - os.environ['PGTZ'] = os.environ.get('TZ', '') @@ -131,10 +128,11 @@ class Database(DatabaseInterface): def get_version(self, connection): if self.name not in self._version_cache: cursor = connection.cursor() - cursor.execute('SELECT version()') + cursor.execute('SHOW server_version_num') version, = cursor.fetchone() - self._version_cache[self.name] = tuple(map(int, - RE_VERSION.search(version).groups())) + major, rest = divmod(int(version), 10000) + minor, patch = divmod(rest, 100) + self._version_cache[self.name] = (major, minor, patch) return self._version_cache[self.name] @staticmethod @@ -353,6 +351,80 @@ class Database(DatabaseInterface): self.put_connection(connection) return self._search_path + @classmethod + def has_sequence(cls): + return True + + def sequence_exist(self, connection, name): + cursor = connection.cursor() + for schema in self.search_path: + cursor.execute('SELECT 1 ' + 'FROM information_schema.sequences ' + 'WHERE sequence_name = %s AND sequence_schema = %s', + (name, schema)) + if cursor.rowcount: + return True + return False + + def sequence_create( + self, connection, name, number_increment=1, start_value=1): + cursor = connection.cursor() + + param = self.flavor.param + cursor.execute( + 'CREATE SEQUENCE "%s" ' + 'INCREMENT BY %s ' + 'START WITH %s' + % (name, param, param), + (number_increment, start_value)) + + def sequence_update( + self, connection, name, number_increment=1, start_value=1): + cursor = connection.cursor() + param = self.flavor.param + cursor.execute( + 'ALTER SEQUENCE "%s" ' + 'INCREMENT BY %s ' + 'RESTART WITH %s' + % (name, param, param), + (number_increment, start_value)) + + def sequence_rename(self, connection, old_name, new_name): + cursor = connection.cursor() + if (self.sequence_exist(connection, old_name) + and not self.sequence_exist(connection, new_name)): + cursor.execute('ALTER TABLE "%s" RENAME TO "%s"' + % (old_name, new_name)) + + def sequence_delete(self, connection, name): + cursor = connection.cursor() + cursor.execute('DROP SEQUENCE "%s"' % name) + + def sequence_next_number(self, connection, name): + cursor = connection.cursor() + version = self.get_version(connection) + if version >= (10, 0): + cursor.execute( + 'SELECT increment_by ' + 'FROM pg_sequences ' + 'WHERE sequencename=%s ' + % self.flavor.param, + (name,)) + increment, = cursor.fetchone() + cursor.execute( + 'SELECT CASE WHEN NOT is_called THEN last_value ' + 'ELSE last_value + %s ' + 'END ' + 'FROM "%s"' % (self.flavor.param, name), + (increment,)) + else: + cursor.execute( + 'SELECT CASE WHEN NOT is_called THEN last_value ' + 'ELSE last_value + increment_by ' + 'END ' + 'FROM "%s"' % name) + return cursor.fetchone()[0] + register_type(UNICODE) if PYDATE: register_type(PYDATE) diff --git a/trytond/backend/postgresql/table.py b/trytond/backend/postgresql/table.py index a3323041c98be880cf9053b98589dc78e16265b5..8cae6ca12fcacf5a5cdd924416f26ef0ca7ca7bf 100644 --- a/trytond/backend/postgresql/table.py +++ b/trytond/backend/postgresql/table.py @@ -22,8 +22,10 @@ class TableHandler(TableHandlerInterface): transaction = Transaction() cursor = transaction.connection.cursor() # Create sequence if necessary - if not self.sequence_exist(self.sequence_name): - cursor.execute('CREATE SEQUENCE "%s"' % self.sequence_name) + if not transaction.database.sequence_exist( + transaction.connection, self.sequence_name): + transaction.database.sequence_create( + transaction.connection, self.sequence_name) # Create new table if necessary if not self.table_exist(self.table_name): @@ -81,7 +83,8 @@ class TableHandler(TableHandlerInterface): @staticmethod def table_rename(old_name, new_name): - cursor = Transaction().connection.cursor() + transaction = Transaction() + cursor = transaction.connection.cursor() # Rename table if (TableHandler.table_exist(old_name) and not TableHandler.table_exist(new_name)): @@ -90,7 +93,8 @@ class TableHandler(TableHandlerInterface): # Rename sequence old_sequence = old_name + '_id_seq' new_sequence = new_name + '_id_seq' - TableHandler.sequence_rename(old_sequence, new_sequence) + transaction.database.sequence_rename( + transaction.connection, old_sequence, new_sequence) # Rename history table old_history = old_name + "__history" new_history = new_name + "__history" @@ -99,30 +103,6 @@ class TableHandler(TableHandlerInterface): cursor.execute('ALTER TABLE "%s" RENAME TO "%s"' % (old_history, new_history)) - @classmethod - def sequence_schema(cls, sequence_name): - transaction = Transaction() - cursor = transaction.connection.cursor() - for schema in transaction.database.search_path: - cursor.execute('SELECT 1 ' - 'FROM information_schema.sequences ' - 'WHERE sequence_name = %s AND sequence_schema = %s', - (sequence_name, schema)) - if cursor.rowcount: - return schema - - @classmethod - def sequence_exist(cls, sequence_name): - return bool(cls.sequence_schema(sequence_name)) - - @staticmethod - def sequence_rename(old_name, new_name): - cursor = Transaction().connection.cursor() - if (TableHandler.sequence_exist(old_name) - and not TableHandler.sequence_exist(new_name)): - cursor.execute('ALTER TABLE "%s" RENAME TO "%s"' - % (old_name, new_name)) - def column_exist(self, column_name): return column_name in self._columns diff --git a/trytond/backend/table.py b/trytond/backend/table.py index 887d415af80a3f69061808797b70621f73c0108d..853c51918e0b918f0aa9ad392eac7d22697990dd 100644 --- a/trytond/backend/table.py +++ b/trytond/backend/table.py @@ -46,26 +46,6 @@ class TableHandlerInterface(object): ''' raise NotImplementedError - @staticmethod - def sequence_exist(sequence_name): - ''' - Sequence exist - - :param sequence_name: the sequence name - :return: a boolean - ''' - raise NotImplementedError - - @staticmethod - def sequence_rename(old_name, new_name): - ''' - Rename sequence - - :param old_name: the old sequence name - :param new_name: the new sequence name - ''' - raise NotImplementedError - def column_exist(self, column_name): ''' Column exist diff --git a/trytond/ir/sequence.py b/trytond/ir/sequence.py index 21bc3c12ee62990ce91fe675520582e3db8c41a8..f6791cf692fb6f9161d7a8607c01f638a576309d 100644 --- a/trytond/ir/sequence.py +++ b/trytond/ir/sequence.py @@ -3,7 +3,7 @@ from string import Template import time from itertools import izip -from sql import Flavor +from sql import Literal, For from ..model import ModelView, ModelSQL, fields, Check from ..tools import datetime_strftime @@ -16,7 +16,7 @@ __all__ = [ 'SequenceType', 'Sequence', 'SequenceStrict', ] -sql_sequence = backend.name() == 'postgresql' +sql_sequence = backend.get('Database').has_sequence() class SequenceType(ModelSQL, ModelView): @@ -104,6 +104,7 @@ class Sequence(ModelSQL, ModelView): def __register__(cls, module_name): TableHandler = backend.get('TableHandler') table = TableHandler(cls, module_name) + transaction = Transaction() # Migration from 2.0 rename number_next into number_next_internal table.column_rename('number_next', 'number_next_internal') @@ -116,8 +117,8 @@ class Sequence(ModelSQL, ModelView): for sequence in sequences: if sequence.type != 'incremental': continue - if not TableHandler.sequence_exist( - sequence._sql_sequence_name): + if not transaction.database.sequence_exist( + transaction.connection, sequence._sql_sequence_name): sequence.create_sql_sequence(sequence.number_next_internal) @staticmethod @@ -159,14 +160,11 @@ class Sequence(ModelSQL, ModelView): def get_number_next(self, name): if self.type != 'incremental': return - cursor = Transaction().connection.cursor() - sql_name = self._sql_sequence_name + + transaction = Transaction() if sql_sequence and not self._strict: - cursor.execute('SELECT ' - 'CASE WHEN NOT is_called THEN last_value ' - 'ELSE last_value + increment_by ' - 'END FROM "%s"' % sql_name) - return cursor.fetchone()[0] + return transaction.database.sequence_next_number( + transaction.connection, self._sql_sequence_name) else: return self.number_next_internal @@ -260,22 +258,22 @@ class Sequence(ModelSQL, ModelView): def create_sql_sequence(self, number_next=None): 'Create the SQL sequence' - cursor = Transaction().connection.cursor() - param = Flavor.get().param + transaction = Transaction() + if self.type != 'incremental': return if number_next is None: number_next = self.number_next - cursor.execute('CREATE SEQUENCE "' + self._sql_sequence_name - + '" INCREMENT BY ' + param + ' START WITH ' + param, - (self.number_increment, number_next)) + if sql_sequence: + transaction.database.sequence_create(transaction.connection, + self._sql_sequence_name, self.number_increment, number_next) def update_sql_sequence(self, number_next=None): 'Update the SQL sequence' - TableHandler = backend.get('TableHandler') - cursor = Transaction().connection.cursor() - param = Flavor.get().param - exist = TableHandler.sequence_exist(self._sql_sequence_name) + transaction = Transaction() + + exist = transaction.database.sequence_exist( + transaction.connection, self._sql_sequence_name) if self.type != 'incremental': if exist: self.delete_sql_sequence() @@ -285,17 +283,16 @@ class Sequence(ModelSQL, ModelView): return if number_next is None: number_next = self.number_next - cursor.execute('ALTER SEQUENCE "' + self._sql_sequence_name - + '" INCREMENT BY ' + param + ' RESTART WITH ' + param, - (self.number_increment, number_next)) + transaction.database.sequence_update(transaction.connection, + self._sql_sequence_name, self.number_increment, number_next) def delete_sql_sequence(self): 'Delete the SQL sequence' - cursor = Transaction().connection.cursor() + transaction = Transaction() if self.type != 'incremental': return - cursor.execute('DROP SEQUENCE "%s"' - % self._sql_sequence_name) + transaction.database.sequence_delete( + transaction.connection, self._sql_sequence_name) @staticmethod def _process(string, date=None): diff --git a/trytond/res/ir.py b/trytond/res/ir.py index 754a6226a6b5e1d7aca4b5575ed8232f9525eb2a..0265c46e4e6857753613a27d6a44575d003c4718 100644 --- a/trytond/res/ir.py +++ b/trytond/res/ir.py @@ -3,6 +3,7 @@ from ..model import ModelSQL, fields from .. import backend from ..pool import Pool, PoolMeta +from ..transaction import Transaction __all__ = [ 'UIMenuGroup', 'ActionGroup', 'ModelFieldGroup', 'ModelButtonGroup', @@ -23,10 +24,11 @@ class UIMenuGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') + transaction = Transaction() # Migration from 1.0 table name change TableHandler.table_rename('ir_ui_menu_group_rel', cls._table) - TableHandler.sequence_rename('ir_ui_menu_group_rel_id_seq', - cls._table + '_id_seq') + transaction.database.sequence_rename(transaction.connection, + 'ir_ui_menu_group_rel_id_seq', cls._table + '_id_seq') # Migration from 2.0 menu_id and gid renamed into menu group table = TableHandler(cls, module_name) table.column_rename('menu_id', 'menu') @@ -64,10 +66,11 @@ class ActionGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') + transaction = Transaction() # Migration from 1.0 table name change TableHandler.table_rename('ir_action_group_rel', cls._table) - TableHandler.sequence_rename('ir_action_group_rel_id_seq', - cls._table + '_id_seq') + transaction.database.sequence_rename(transaction.connection, + 'ir_action_group_rel_id_seq', cls._table + '_id_seq') # Migration from 2.0 action_id and gid renamed into action and group table = TableHandler(cls, module_name) table.column_rename('action_id', 'action') @@ -118,10 +121,12 @@ class ModelFieldGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') + database = Transaction().database + transaction = Transaction() # Migration from 1.0 table name change TableHandler.table_rename('ir_model_field_group_rel', cls._table) - TableHandler.sequence_rename('ir_model_field_group_rel_id_seq', - cls._table + '_id_seq') + transaction.database.sequence_rename(transaction.connection, + 'ir_model_field_group_rel_id_seq', cls._table + '_id_seq') table = TableHandler(cls, module_name) # Migration from 2.6: field_id and group_id renamed to field and group table.column_rename('field_id', 'field') @@ -176,10 +181,11 @@ class RuleGroupGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') + transaction = Transaction() # Migration from 1.0 table name change TableHandler.table_rename('group_rule_group_rel', cls._table) - TableHandler.sequence_rename('group_rule_group_rel_id_seq', - cls._table + '_id_seq') + transaction.database.sequence_rename(transaction.connection, + 'group_rule_group_rel_id_seq', cls._table + '_id_seq') # Migration from 2.0 rule_group_id and group_id renamed into rule_group # and group table = TableHandler(cls, module_name) @@ -199,10 +205,11 @@ class RuleGroupUser(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') + transaction = Transaction() # Migration from 1.0 table name change TableHandler.table_rename('user_rule_group_rel', cls._table) - TableHandler.sequence_rename('user_rule_group_rel_id_seq', - cls._table + '_id_seq') + transaction.database.sequence_rename(transaction.connection, + 'user_rule_group_rel_id_seq', cls._table + '_id_seq') # Migration from 2.0 rule_group_id and user_id renamed into rule_group # and user table = TableHandler(cls, module_name) diff --git a/trytond/res/user.py b/trytond/res/user.py index ac539065b01127b77540c4f3dbdca9b3383d8978..dd13df43b05f9f243d9e1e29573252c0640b7122 100644 --- a/trytond/res/user.py +++ b/trytond/res/user.py @@ -645,10 +645,12 @@ class UserGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') + transaction = Transaction() + # Migration from 1.0 table name change TableHandler.table_rename('res_group_user_rel', cls._table) - TableHandler.sequence_rename('res_group_user_rel_id_seq', - cls._table + '_id_seq') + transaction.database.sequence_rename(transaction.connection, + 'res_group_user_rel_id_seq', cls._table + '_id_seq') # Migration from 2.0 uid and gid rename into user and group table = TableHandler(cls, module_name) table.column_rename('uid', 'user')