From 2fef47957594beea4ec69574616c73aadfeef3ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Thu, 3 Mar 2016 21:51:57 +0100 Subject: [PATCH] Follow PEP-0249 for Transaction and backends issue4949 review19781002 --- CHANGELOG | 1 + COPYRIGHT | 1 + doc/ref/transaction.rst | 45 ++-- trytond/backend/database.py | 148 ++++---------- trytond/backend/mysql/database.py | 110 ++++------ trytond/backend/mysql/table.py | 273 ++++++++++++++----------- trytond/backend/postgresql/database.py | 204 ++++++++---------- trytond/backend/postgresql/table.py | 253 ++++++++++++----------- trytond/backend/sqlite/database.py | 179 ++++++++-------- trytond/backend/sqlite/table.py | 78 +++---- trytond/backend/table.py | 18 +- trytond/cache.py | 55 +++-- trytond/convert.py | 14 +- trytond/ir/action.py | 29 +-- trytond/ir/attachment.py | 11 +- trytond/ir/cron.py | 12 +- trytond/ir/lang.py | 3 +- trytond/ir/model.py | 24 +-- trytond/ir/module.py | 27 ++- trytond/ir/property.py | 2 +- trytond/ir/rule.py | 4 +- trytond/ir/sequence.py | 20 +- trytond/ir/session.py | 3 +- trytond/ir/translation.py | 69 +++---- trytond/ir/trigger.py | 14 +- trytond/ir/ui/menu.py | 6 +- trytond/ir/ui/view.py | 13 +- trytond/model/fields/many2one.py | 2 +- trytond/model/fields/property.py | 4 +- trytond/model/modelsql.py | 85 ++++---- trytond/model/modelstorage.py | 27 +-- trytond/modules/__init__.py | 245 +++++++++++----------- trytond/pool.py | 4 +- trytond/protocols/dispatcher.py | 53 ++--- trytond/res/ir.py | 37 ++-- trytond/res/user.py | 21 +- trytond/security.py | 11 +- trytond/server.py | 23 ++- trytond/tests/test_access.py | 28 +-- trytond/tests/test_fields.py | 64 +++--- trytond/tests/test_history.py | 83 ++++---- trytond/tests/test_importdata.py | 38 ++-- trytond/tests/test_mixins.py | 4 +- trytond/tests/test_modelsql.py | 15 +- trytond/tests/test_transaction.py | 29 +-- trytond/tests/test_trigger.py | 6 +- trytond/tests/test_tryton.py | 13 +- trytond/tools/__init__.py | 12 ++ trytond/tools/misc.py | 2 +- trytond/transaction.py | 134 ++++++------ trytond/url.py | 2 +- 51 files changed, 1227 insertions(+), 1331 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 22a7a8f830..5cc0eeb70f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Follow PEP-0249 for Database, Transaction and Cursor * Add Python3 support * Make TestCase create and drop its database * Add with_transaction decorator for tests diff --git a/COPYRIGHT b/COPYRIGHT index 16cc09a2f0..f778918e33 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -3,6 +3,7 @@ Copyright (C) 2007-2015 Cédric Krier. Copyright (C) 2007-2013 Bertrand Chenal. Copyright (C) 2008-2015 B2CK SPRL. Copyright (C) 2011 Openlabs Technologies & Consulting (P) Ltd. +Copyright (C) 2011-2016 Nicolas Évrard. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/doc/ref/transaction.rst b/doc/ref/transaction.rst index 2ddc2922b8..760b8ce0d4 100644 --- a/doc/ref/transaction.rst +++ b/doc/ref/transaction.rst @@ -7,20 +7,22 @@ Transaction .. class:: Transaction -This class is a `singleton`_ that contains thread-local parameters of the -database transaction. - -.. _`singleton`: http://en.wikipedia.org/wiki/Singleton_pattern - - -.. attribute:: Transaction.cursor - - The database cursor. +This class represents a Tryton transaction that contains thread-local +parameters of a database connection. The Transaction instances are +`context manager`_ that will commit or rollback the database transaction. In +the event of an exception the transaction is rolled back, otherwise it is +commited. .. attribute:: Transaction.database The database. +.. attribute:: Transaction.readonly + +.. attribute:: Transaction.connection + + The database connection as defined by the `PEP-0249`_. + .. attribute:: Transaction.user The id of the user. @@ -43,14 +45,20 @@ database transaction. Count the number of modification made in this transaction. +.. method:: Transaction.cursor() + + Returns a cursor object using the ``Transaction.connection``. + .. method:: Transaction.start(database_name, user[, readonly[, context[, close[, autocommit]]]]) Start a new transaction and return a `context manager`_. .. method:: Transaction.stop() - Stop a started transaction. This method should not be called directly as it - will be by the context manager when exiting the `with` statement. + Stop a started transaction and pop it from the stack of transactions. + + This method should not be called directly as it will be by the context + manager when exiting the `with` statement. .. method:: Transaction.set_context(context, \**kwargs) @@ -63,15 +71,16 @@ database transaction. `set_context` will put the previous user id in the context to simulate the record rules. The user will be restored when exiting the `with` statement. -.. method:: Transaction.set_cursor(cursor) +.. method:: Transaction.set_current_transaction(transaction) - Modify the cursor of the transaction and return a `context manager`_. The - previous cursor will be restored when exiting the `with` statement. + Add a specific ``transaction`` on the top of the transaction stack. A + transaction is commited or rollbacked only when its last reference is + popped from the stack. -.. method:: Transaction.new_cursor([autocommit[, readonly]]) +.. method:: Transaction.new_transaction([autocommit[, readonly]]) - Change the cursor of the transaction with a new one on the same database - and return a `context manager`_. The previous cursor will be restored when - exiting the `with` statement and the new one will be closed. + Create a new transaction with the same database, user and context as the + original transaction and adds it to the stack of transactions. .. _`context manager`: http://docs.python.org/reference/datamodel.html#context-managers +.. _`PEP-0249`: https://www.python.org/dev/peps/pep-0249/ diff --git a/trytond/backend/database.py b/trytond/backend/database.py index 06f618f536..22567cd61c 100644 --- a/trytond/backend/database.py +++ b/trytond/backend/database.py @@ -1,7 +1,5 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.config import config - DatabaseIntegrityError = None DatabaseOperationalError = None @@ -11,12 +9,13 @@ class DatabaseInterface(object): Define generic interface for database connection ''' flavor = None + IN_MAX = 1000 - def __new__(cls, database_name=''): + def __new__(cls, name=''): return object.__new__(cls) - def __init__(self, database_name=''): - self.database_name = database_name + def __init__(self, name=''): + self.name = name def connect(self): ''' @@ -26,12 +25,18 @@ class DatabaseInterface(object): ''' raise NotImplementedError - def cursor(self, autocommit=False, readonly=False): - ''' - Retreive a cursor on the database + def get_connection(self, autocommit, readonly=False): + '''Retrieve a connection on the database - :param autocommit: a boolean to active autocommit - :return: a Cursor + :param autocommit: a boolean to activate autocommit + :param readonly: a boolean to specify if the transaction is readonly + ''' + raise NotImplementedError + + def put_connection(self, connection, close=False): + '''Release the connection + + :param close: if close is True the connection is discarded ''' raise NotImplementedError @@ -41,21 +46,21 @@ class DatabaseInterface(object): ''' raise NotImplementedError - @staticmethod - def create(cursor, database_name): + @classmethod + def create(cls, connection, database_name): ''' Create a database - :param database_name: the database name + :param connection: the connection to the database + :param database_name: the new database name ''' raise NotImplementedError - @staticmethod - def drop(cursor, database_name): + def drop(self, connection, database_name): ''' Drop a database - :param cursor: a cursor on an other database + :param connection: the connection to the database :param database_name: the database name ''' raise NotImplementedError @@ -81,8 +86,7 @@ class DatabaseInterface(object): ''' raise NotImplementedError - @staticmethod - def list(cursor): + def list(self): ''' Get the list of database @@ -90,107 +94,60 @@ class DatabaseInterface(object): ''' raise NotImplementedError - @staticmethod - def init(cursor): + def init(self): ''' Initialize a database - - :param cursor: a cursor on the database ''' raise NotImplementedError - -class CursorInterface(object): - ''' - Define generic interface for database cursor - ''' - IN_MAX = 1000 - cache_keys = {'language', 'fuzzy_translation', '_datetime'} - - def __init__(self): - self.cache = {} - - def get_cache(self): - from trytond.cache import LRUDict - from trytond.transaction import Transaction - user = Transaction().user - context = Transaction().context - keys = tuple(((key, context[key]) for key in sorted(self.cache_keys) - if key in context)) - return self.cache.setdefault((user, keys), - LRUDict(config.getint('cache', 'model'))) - - def execute(self, sql, params=None): - ''' - Execute a query - - :param sql: a sql query string - :param params: a tuple or list of parameters - ''' - raise NotImplementedError - - def close(self, close=False): - ''' - Close the cursor - - :param close: boolean to not release cursor in pool - ''' - raise NotImplementedError - - def commit(self): - ''' - Commit the cursor - ''' - for cache in self.cache.itervalues(): - cache.clear() - - def rollback(self): - ''' - Rollback the cursor - ''' - for cache in self.cache.itervalues(): - cache.clear() - def test(self): ''' Test if it is a Tryton database. ''' raise NotImplementedError - def nextid(self, table): + def nextid(self, connection, table): ''' Return the next sequenced id for a table. + :param connection: a connection on the database :param table: the table name :return: an integer ''' - def setnextid(self, table, value): + def setnextid(self, connection, table, value): ''' Set the current sequenced id for a table. + :param connection: a connection on the database :param table: the table name ''' - def currid(self, table): + def currid(self, connection, table): ''' Return the current sequenced id for a table. + :param connection: a connection on the database :param table: the table name :return: an integer ''' - def lastid(self): + def update_auto_increment(self, connection, table, value): ''' - Return the last id inserted. + Update auto_increment value of table - :return: an integer + :param connection: a connection on the database + :param table: the table name + :param value: the auto_increment value ''' + pass - def lock(self, table): + @classmethod + def lock(cls, connection, table): ''' Lock the table + :param connection: a connection on the database :param table: the table name ''' raise NotImplementedError @@ -203,15 +160,6 @@ class CursorInterface(object): ''' raise NotImplementedError - def update_auto_increment(self, table, value): - ''' - Update auto_increment value of table - - :param table: the table name - :param value: the auto_increment value - ''' - pass - def has_returning(self): ''' Return True if database implements RETURNING clause in INSERT or UPDATE @@ -219,26 +167,8 @@ class CursorInterface(object): :return: a boolean ''' + return False def has_multirow_insert(self): 'Return True if database supports multirow insert' return False - - def __build_dict(self, row): - return dict((desc[0], row[i]) - for i, desc in enumerate(self.description)) - - def dictfetchone(self): - row = self.fetchone() - if row: - return self.__build_dict(row) - else: - return row - - def dictfetchmany(self, size): - rows = self.fetchmany(size) - return [self.__build_dict(row) for row in rows] - - def dictfetchall(self): - rows = self.fetchall() - return [self.__build_dict(row) for row in rows] diff --git a/trytond/backend/mysql/database.py b/trytond/backend/mysql/database.py index e912f5c41a..0c091f69c2 100644 --- a/trytond/backend/mysql/database.py +++ b/trytond/backend/mysql/database.py @@ -1,6 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.backend.database import DatabaseInterface, CursorInterface +from trytond.backend.database import DatabaseInterface from trytond.config import config, parse_uri import MySQLdb import MySQLdb.cursors @@ -15,8 +15,7 @@ import urllib from sql import Flavor, Expression from sql.functions import Extract, Overlay, CharLength -__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError', - 'Cursor'] +__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError'] class MySQLExtract(Extract): @@ -82,12 +81,12 @@ class Database(DatabaseInterface): def connect(self): return self - def cursor(self, autocommit=False, readonly=False): + def get_connection(self, autocommit=False, readonly=False): conv = MySQLdb.converters.conversions.copy() conv[float] = lambda value, _: repr(value) conv[MySQLdb.constants.FIELD_TYPE.TIME] = MySQLdb.times.Time_or_None args = { - 'db': self.database_name, + 'db': self.name, 'sql_mode': 'traditional,postgresql', 'use_unicode': True, 'charset': 'utf8', @@ -104,21 +103,26 @@ class Database(DatabaseInterface): if uri.password: args['passwd'] = urllib.unquote_plus(uri.password) conn = MySQLdb.connect(**args) - cursor = Cursor(conn, self.database_name) + cursor = conn.cursor() cursor.execute('SET time_zone = `UTC`') - return cursor + return conn + + def put_connection(self, connection, close=False): + connection.close() def close(self): return @classmethod - def create(cls, cursor, database_name): + def create(cls, connection, database_name): + cursor = connection.cursor() cursor.execute('CREATE DATABASE `' + database_name + '` ' 'DEFAULT CHARACTER SET = \'utf8\'') cls._list_cache = None @classmethod - def drop(cls, cursor, database_name): + def drop(cls, connection, database_name): + cursor = connection.cursor() cursor.execute('DROP DATABASE `' + database_name + '`') cls._list_cache = None @@ -195,13 +199,14 @@ class Database(DatabaseInterface): Database._list_cache = None return True - @staticmethod - def list(cursor): + def list(self): now = time.time() timeout = config.getint('session', 'timeout') res = Database._list_cache if res and abs(Database._list_cache_timestamp - now) < timeout: return res + conn = self.get_connection() + cursor = conn.cursor() cursor.execute('SHOW DATABASES') res = [] for db_name, in cursor.fetchall(): @@ -209,20 +214,19 @@ class Database(DatabaseInterface): database = Database(db_name).connect() except Exception: continue - cursor2 = database.cursor() - if cursor2.test(): + if database.test(): res.append(db_name) - cursor2.close(close=True) - else: - cursor2.close() - database.close() + database.close() + self.put_connection(conn) Database._list_cache = res Database._list_cache_timestamp = now return res - @staticmethod - def init(cursor): + def init(self): from trytond.modules import get_module_info + + connection = self.get_connection() + cursor = connection.cursor() sql_file = os.path.join(os.path.dirname(__file__), 'init.sql') with open(sql_file) as fp: for line in fp.read().split(';'): @@ -246,40 +250,15 @@ class Database(DatabaseInterface): 'VALUES (%s, now(), %s, %s)', (0, module_id, dependency)) - -class Cursor(CursorInterface): - - def __init__(self, conn, database_name): - super(Cursor, self).__init__() - self._conn = conn - self.database_name = database_name - self.dbname = self.database_name # XXX to remove - self.cursor = conn.cursor() - - def __getattr__(self, name): - return getattr(self.cursor, name) - - def execute(self, sql, params=None): - if params: - return self.cursor.execute(sql, params) - else: - return self.cursor.execute(sql) - - def close(self, close=False): - self.cursor.close() - self.rollback() - - def commit(self): - super(Cursor, self).commit() - self._conn.commit() - - def rollback(self): - super(Cursor, self).rollback() - self._conn.rollback() + connection.commit() + self.put_connection(connection) def test(self): - self.cursor.execute("SHOW TABLES") - for table, in self.cursor.fetchall(): + is_tryton_database = False + connection = self.get_connection() + cursor = connection.cursor() + cursor.execute("SHOW TABLES") + for table, in cursor.fetchall(): if table in ( 'ir_model', 'ir_model_field', @@ -292,14 +271,17 @@ class Cursor(CursorInterface): 'ir_translation', 'ir_lang', ): - return True - return False + is_tryton_database = True + break + self.put_connection(connection) + return is_tryton_database - def lastid(self): - self.cursor.execute('SELECT LAST_INSERT_ID()') - return self.cursor.fetchone()[0] + def lastid(self, cursor): + # This call is not thread safe + cursor.execute('SELECT LAST_INSERT_ID()') + return cursor.fetchone()[0] - def lock(self, table): + def lock(self, connection, table): # Lock of table doesn't work because MySQL require # that the session locks all tables that will be accessed # but 'FLUSH TABLES WITH READ LOCK' creates deadlock @@ -311,15 +293,7 @@ class Cursor(CursorInterface): def has_multirow_insert(self): return True - def limit_clause(self, select, limit=None, offset=None): - if offset and limit is None: - limit = 18446744073709551610 # max bigint - if limit is not None: - select += ' LIMIT %d' % limit - if offset is not None and offset != 0: - select += ' OFFSET %d' % offset - return select - - def update_auto_increment(self, table, value): - self.cursor.execute('ALTER TABLE `%s` AUTO_INCREMENT = %%s' % table, + def update_auto_increment(self, connection, table, value): + cursor = connection.cursor() + cursor.execute('ALTER TABLE `%s` AUTO_INCREMENT = %%s' % table, (value,)) diff --git a/trytond/backend/mysql/table.py b/trytond/backend/mysql/table.py index 14b604dd65..903cd40620 100644 --- a/trytond/backend/mysql/table.py +++ b/trytond/backend/mysql/table.py @@ -1,6 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. +from trytond.transaction import Transaction from trytond.backend.table import TableHandlerInterface import logging @@ -9,8 +10,8 @@ logger = logging.getLogger(__name__) class TableHandler(TableHandlerInterface): - def __init__(self, cursor, model, module_name=None, history=False): - super(TableHandler, self).__init__(cursor, model, + def __init__(self, model, module_name=None, history=False): + super(TableHandler, self).__init__(model, module_name=module_name, history=history) self._columns = {} self._constraints = [] @@ -19,15 +20,16 @@ class TableHandler(TableHandlerInterface): self._field2module = {} self._model = model + cursor = Transaction().connection.cursor() # Create new table if necessary - if not self.table_exist(self.cursor, self.table_name): + if not self.table_exist(self.table_name): if not self.history: - self.cursor.execute('CREATE TABLE `%s` (' + cursor.execute('CREATE TABLE `%s` (' 'id BIGINT AUTO_INCREMENT NOT NULL, ' 'PRIMARY KEY(id)' ') ENGINE=InnoDB;' % self.table_name) else: - self.cursor.execute('CREATE TABLE `%s` (' + cursor.execute('CREATE TABLE `%s` (' '__id BIGINT AUTO_INCREMENT NOT NULL, ' 'id BIGINT, ' 'PRIMARY KEY(__id)' @@ -36,56 +38,60 @@ class TableHandler(TableHandlerInterface): self._update_definitions() if 'id' not in self._columns: if not self.history: - self.cursor.execute('ALTER TABLE `%s` ' + cursor.execute('ALTER TABLE `%s` ' 'ADD COLUMN id BIGINT AUTO_INCREMENT ' 'NOT NULL PRIMARY KEY' % self.table_name) else: - self.cursor.execute('ALTER TABLE `%s` ' + cursor.execute('ALTER TABLE `%s` ' 'ADD COLUMN id BIGINT' % self.table_name) self._update_definitions() if self.history and '__id' not in self._columns: - self.cursor.execute('ALTER TABLE `%s` ' + cursor.execute('ALTER TABLE `%s` ' 'ADD COLUMN __id BIGINT AUTO_INCREMENT ' 'NOT NULL PRIMARY KEY' % self.table_name) self._update_definitions() @staticmethod - def table_exist(cursor, table_name): + def table_exist(table_name): + transaction = Transaction() + cursor = transaction.connection.cursor() cursor.execute("SELECT table_name FROM information_schema.tables " "WHERE table_schema = %s AND table_name = %s", - (cursor.database_name, table_name)) + (transaction.database.name, table_name)) return bool(cursor.rowcount) @staticmethod - def table_rename(cursor, old_name, new_name): + def table_rename(old_name, new_name): + cursor = Transaction().connection.cursor() # Rename table - if (TableHandler.table_exist(cursor, old_name) - and not TableHandler.table_exist(cursor, new_name)): + if (TableHandler.table_exist(old_name) + and not TableHandler.table_exist(new_name)): cursor.execute('ALTER TABLE `%s` RENAME TO `%s`' % (old_name, new_name)) # Rename history table old_history = old_name + '__history' new_history = new_name + '__history' - if (TableHandler.table_exist(cursor, old_history) - and not TableHandler.table_exist(cursor, new_history)): + if (TableHandler.table_exist(old_history) + and not TableHandler.table_exist(new_history)): cursor.execute('ALTER TABLE `%s` RENAME TO `%s`' % (old_history, new_history)) @staticmethod - def sequence_exist(cursor, sequence_name): + def sequence_exist(sequence_name): return True @staticmethod - def sequence_rename(cursor, old_name, new_name): + def sequence_rename(old_name, new_name): pass def column_exist(self, column_name): return column_name in self._columns def column_rename(self, old_name, new_name, exception=False): + cursor = Transaction().connection.cursor() if (self.column_exist(old_name) and not self.column_exist(new_name)): - self.cursor.execute('ALTER TABLE `%s` ' + cursor.execute('ALTER TABLE `%s` ' 'RENAME COLUMN `%s` TO `%s`' % (self.table_name, old_name, new_name)) self._update_definitions() @@ -96,14 +102,16 @@ class TableHandler(TableHandlerInterface): self.table_name, new_name)) def _update_definitions(self): + transaction = Transaction() + cursor = transaction.connection.cursor() # Fetch columns definitions from the table - self.cursor.execute("SELECT column_name, character_maximum_length, " + cursor.execute("SELECT column_name, character_maximum_length, " "data_type, is_nullable, column_default " "FROM information_schema.columns " "WHERE table_schema = %s AND table_name = %s", - (self.cursor.database_name, self.table_name)) + (transaction.database.name, self.table_name)) self._columns = {} - for line in self.cursor.fetchall(): + for line in cursor.fetchall(): column, size, typname, nullable, default = line self._columns[column] = { 'size': size, @@ -113,13 +121,13 @@ class TableHandler(TableHandlerInterface): } # fetch constraints for the table - self.cursor.execute("SELECT constraint_name, constraint_type " + cursor.execute("SELECT constraint_name, constraint_type " "FROM information_schema.table_constraints " "WHERE table_schema = %s AND table_name = %s", - (self.cursor.database_name, self.table_name)) + (transaction.database.name, self.table_name)) self._constraints = [] self._fkeys = [] - for line in self.cursor.fetchall(): + for line in cursor.fetchall(): conname, contype = line if contype not in ('PRIMARY KEY', 'FOREIGN KEY'): self._constraints.append(conname) @@ -127,37 +135,40 @@ class TableHandler(TableHandlerInterface): self._fkeys.append(conname) # Fetch indexes defined for the table - self.cursor.execute('SHOW INDEXES FROM `%s`' % self.table_name) - self._indexes = list(set(x[2] for x in self.cursor.fetchall() + cursor.execute('SHOW INDEXES FROM `%s`' % self.table_name) + self._indexes = list(set(x[2] for x in cursor.fetchall() if x[2] != 'PRIMARY')) # Keep track of which module created each field self._field2module = {} if self.object_name is not None: - self.cursor.execute('SELECT f.name, f.module ' + cursor.execute('SELECT f.name, f.module ' 'FROM ir_model_field f ' 'JOIN ir_model m on (f.model=m.id) ' 'WHERE m.model = %s', (self.object_name,)) - for line in self.cursor.fetchall(): + for line in cursor.fetchall(): self._field2module[line[0]] = line[1] def alter_size(self, column_name, column_type): - self.cursor.execute('ALTER TABLE `%s` ' + cursor = Transaction().connection.cursor() + cursor.execute('ALTER TABLE `%s` ' 'MODIFY COLUMN `%s` %s' % (self.table_name, column_name, self._column_definition(column_name))) self._update_definitions() def alter_type(self, column_name, column_type): - self.cursor.execute('ALTER TABLE `%s` ' + cursor = Transaction().connection.cursor() + cursor.execute('ALTER TABLE `%s` ' 'MODIFY COLUMN `%s` %s' % (self.table_name, column_name, self._column_definition(column_name, typname=column_type))) self._update_definitions() def db_default(self, column_name, value): - self.cursor.execute('ALTER TABLE `%s` ' + cursor = Transaction().connection.cursor() + cursor.execute('ALTER TABLE `%s` ' 'MODIFY COLUMN `%s` %s' % (self.table_name, column_name, self._column_definition(column_name, default=value))) @@ -209,19 +220,20 @@ class TableHandler(TableHandlerInterface): field_size) return + cursor = Transaction().connection.cursor() column_type = column_type[1] - self.cursor.execute('ALTER TABLE `%s` ADD COLUMN `%s` %s' % + cursor.execute('ALTER TABLE `%s` ADD COLUMN `%s` %s' % (self.table_name, column_name, column_type)) if column_format: # check if table is non-empty: - self.cursor.execute('SELECT 1 FROM `%s` limit 1' % self.table_name) - if self.cursor.rowcount: + cursor.execute('SELECT 1 FROM `%s` limit 1' % self.table_name) + if cursor.rowcount: # Populate column with default values: default = None if default_fun is not None: default = default_fun() - self.cursor.execute('UPDATE `' + self.table_name + '` ' + cursor.execute('UPDATE `' + self.table_name + '` ' 'SET `' + column_name + '` = %s', (column_format(default),)) @@ -233,17 +245,20 @@ class TableHandler(TableHandlerInterface): conname = '%s_%s_fkey' % (self.table_name, column_name) if conname in self._fkeys: self.drop_fk(column_name) - self.cursor.execute('ALTER TABLE `%s` ' + cursor = Transaction().connection.cursor() + cursor.execute('ALTER TABLE `%s` ' 'ADD CONSTRAINT `%s` FOREIGN KEY (`%s`) ' 'REFERENCES `%s` (id) ON DELETE %s' - % (self.table_name, conname, column_name, reference, on_delete)) + % (self.table_name, conname, column_name, reference, + on_delete)) self._update_definitions() def drop_fk(self, column_name, table=None): conname = '%s_%s_fkey' % (self.table_name, column_name) if conname not in self._fkeys: return - self.cursor.execute('ALTER TABLE `%s` ' + cursor = Transaction().connection.cursor() + cursor.execute('ALTER TABLE `%s` ' 'DROP FOREIGN KEY `%s`' % (self.table_name, conname)) self._update_definitions() @@ -260,123 +275,135 @@ class TableHandler(TableHandlerInterface): if self._columns[k]['typname'] in ('text', 'blob'): return - if action == 'add': - if index_name in self._indexes: - return - self.cursor.execute('CREATE INDEX `' + index_name + '` ' - 'ON `' + self.table_name + '` ' - '( ' + ','.join(['`' + x + '`' for x in column_name]) + ')') - self._update_definitions() - elif action == 'remove': - if len(column_name) == 1: - if (self._field2module.get(column_name[0], self.module_name) - != self.module_name): + with Transaction().connection.cursor() as cursor: + if action == 'add': + if index_name in self._indexes: return - - if index_name in self._indexes: - self.cursor.execute('DROP INDEX `%s` ON `%s`' - % (index_name, self.table_name)) + cursor.execute('CREATE INDEX `' + index_name + '` ' + 'ON `' + self.table_name + + '` ( ' + + ','.join(['`' + x + '`' for x in column_name]) + + ')') self._update_definitions() - else: - raise Exception('Index action not supported!') + elif action == 'remove': + if len(column_name) == 1: + if (self._field2module.get(column_name[0], + self.module_name) != self.module_name): + return + + if index_name in self._indexes: + cursor.execute('DROP INDEX `%s` ON `%s`' + % (index_name, self.table_name)) + self._update_definitions() + else: + raise Exception('Index action not supported!') def not_null_action(self, column_name, action='add'): if not self.column_exist(column_name): return - if action == 'add': - if not self._columns[column_name]['nullable']: - return - self.cursor.execute('SELECT id FROM `%s` ' - 'WHERE `%s` IS NULL' - % (self.table_name, column_name)) - if not self.cursor.rowcount: - self.cursor.execute('ALTER TABLE `%s` ' + with Transaction().connection.cursor() as cursor: + if action == 'add': + if not self._columns[column_name]['nullable']: + return + cursor.execute('SELECT id FROM `%s` ' + 'WHERE `%s` IS NULL' + % (self.table_name, column_name)) + if not cursor.rowcount: + cursor.execute('ALTER TABLE `%s` ' + 'MODIFY COLUMN `%s` %s' + % (self.table_name, column_name, + self._column_definition(column_name, + nullable=False))) + self._update_definitions() + else: + logger.warning( + 'Unable to set column %s ' + 'of table %s not null !\n' + 'Try to re-run: ' + 'trytond.py --update=module\n' + 'If it doesn\'t work, update records ' + 'and execute manually:\n' + 'ALTER TABLE `%s` MODIFY COLUMN `%s` %s', + column_name, self.table_name, self.table_name, + column_name, self._column_definition(column_name, + nullable=False)) + elif action == 'remove': + if self._columns[column_name]['nullable']: + return + if (self._field2module.get(column_name, self.module_name) + != self.module_name): + return + cursor.execute('ALTER TABLE `%s` ' 'MODIFY COLUMN `%s` %s' % (self.table_name, column_name, - self._column_definition(column_name, nullable=False))) + self._column_definition(column_name, nullable=True))) self._update_definitions() else: - logger.warning( - 'Unable to set column %s ' - 'of table %s not null !\n' - 'Try to re-run: ' - 'trytond.py --update=module\n' - 'If it doesn\'t work, update records ' - 'and execute manually:\n' - 'ALTER TABLE `%s` MODIFY COLUMN `%s` %s', - column_name, self.table_name, self.table_name, - column_name, self._column_definition(column_name, - nullable=False)) - elif action == 'remove': - if self._columns[column_name]['nullable']: - return - if (self._field2module.get(column_name, self.module_name) - != self.module_name): - return - self.cursor.execute('ALTER TABLE `%s` ' - 'MODIFY COLUMN `%s` %s' - % (self.table_name, column_name, - self._column_definition(column_name, nullable=True))) - self._update_definitions() - else: - raise Exception('Not null action not supported!') + raise Exception('Not null action not supported!') def add_constraint(self, ident, constraint, exception=False): ident = self.table_name + "_" + ident if ident in self._constraints: # This constrain already exists return - try: - self.cursor.execute('ALTER TABLE `%s` ' - 'ADD CONSTRAINT `%s` %s' - % (self.table_name, ident, constraint), constraint.params) - except Exception: - if exception: - raise - logger.warning( - 'unable to add \'%s\' constraint on table %s !\n' - 'If you want to have it, you should update the records ' - 'and execute manually:\n' - 'ALTER table `%s` ADD CONSTRAINT `%s` %s', - constraint, self.table_name, self.table_name, ident, - constraint, exc_info=True) + + with Transaction().connection.cursor() as cursor: + try: + cursor.execute('ALTER TABLE `%s` ' + 'ADD CONSTRAINT `%s` %s' + % (self.table_name, ident, constraint), constraint.params) + except Exception: + if exception: + raise + logger.warning( + 'unable to add \'%s\' constraint on table %s !\n' + 'If you want to have it, you should update the records ' + 'and execute manually:\n' + 'ALTER table `%s` ADD CONSTRAINT `%s` %s', + constraint, self.table_name, self.table_name, ident, + constraint, exc_info=True) self._update_definitions() def drop_constraint(self, ident, exception=False, table=None): ident = (table or self.table_name) + "_" + ident if ident not in self._constraints: return - try: - self.cursor.execute('ALTER TABLE `%s` ' - 'DROP CONSTRAINT `%s`' - % (self.table_name, ident)) - except Exception: - if exception: - raise - logger.warning( - 'unable to drop \'%s\' constraint on table %s!', - ident, self.table_name) + + with Transaction().connection.cursor() as cursor: + try: + cursor.execute('ALTER TABLE `%s` ' + 'DROP CONSTRAINT `%s`' + % (self.table_name, ident)) + except Exception: + if exception: + raise + logger.warning( + 'unable to drop \'%s\' constraint on table %s!', + ident, self.table_name) self._update_definitions() def drop_column(self, column_name, exception=False): if not self.column_exist(column_name): return - try: - self.cursor.execute( - 'ALTER TABLE `%s` DROP COLUMN `%s`' % - (self.table_name, column_name)) - except Exception: - if exception: - raise - logger.warning( - 'unable to drop \'%s\' column on table %s!', - column_name, self.table_name) + with Transaction().connection.cursor() as cursor: + try: + cursor.execute( + 'ALTER TABLE `%s` DROP COLUMN `%s`' % + (self.table_name, column_name)) + + except Exception: + if exception: + raise + logger.warning( + 'unable to drop \'%s\' column on table %s!', + column_name, self.table_name) self._update_definitions() @staticmethod - def drop_table(cursor, model, table, cascade=False): + def drop_table(model, table, cascade=False): + cursor = Transaction().connection.cursor() cursor.execute('DELETE from ir_model_data where ' 'model = %s', model) diff --git a/trytond/backend/postgresql/database.py b/trytond/backend/postgresql/database.py index 9d2fa11c2d..a393224a1c 100644 --- a/trytond/backend/postgresql/database.py +++ b/trytond/backend/postgresql/database.py @@ -26,11 +26,10 @@ from psycopg2 import OperationalError as DatabaseOperationalError from sql import Flavor -from trytond.backend.database import DatabaseInterface, CursorInterface +from trytond.backend.database import DatabaseInterface from trytond.config import config, parse_uri -__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError', - 'Cursor'] +__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError'] logger = logging.getLogger(__name__) @@ -60,24 +59,26 @@ class Database(DatabaseInterface): _version_cache = {} flavor = Flavor(ilike=True) - def __new__(cls, database_name='template1'): - if database_name in cls._databases: - return cls._databases[database_name] - return DatabaseInterface.__new__(cls, database_name=database_name) + def __new__(cls, name='template1'): + if name in cls._databases: + return cls._databases[name] + return DatabaseInterface.__new__(cls, name=name) - def __init__(self, database_name='template1'): - super(Database, self).__init__(database_name=database_name) - self._databases.setdefault(database_name, self) + def __init__(self, name='template1'): + super(Database, self).__init__(name=name) + self._databases.setdefault(name, self) + self._search_path = None + self._current_user = None def connect(self): if self._connpool is not None: return self - logger.info('connect to "%s"', self.database_name) + logger.info('connect to "%s"', self.name) uri = parse_uri(config.get('database', 'uri')) assert uri.scheme == 'postgresql' host = uri.hostname and "host=%s" % uri.hostname or '' port = uri.port and "port=%s" % uri.port or '' - name = "dbname=%s" % self.database_name + name = "dbname=%s" % self.name user = uri.username and "user=%s" % uri.username or '' password = ("password=%s" % urllib.unquote_plus(uri.password) if uri.password else '') @@ -87,7 +88,7 @@ class Database(DatabaseInterface): self._connpool = ThreadedConnectionPool(minconn, maxconn, dsn) return self - def cursor(self, autocommit=False, readonly=False): + def get_connection(self, autocommit=False, readonly=False): if self._connpool is None: self.connect() conn = self._connpool.getconn() @@ -95,10 +96,13 @@ class Database(DatabaseInterface): conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) else: conn.set_isolation_level(ISOLATION_LEVEL_REPEATABLE_READ) - cursor = Cursor(self._connpool, conn, self) if readonly: + cursor = conn.cursor() cursor.execute('SET TRANSACTION READ ONLY') - return cursor + return conn + + def put_connection(self, connection, close=False): + self._connpool.putconn(connection, close=close) def close(self): if self._connpool is None: @@ -107,23 +111,26 @@ class Database(DatabaseInterface): self._connpool = None @classmethod - def create(cls, cursor, database_name): + def create(cls, connection, database_name): + cursor = connection.cursor() cursor.execute('CREATE DATABASE "' + database_name + '" ' 'TEMPLATE template0 ENCODING \'unicode\'') + connection.commit() cls._list_cache = None - @classmethod - def drop(cls, cursor, database_name): + def drop(self, connection, database_name): + cursor = connection.cursor() cursor.execute('DROP DATABASE "' + database_name + '"') - cls._list_cache = None + Database._list_cache = None - def get_version(self, cursor): - if self.database_name not in self._version_cache: + def get_version(self, connection): + if self.name not in self._version_cache: + cursor = connection.cursor() cursor.execute('SELECT version()') version, = cursor.fetchone() - self._version_cache[self.database_name] = tuple(map(int, + self._version_cache[self.name] = tuple(map(int, RE_VERSION.search(version).groups())) - return self._version_cache[self.database_name] + return self._version_cache[self.name] @staticmethod def dump(database_name): @@ -158,10 +165,8 @@ class Database(DatabaseInterface): from trytond.tools import exec_command_pipe database = Database().connect() - cursor = database.cursor(autocommit=True) - database.create(cursor, database_name) - cursor.commit() - cursor.close() + connection = database.get_connection(autocommit=True) + database.create(connection, database_name) database.close() cmd = ['pg_restore', '--no-owner'] @@ -195,8 +200,8 @@ class Database(DatabaseInterface): raise Exception('Couldn\'t restore database') database = Database(database_name).connect() - cursor = database.cursor() - if not cursor.test(): + cursor = database.get_connection().cursor() + if not database.test(): cursor.close() database.close() raise Exception('Couldn\'t restore database!') @@ -205,35 +210,38 @@ class Database(DatabaseInterface): Database._list_cache = None return True - @staticmethod - def list(cursor): + def list(self): now = time.time() timeout = config.getint('session', 'timeout') res = Database._list_cache if res and abs(Database._list_cache_timestamp - now) < timeout: return res + + connection = self.get_connection() + cursor = connection.cursor() cursor.execute('SELECT datname FROM pg_database ' 'WHERE datistemplate = false ORDER BY datname') res = [] - for db_name, in cursor.fetchall(): + for db_name, in cursor: try: database = Database(db_name).connect() except Exception: continue - cursor2 = database.cursor() - if cursor2.test(): + if database.test(): res.append(db_name) - cursor2.close(close=True) else: - cursor2.close(close=True) database.close() + self.put_connection(connection) + Database._list_cache = res Database._list_cache_timestamp = now return res - @staticmethod - def init(cursor): + def init(self): from trytond.modules import get_module_info + + connection = self.get_connection() + cursor = connection.cursor() sql_file = os.path.join(os.path.dirname(__file__), 'init.sql') with open(sql_file) as fp: for line in fp.read().split(';'): @@ -257,108 +265,72 @@ class Database(DatabaseInterface): 'VALUES (%s, now(), %s, %s)', (0, module_id, dependency)) - -class Cursor(CursorInterface): - - def __init__(self, connpool, conn, database): - super(Cursor, self).__init__() - self._connpool = connpool - self._conn = conn - self._database = database - self._current_user = None - self._search_path = None - self.cursor = conn.cursor() - self.commit() - self.sql_from_log = {} - self.sql_into_log = {} - self.count = { - 'from': 0, - 'into': 0, - } - - @property - def database_name(self): - return self._database.database_name - - # TODO to remove - @property - def dbname(self): - return self.database_name - - def __getattr__(self, name): - return getattr(self.cursor, name) - - def execute(self, sql, params=None): - if params: - return self.cursor.execute(sql, params) - else: - return self.cursor.execute(sql) - - def close(self, close=False): - self.cursor.close() - self.rollback() - self._connpool.putconn(self._conn, close=close) - - def commit(self): - super(Cursor, self).commit() - self._conn.commit() - - def rollback(self): - super(Cursor, self).rollback() - self._conn.rollback() + connection.commit() + self.put_connection(connection) def test(self): - self.cursor.execute('SELECT 1 FROM information_schema.tables ' + connection = self.get_connection() + cursor = connection.cursor() + cursor.execute('SELECT 1 FROM information_schema.tables ' 'WHERE table_name IN %s', (('ir_model', 'ir_model_field', 'ir_ui_view', 'ir_ui_menu', 'res_user', 'res_group', 'ir_module', - 'ir_module_dependency', 'ir_translation', 'ir_lang'),)) - return len(self.cursor.fetchall()) != 0 + 'ir_module_dependency', 'ir_translation', + 'ir_lang'),)) + is_tryton_database = len(cursor.fetchall()) != 0 + self.put_connection(connection) + return is_tryton_database - def nextid(self, table): - self.cursor.execute("SELECT NEXTVAL('" + table + "_id_seq')") - return self.cursor.fetchone()[0] + def nextid(self, connection, table): + cursor = connection.cursor() + cursor.execute("SELECT NEXTVAL('" + table + "_id_seq')") + return cursor.fetchone()[0] - def setnextid(self, table, value): - self.cursor.execute("SELECT SETVAL('" + table + "_id_seq', %d)" - % value) + def setnextid(self, connection, table, value): + cursor = connection.cursor() + cursor.execute("SELECT SETVAL('" + table + "_id_seq', %d)" % value) - def currid(self, table): - self.cursor.execute('SELECT last_value FROM "' + table + '_id_seq"') - return self.cursor.fetchone()[0] + def currid(self, connection, table): + cursor = connection.cursor() + cursor.execute('SELECT last_value FROM "' + table + '_id_seq"') + return cursor.fetchone()[0] - def lock(self, table): - self.cursor.execute('LOCK "%s" IN EXCLUSIVE MODE NOWAIT' % table) + def lock(self, connection, table): + cursor = connection.cursor() + cursor.execute('LOCK "%s" IN EXCLUSIVE MODE NOWAIT' % table) def has_constraint(self): return True - def limit_clause(self, select, limit=None, offset=None): - if limit is not None: - select += ' LIMIT %d' % limit - if offset is not None and offset != 0: - select += ' OFFSET %d' % offset - return select - - def has_returning(self): - # RETURNING clause is available since PostgreSQL 8.2 - return self._database.get_version(self) >= (8, 2) - def has_multirow_insert(self): return True + def get_table_schema(self, connection, table_name): + cursor = connection.cursor() + for schema in self.search_path: + cursor.execute('SELECT 1 ' + 'FROM information_schema.tables ' + 'WHERE table_name = %s AND table_schema = %s', + (table_name, schema)) + if cursor.rowcount: + return schema + @property def current_user(self): if self._current_user is None: - self.execute('SELECT current_user') - self._current_user, = self.fetchone() + connection = self.get_connection() + cursor = connection.cursor() + cursor.execute('SELECT current_user') + self._current_user = cursor.fetchone()[0] return self._current_user @property def search_path(self): if self._search_path is None: - self.execute('SHOW search_path') - path, = self.fetchone() + connection = self.get_connection() + cursor = connection.cursor() + cursor.execute('SHOW search_path') + path, = cursor.fetchone() special_values = { 'user': self.current_user, } diff --git a/trytond/backend/postgresql/table.py b/trytond/backend/postgresql/table.py index 5166cf10a1..9250749f24 100644 --- a/trytond/backend/postgresql/table.py +++ b/trytond/backend/postgresql/table.py @@ -1,6 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. - +from trytond.transaction import Transaction from trytond.backend.table import TableHandlerInterface import logging @@ -11,8 +11,8 @@ logger = logging.getLogger(__name__) class TableHandler(TableHandlerInterface): - def __init__(self, cursor, model, module_name=None, history=False): - super(TableHandler, self).__init__(cursor, model, + def __init__(self, model, module_name=None, history=False): + super(TableHandler, self).__init__(model, module_name=module_name, history=history) self._columns = {} self._constraints = [] @@ -20,93 +20,91 @@ class TableHandler(TableHandlerInterface): self._indexes = [] self._field2module = {} + transaction = Transaction() + cursor = transaction.connection.cursor() # Create sequence if necessary - if not self.sequence_exist(self.cursor, self.sequence_name): - self.cursor.execute('CREATE SEQUENCE "%s"' % self.sequence_name) + if not self.sequence_exist(self.sequence_name): + cursor.execute('CREATE SEQUENCE "%s"' % self.sequence_name) # Create new table if necessary - if not self.table_exist(self.cursor, self.table_name): - self.cursor.execute('CREATE TABLE "%s" ()' % self.table_name) - self.table_schema = self.get_table_schema(self.cursor, self.table_name) + if not self.table_exist(self.table_name): + cursor.execute('CREATE TABLE "%s" ()' % self.table_name) + self.table_schema = transaction.database.get_table_schema( + transaction.connection, self.table_name) - self.cursor.execute('SELECT tableowner = current_user FROM pg_tables ' + cursor.execute('SELECT tableowner = current_user FROM pg_tables ' 'WHERE tablename = %s AND schemaname = %s', (self.table_name, self.table_schema)) - self.is_owner, = self.cursor.fetchone() + self.is_owner, = cursor.fetchone() if model.__doc__ and self.is_owner: - self.cursor.execute('COMMENT ON TABLE "%s" IS \'%s\'' % + cursor.execute('COMMENT ON TABLE "%s" IS \'%s\'' % (self.table_name, model.__doc__.replace("'", "''"))) self._update_definitions() if 'id' not in self._columns: if not self.history: - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'ADD COLUMN id INTEGER ' 'DEFAULT nextval(\'"%s"\') NOT NULL' % (self.table_name, self.sequence_name)) - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'ADD PRIMARY KEY(id)' % self.table_name) else: - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'ADD COLUMN id INTEGER' % self.table_name) self._update_definitions() if self.history and '__id' not in self._columns: - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'ADD COLUMN __id INTEGER ' 'DEFAULT nextval(\'"%s"\') NOT NULL' % (self.table_name, self.sequence_name)) - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'ADD PRIMARY KEY(__id)' % self.table_name) else: default = "nextval('%s'::regclass)" % self.sequence_name if self.history: if self._columns['__id']['default'] != default: - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'ALTER __id SET DEFAULT %s' % (self.table_name, default)) if self._columns['id']['default'] != default: - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'ALTER id SET DEFAULT %s' % (self.table_name, default)) self._update_definitions() - @classmethod - def get_table_schema(cls, cursor, table_name): - for schema in cursor.search_path: - cursor.execute('SELECT 1 ' - 'FROM information_schema.tables ' - 'WHERE table_name = %s AND table_schema = %s', - (table_name, schema)) - if cursor.rowcount: - return schema - - @classmethod - def table_exist(cls, cursor, table_name): - return bool(cls.get_table_schema(cursor, table_name)) + @staticmethod + def table_exist(table_name): + transaction = Transaction() + return bool(transaction.database.get_table_schema( + transaction.connection, table_name)) @staticmethod - def table_rename(cursor, old_name, new_name): + def table_rename(old_name, new_name): + cursor = Transaction().connection.cursor() # Rename table - if (TableHandler.table_exist(cursor, old_name) - and not TableHandler.table_exist(cursor, new_name)): + if (TableHandler.table_exist(old_name) + and not TableHandler.table_exist(new_name)): cursor.execute('ALTER TABLE "%s" RENAME TO "%s"' % (old_name, new_name)) # Rename sequence old_sequence = old_name + '_id_seq' new_sequence = new_name + '_id_seq' - TableHandler.sequence_rename(cursor, old_sequence, new_sequence) + TableHandler.sequence_rename(old_sequence, new_sequence) # Rename history table old_history = old_name + "__history" new_history = new_name + "__history" - if (TableHandler.table_exist(cursor, old_history) - and not TableHandler.table_exist(cursor, new_history)): + if (TableHandler.table_exist(old_history) + and not TableHandler.table_exist(new_history)): cursor.execute('ALTER TABLE "%s" RENAME TO "%s"' % (old_history, new_history)) @classmethod - def sequence_schema(cls, cursor, sequence_name): - for schema in cursor.search_path: + 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', @@ -115,13 +113,14 @@ class TableHandler(TableHandlerInterface): return schema @classmethod - def sequence_exist(cls, cursor, sequence_name): - return bool(cls.sequence_schema(cursor, sequence_name)) + def sequence_exist(cls, sequence_name): + return bool(cls.sequence_schema(sequence_name)) @staticmethod - def sequence_rename(cursor, old_name, new_name): - if (TableHandler.sequence_exist(cursor, old_name) - and not TableHandler.sequence_exist(cursor, new_name)): + 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)) @@ -129,9 +128,10 @@ class TableHandler(TableHandlerInterface): return column_name in self._columns def column_rename(self, old_name, new_name, exception=False): + cursor = Transaction().connection.cursor() if (self.column_exist(old_name) and not self.column_exist(new_name)): - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'RENAME COLUMN "%s" TO "%s"' % (self.table_name, old_name, new_name)) self._update_definitions() @@ -142,15 +142,16 @@ class TableHandler(TableHandlerInterface): self.table_name, new_name)) def _update_definitions(self): + cursor = Transaction().connection.cursor() self._columns = {} # Fetch columns definitions from the table - self.cursor.execute('SELECT ' + cursor.execute('SELECT ' 'column_name, udt_name, is_nullable, character_maximum_length, ' 'column_default ' 'FROM information_schema.columns ' 'WHERE table_name = %s AND table_schema = %s', (self.table_name, self.table_schema)) - for column, typname, nullable, size, default in self.cursor.fetchall(): + for column, typname, nullable, size, default in cursor.fetchall(): self._columns[column] = { 'typname': typname, 'notnull': True if nullable == 'NO' else False, @@ -159,60 +160,61 @@ class TableHandler(TableHandlerInterface): } # fetch constraints for the table - self.cursor.execute('SELECT constraint_name ' + cursor.execute('SELECT constraint_name ' 'FROM information_schema.table_constraints ' 'WHERE table_name = %s AND table_schema = %s', (self.table_name, self.table_schema)) - self._constraints = [c for c, in self.cursor.fetchall()] + self._constraints = [c for c, in cursor.fetchall()] - self.cursor.execute('SELECT k.column_name, r.delete_rule ' + cursor.execute('SELECT k.column_name, r.delete_rule ' 'FROM information_schema.key_column_usage AS k ' 'JOIN information_schema.referential_constraints AS r ' 'ON r.constraint_schema = k.constraint_schema ' 'AND r.constraint_name = k.constraint_name ' 'WHERE k.table_name = %s AND k.table_schema = %s', (self.table_name, self.table_schema)) - self._fk_deltypes = dict(self.cursor.fetchall()) + self._fk_deltypes = dict(cursor.fetchall()) # Fetch indexes defined for the table - self.cursor.execute("SELECT cl2.relname " + cursor.execute("SELECT cl2.relname " "FROM pg_index ind " "JOIN pg_class cl on (cl.oid = ind.indrelid) " "JOIN pg_namespace n ON (cl.relnamespace = n.oid) " "JOIN pg_class cl2 on (cl2.oid = ind.indexrelid) " "WHERE cl.relname = %s AND n.nspname = %s", (self.table_name, self.table_schema)) - self._indexes = [l[0] for l in self.cursor.fetchall()] + self._indexes = [l[0] for l in cursor.fetchall()] # Keep track of which module created each field self._field2module = {} if self.object_name is not None: - self.cursor.execute('SELECT f.name, f.module ' + cursor.execute('SELECT f.name, f.module ' 'FROM ir_model_field f ' 'JOIN ir_model m on (f.model=m.id) ' 'WHERE m.model = %s', (self.object_name,)) - for line in self.cursor.fetchall(): + for line in cursor: self._field2module[line[0]] = line[1] def alter_size(self, column_name, column_type): - - self.cursor.execute("ALTER TABLE \"%s\" " + cursor = Transaction().connection.cursor() + cursor.execute("ALTER TABLE \"%s\" " "RENAME COLUMN \"%s\" TO _temp_change_size" % (self.table_name, column_name)) - self.cursor.execute("ALTER TABLE \"%s\" " + cursor.execute("ALTER TABLE \"%s\" " "ADD COLUMN \"%s\" %s" % (self.table_name, column_name, column_type)) - self.cursor.execute("UPDATE \"%s\" " + cursor.execute("UPDATE \"%s\" " "SET \"%s\" = _temp_change_size::%s" % (self.table_name, column_name, column_type)) - self.cursor.execute("ALTER TABLE \"%s\" " + cursor.execute("ALTER TABLE \"%s\" " "DROP COLUMN _temp_change_size" % (self.table_name,)) self._update_definitions() def alter_type(self, column_name, column_type): - self.cursor.execute('ALTER TABLE "' + self.table_name + '" ' + cursor = Transaction().connection.cursor() + cursor.execute('ALTER TABLE "' + self.table_name + '" ' 'ALTER "' + column_name + '" TYPE ' + column_type) self._update_definitions() @@ -222,21 +224,24 @@ class TableHandler(TableHandlerInterface): else: test = value if self._columns[column_name]['default'] != test: - self.cursor.execute('ALTER TABLE "' + self.table_name + '" ' + cursor = Transaction().connection.cursor() + cursor.execute('ALTER TABLE "' + self.table_name + '" ' 'ALTER COLUMN "' + column_name + '" SET DEFAULT %s', (value,)) def add_raw_column(self, column_name, column_type, column_format, default_fun=None, field_size=None, migrate=True, string=''): + cursor = Transaction().connection.cursor() + def comment(): if self.is_owner: - self.cursor.execute('COMMENT ON COLUMN "%s"."%s" IS \'%s\'' % + cursor.execute('COMMENT ON COLUMN "%s"."%s" IS \'%s\'' % (self.table_name, column_name, string.replace("'", "''"))) if self.column_exist(column_name): if (column_name in ('create_date', 'write_date') and column_type[1].lower() != 'timestamp(6)'): # Migrate dates from timestamp(0) to timestamp - self.cursor.execute('ALTER TABLE "' + self.table_name + '" ' + cursor.execute('ALTER TABLE "' + self.table_name + '" ' 'ALTER COLUMN "' + column_name + '" TYPE timestamp') comment() if not migrate: @@ -279,19 +284,19 @@ class TableHandler(TableHandlerInterface): return column_type = column_type[1] - self.cursor.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' + cursor.execute('ALTER TABLE "%s" ADD COLUMN "%s" %s' % (self.table_name, column_name, column_type)) comment() if column_format: # check if table is non-empty: - self.cursor.execute('SELECT 1 FROM "%s" limit 1' % self.table_name) - if self.cursor.rowcount: + cursor.execute('SELECT 1 FROM "%s" limit 1' % self.table_name) + if cursor.rowcount: # Populate column with default values: default = None if default_fun is not None: default = default_fun() - self.cursor.execute('UPDATE "' + self.table_name + '" ' + cursor.execute('UPDATE "' + self.table_name + '" ' 'SET "' + column_name + '" = %s', (column_format(default),)) @@ -303,20 +308,21 @@ class TableHandler(TableHandlerInterface): else: on_delete = 'SET NULL' + cursor = Transaction().connection.cursor() name = self.table_name + '_' + column_name + '_fkey' - self.cursor.execute('SELECT 1 ' + 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 self.cursor.rowcount: + if not cursor.rowcount: add = True elif self._fk_deltypes.get(column_name) != on_delete: self.drop_fk(column_name) add = True if add: - self.cursor.execute('ALTER TABLE "' + self.table_name + '" ' + cursor.execute('ALTER TABLE "' + self.table_name + '" ' 'ADD CONSTRAINT "' + name + '" ' 'FOREIGN KEY ("' + column_name + '") ' 'REFERENCES "' + reference + '" ' @@ -336,69 +342,73 @@ class TableHandler(TableHandlerInterface): else: test_index_name = index_name - if action == 'add': - if test_index_name in self._indexes: - return - self.cursor.execute('CREATE INDEX "' + index_name + '" ' - 'ON "' + self.table_name + '" ( ' - + ','.join(['"' + x + '"' for x in column_name]) + ')') - self._update_definitions() - elif action == 'remove': - if len(column_name) == 1: - if (self._field2module.get(column_name[0], self.module_name) - != self.module_name): + with Transaction().connection.cursor() as cursor: + if action == 'add': + if test_index_name in self._indexes: return - - if test_index_name in self._indexes: - self.cursor.execute('DROP INDEX "%s" ' % (index_name,)) + cursor.execute('CREATE INDEX "' + index_name + '" ' + 'ON "' + self.table_name + '" ( ' + + ','.join(['"' + x + '"' for x in column_name]) + ')') self._update_definitions() - else: - raise Exception('Index action not supported!') + elif action == 'remove': + if len(column_name) == 1: + if (self._field2module.get(column_name[0], + self.module_name) != self.module_name): + return + + if test_index_name in self._indexes: + cursor.execute('DROP INDEX "%s" ' % (index_name,)) + self._update_definitions() + else: + raise Exception('Index action not supported!') def not_null_action(self, column_name, action='add'): if not self.column_exist(column_name): return - if action == 'add': - if self._columns[column_name]['notnull']: - return - self.cursor.execute('SELECT id FROM "%s" ' - 'WHERE "%s" IS NULL' - % (self.table_name, column_name)) - if not self.cursor.rowcount: - self.cursor.execute('ALTER TABLE "' + self.table_name + '" ' - 'ALTER COLUMN "' + column_name + '" SET NOT NULL') + with Transaction().connection.cursor() as cursor: + if action == 'add': + if self._columns[column_name]['notnull']: + return + cursor.execute('SELECT id FROM "%s" ' + 'WHERE "%s" IS NULL' + % (self.table_name, column_name)) + if not cursor.rowcount: + cursor.execute('ALTER TABLE "' + self.table_name + '" ' + 'ALTER COLUMN "' + column_name + '" SET NOT NULL') + self._update_definitions() + else: + logger.warning( + 'Unable to set column %s ' + 'of table %s not null !\n' + 'Try to re-run: ' + 'trytond.py --update=module\n' + 'If it doesn\'t work, update records ' + 'and execute manually:\n' + 'ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL', + column_name, self.table_name, self.table_name, + column_name) + elif action == 'remove': + if not self._columns[column_name]['notnull']: + return + if (self._field2module.get(column_name, self.module_name) + != self.module_name): + return + cursor.execute('ALTER TABLE "%s" ' + 'ALTER COLUMN "%s" DROP NOT NULL' + % (self.table_name, column_name)) self._update_definitions() else: - logger.warning( - 'Unable to set column %s ' - 'of table %s not null !\n' - 'Try to re-run: ' - 'trytond.py --update=module\n' - 'If it doesn\'t work, update records ' - 'and execute manually:\n' - 'ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL', - column_name, self.table_name, self.table_name, column_name) - elif action == 'remove': - if not self._columns[column_name]['notnull']: - return - if (self._field2module.get(column_name, self.module_name) - != self.module_name): - return - self.cursor.execute('ALTER TABLE "%s" ' - 'ALTER COLUMN "%s" DROP NOT NULL' - % (self.table_name, column_name)) - self._update_definitions() - else: - raise Exception('Not null action not supported!') + raise Exception('Not null action not supported!') def add_constraint(self, ident, constraint, exception=False): ident = self.table_name + "_" + ident if ident in self._constraints: # This constrain already exist return + cursor = Transaction().connection.cursor() try: - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'ADD CONSTRAINT "%s" %s' % (self.table_name, ident, constraint), constraint.params) except Exception: @@ -417,8 +427,9 @@ class TableHandler(TableHandlerInterface): ident = (table or self.table_name) + "_" + ident if ident not in self._constraints: return + cursor = Transaction().connection.cursor() try: - self.cursor.execute('ALTER TABLE "%s" ' + cursor.execute('ALTER TABLE "%s" ' 'DROP CONSTRAINT "%s"' % (self.table_name, ident)) except Exception: @@ -432,8 +443,9 @@ class TableHandler(TableHandlerInterface): def drop_column(self, column_name, exception=False): if not self.column_exist(column_name): return + cursor = Transaction().connection.cursor() try: - self.cursor.execute( + cursor.execute( 'ALTER TABLE "%s" DROP COLUMN "%s"' % (self.table_name, column_name)) @@ -447,6 +459,7 @@ class TableHandler(TableHandlerInterface): @staticmethod def drop_table(cursor, model, table, cascade=False): + cursor = Transaction().connection.cursor() cursor.execute('DELETE FROM ir_model_data ' 'WHERE model = \'%s\'' % model) diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py index ba9cbcbb03..495558c7eb 100644 --- a/trytond/backend/sqlite/database.py +++ b/trytond/backend/sqlite/database.py @@ -1,6 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.backend.database import DatabaseInterface, CursorInterface +from trytond.backend.database import DatabaseInterface from trytond.config import config import os from decimal import Decimal @@ -24,8 +24,7 @@ from sql import Flavor, Table from sql.functions import (Function, Extract, Position, Substring, Overlay, CharLength, CurrentTimestamp) -__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError', - 'Cursor'] +__all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError'] class SQLiteExtract(Function): @@ -178,35 +177,52 @@ MAPPING = { } +class SQLiteCursor(sqlite.Cursor): + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + +class SQLiteConnection(sqlite.Connection): + + def cursor(self): + return super(SQLiteConnection, self).cursor(SQLiteCursor) + + class Database(DatabaseInterface): _local = threading.local() _conn = None flavor = Flavor(paramstyle='qmark', function_mapping=MAPPING) + IN_MAX = 200 - def __new__(cls, database_name=':memory:'): - if (database_name == ':memory:' + def __new__(cls, name=':memory:'): + if (name == ':memory:' and getattr(cls._local, 'memory_database', None)): return cls._local.memory_database - return DatabaseInterface.__new__(cls, database_name=database_name) + return DatabaseInterface.__new__(cls, name=name) - def __init__(self, database_name=':memory:'): - super(Database, self).__init__(database_name=database_name) - if database_name == ':memory:': + def __init__(self, name=':memory:'): + super(Database, self).__init__(name=name) + if name == ':memory:': Database._local.memory_database = self def connect(self): - if self.database_name == ':memory:': + if self.name == ':memory:': path = ':memory:' else: - db_filename = self.database_name + '.sqlite' + db_filename = self.name + '.sqlite' path = os.path.join(config.get('database', 'path'), db_filename) if not os.path.isfile(path): raise IOError('Database "%s" doesn\'t exist!' % db_filename) if self._conn is not None: return self self._conn = sqlite.connect(path, - detect_types=sqlite.PARSE_DECLTYPES | sqlite.PARSE_COLNAMES) + detect_types=sqlite.PARSE_DECLTYPES | sqlite.PARSE_COLNAMES, + factory=SQLiteConnection) self._conn.create_function('extract', 2, SQLiteExtract.extract) self._conn.create_function('date_trunc', 2, date_trunc) self._conn.create_function('split_part', 3, split_part) @@ -222,24 +238,27 @@ class Database(DatabaseInterface): self._conn.execute('PRAGMA foreign_keys = ON') return self - def cursor(self, autocommit=False, readonly=False): + def get_connection(self, autocommit=False, readonly=False): if self._conn is None: self.connect() if autocommit: self._conn.isolation_level = None else: self._conn.isolation_level = 'IMMEDIATE' - return Cursor(self._conn, self.database_name) + return self._conn + + def put_connection(self, connection=None, close=False): + pass def close(self): - if self.database_name == ':memory:': + if self.name == ':memory:': return if self._conn is None: return self._conn = None - @staticmethod - def create(cursor, database_name): + @classmethod + def create(cls, connection, database_name): if database_name == ':memory:': path = ':memory:' else: @@ -251,10 +270,9 @@ class Database(DatabaseInterface): cursor = conn.cursor() cursor.close() - @classmethod - def drop(cls, cursor, database_name): + def drop(self, connection, database_name): if database_name == ':memory:': - cls._local.memory_database._conn = None + self._local.memory_database._conn = None return if os.sep in database_name: return @@ -286,8 +304,7 @@ class Database(DatabaseInterface): with open(path, 'wb') as file_p: file_p.write(data) - @staticmethod - def list(cursor): + def list(self): res = [] listdir = [':memory:'] try: @@ -304,77 +321,45 @@ class Database(DatabaseInterface): database = Database(db_name) except Exception: continue - cursor2 = database.cursor() - if cursor2.test(): + if database.test(): res.append(db_name) - cursor2.close() + database.close() return res - @staticmethod - def init(cursor): + def init(self): from trytond.modules import get_module_info - sql_file = os.path.join(os.path.dirname(__file__), 'init.sql') - with open(sql_file) as fp: - for line in fp.read().split(';'): - if (len(line) > 0) and (not line.isspace()): - cursor.execute(line) + with self._conn as conn: + cursor = conn.cursor() + sql_file = os.path.join(os.path.dirname(__file__), 'init.sql') + with open(sql_file) as fp: + for line in fp.read().split(';'): + if (len(line) > 0) and (not line.isspace()): + cursor.execute(line) - ir_module = Table('ir_module') - ir_module_dependency = Table('ir_module_dependency') - for module in ('ir', 'res'): - state = 'uninstalled' - if module in ('ir', 'res'): - state = 'to install' - info = get_module_info(module) - insert = ir_module.insert( - [ir_module.create_uid, ir_module.create_date, ir_module.name, - ir_module.state], - [[0, CurrentTimestamp(), module, state]]) - cursor.execute(*insert) - cursor.execute('SELECT last_insert_rowid()') - module_id, = cursor.fetchone() - for dependency in info.get('depends', []): - insert = ir_module_dependency.insert( - [ir_module_dependency.create_uid, - ir_module_dependency.create_date, - ir_module_dependency.module, ir_module_dependency.name - ], - [[0, CurrentTimestamp(), module_id, dependency]]) + ir_module = Table('ir_module') + ir_module_dependency = Table('ir_module_dependency') + for module in ('ir', 'res'): + state = 'uninstalled' + if module in ('ir', 'res'): + state = 'to install' + info = get_module_info(module) + insert = ir_module.insert( + [ir_module.create_uid, ir_module.create_date, + ir_module.name, ir_module.state], + [[0, CurrentTimestamp(), module, state]]) cursor.execute(*insert) - - -class Cursor(CursorInterface): - IN_MAX = 200 - - def __init__(self, conn, database_name): - super(Cursor, self).__init__() - self._conn = conn - self.database_name = database_name - self.dbname = self.database_name # XXX to remove - self.cursor = conn.cursor() - - def __getattr__(self, name): - if _FIX_ROWCOUNT and name == 'rowcount': - return -1 - return getattr(self.cursor, name) - - def execute(self, sql, params=None): - if params: - return self.cursor.execute(sql, params) - else: - return self.cursor.execute(sql) - - def close(self, close=False): - self.cursor.close() - self.rollback() - - def commit(self): - super(Cursor, self).commit() - self._conn.commit() - - def rollback(self): - super(Cursor, self).rollback() - self._conn.rollback() + cursor.execute('SELECT last_insert_rowid()') + module_id, = cursor.fetchone() + for dependency in info.get('depends', []): + insert = ir_module_dependency.insert( + [ir_module_dependency.create_uid, + ir_module_dependency.create_date, + ir_module_dependency.module, + ir_module_dependency.name, + ], + [[0, CurrentTimestamp(), module_id, dependency]]) + cursor.execute(*insert) + conn.commit() def test(self): sqlite_master = Table('sqlite_master') @@ -392,17 +377,19 @@ class Cursor(CursorInterface): 'ir_translation', 'ir_lang', ]) - try: - self.cursor.execute(*select) - except Exception: - return False - return len(self.cursor.fetchall()) != 0 + with self._conn as conn: + cursor = conn.cursor() + try: + cursor.execute(*select) + except Exception: + return False + return len(cursor.fetchall()) != 0 - def lastid(self): - self.cursor.execute('SELECT last_insert_rowid()') - return self.cursor.fetchone()[0] + def lastid(self, cursor): + # This call is not thread safe + return cursor.lastrowid - def lock(self, table): + def lock(self, connection, table): pass def has_constraint(self): diff --git a/trytond/backend/sqlite/table.py b/trytond/backend/sqlite/table.py index 9debc8818f..56fb9f26c7 100644 --- a/trytond/backend/sqlite/table.py +++ b/trytond/backend/sqlite/table.py @@ -1,6 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. +from trytond.transaction import Transaction from trytond.backend.table import TableHandlerInterface import logging import re @@ -12,8 +13,8 @@ logger = logging.getLogger(__name__) class TableHandler(TableHandlerInterface): - def __init__(self, cursor, model, module_name=None, history=False): - super(TableHandler, self).__init__(cursor, model, + def __init__(self, model, module_name=None, history=False): + super(TableHandler, self).__init__(model, module_name=module_name, history=history) self._columns = {} self._constraints = [] @@ -22,21 +23,23 @@ class TableHandler(TableHandlerInterface): self._field2module = {} self._model = model + cursor = Transaction().connection.cursor() # Create new table if necessary - if not self.table_exist(self.cursor, self.table_name): + if not self.table_exist(self.table_name): if not self.history: - self.cursor.execute('CREATE TABLE "%s" ' + cursor.execute('CREATE TABLE "%s" ' '(id INTEGER PRIMARY KEY AUTOINCREMENT)' % self.table_name) else: - self.cursor.execute('CREATE TABLE "%s" ' + cursor.execute('CREATE TABLE "%s" ' '(__id INTEGER PRIMARY KEY AUTOINCREMENT, ' 'id INTEGER)' % self.table_name) self._update_definitions() @staticmethod - def table_exist(cursor, table_name): + def table_exist(table_name): + cursor = Transaction().connection.cursor() cursor.execute("SELECT sql FROM sqlite_master " "WHERE type = 'table' AND name = ?", (table_name,)) @@ -69,38 +72,38 @@ class TableHandler(TableHandlerInterface): return True @staticmethod - def table_rename(cursor, old_name, new_name): - if (TableHandler.table_exist(cursor, old_name) - and not TableHandler.table_exist(cursor, new_name)): + def table_rename(old_name, new_name): + cursor = Transaction().connection.cursor() + if (TableHandler.table_exist(old_name) + and not TableHandler.table_exist(new_name)): cursor.execute('ALTER TABLE "%s" RENAME TO "%s"' % (old_name, new_name)) # Rename history table old_history = old_name + "__history" new_history = new_name + "__history" - if (TableHandler.table_exist(cursor, old_history) - and not TableHandler.table_exist(cursor, new_history)): + if (TableHandler.table_exist(old_history) + and not TableHandler.table_exist(new_history)): cursor.execute('ALTER TABLE "%s" RENAME TO "%s"' % (old_history, new_history)) @staticmethod - def sequence_exist(cursor, sequence_name): + def sequence_exist(sequence_name): return True @staticmethod - def sequence_rename(cursor, old_name, new_name): + def sequence_rename(old_name, new_name): pass def column_exist(self, column_name): return column_name in self._columns def column_rename(self, old_name, new_name, exception=False): + cursor = Transaction().connection.cursor() if self.column_exist(old_name) and \ not self.column_exist(new_name): temp_table = '_temp_%s' % self.table_name - TableHandler.table_rename(self.cursor, self.table_name, - temp_table) - new_table = TableHandler(self.cursor, self._model, - history=self.history) + TableHandler.table_rename(self.table_name, temp_table) + new_table = TableHandler(self._model, history=self.history) for column, (notnull, hasdef, size, typname) \ in self._columns.iteritems(): if column == old_name: @@ -110,12 +113,12 @@ class TableHandler(TableHandlerInterface): new_columns = new_table._columns.keys() old_columns = [x if x != old_name else new_name for x in new_columns] - self.cursor.execute(('INSERT INTO "%s" (' + + cursor.execute(('INSERT INTO "%s" (' + ','.join('"%s"' % x for x in new_columns) + ') SELECT ' + ','.join('"%s"' % x for x in old_columns) + ' ' + 'FROM "%s"') % (self.table_name, temp_table)) - self.cursor.execute('DROP TABLE "%s"' % temp_table) + cursor.execute('DROP TABLE "%s"' % temp_table) self._update_definitions() elif exception and self.column_exist(new_name): raise Exception('Unable to rename column %s.%s to %s.%s: ' @@ -124,11 +127,11 @@ class TableHandler(TableHandlerInterface): self.table_name, new_name)) def _update_definitions(self): - + cursor = Transaction().connection.cursor() # Fetch columns definitions from the table - self.cursor.execute('PRAGMA table_info("' + self.table_name + '")') + cursor.execute('PRAGMA table_info("' + self.table_name + '")') self._columns = {} - for _, column, type_, notnull, hasdef, _ in self.cursor.fetchall(): + for _, column, type_, notnull, hasdef, _ in cursor.fetchall(): column = re.sub(r'^\"|\"$', '', column) match = re.match(r'(\w+)(\((.*?)\))?', type_) if match: @@ -146,20 +149,20 @@ class TableHandler(TableHandlerInterface): # Fetch indexes defined for the table try: - self.cursor.execute('PRAGMA index_list("' + self.table_name + '")') + cursor.execute('PRAGMA index_list("' + self.table_name + '")') except IndexError: # There is sometimes IndexError - self.cursor.execute('PRAGMA index_list("' + self.table_name + '")') - self._indexes = [l[1] for l in self.cursor.fetchall()] + cursor.execute('PRAGMA index_list("' + self.table_name + '")') + self._indexes = [l[1] for l in cursor.fetchall()] # Keep track of which module created each field self._field2module = {} if self.object_name is not None: - self.cursor.execute('SELECT f.name, f.module ' + cursor.execute('SELECT f.name, f.module ' 'FROM ir_model_field f ' 'JOIN ir_model m on (f.model=m.id) ' 'WHERE m.model = ?', (self.object_name,)) - for line in self.cursor.fetchall(): + for line in cursor.fetchall(): self._field2module[line[0]] = line[1] def alter_size(self, column_name, column_type): @@ -213,20 +216,21 @@ class TableHandler(TableHandlerInterface): field_size) return + cursor = Transaction().connection.cursor() column_type = column_type[1] default = '' - self.cursor.execute(('ALTER TABLE "%s" ADD COLUMN "%s" %s' + default) % + cursor.execute(('ALTER TABLE "%s" ADD COLUMN "%s" %s' + default) % (self.table_name, column_name, column_type)) if column_format: # check if table is non-empty: - self.cursor.execute('SELECT 1 FROM "%s" limit 1' % self.table_name) - if self.cursor.fetchone(): + cursor.execute('SELECT 1 FROM "%s" limit 1' % self.table_name) + if cursor.fetchone(): # Populate column with default values: default = None if default_fun is not None: default = default_fun() - self.cursor.execute('UPDATE "' + self.table_name + '" ' + cursor.execute('UPDATE "' + self.table_name + '" ' 'SET "' + column_name + '" = ?', (column_format(default),)) @@ -243,22 +247,23 @@ class TableHandler(TableHandlerInterface): column_name = [column_name] index_name = self.table_name + "_" + '_'.join(column_name) + "_index" + cursor = Transaction().connection.cursor() if action == 'add': if index_name in self._indexes: return - self.cursor.execute('CREATE INDEX "' + index_name + '" ' + cursor.execute('CREATE INDEX "' + index_name + '" ' 'ON "' + self.table_name + '" ( ' + ','.join('"' + x + '"' for x in column_name) + ')') self._update_definitions() elif action == 'remove': if len(column_name) == 1: - if self._field2module.get(column_name[0], self.module_name) \ - != self.module_name: + if self._field2module.get(column_name[0], + self.module_name) != self.module_name: return if index_name in self._indexes: - self.cursor.execute('DROP INDEX "%s" ' % (index_name,)) + cursor.execute('DROP INDEX "%s" ' % (index_name,)) self._update_definitions() else: raise Exception('Index action not supported!') @@ -284,7 +289,8 @@ class TableHandler(TableHandlerInterface): warnings.warn('Unable to drop column with SQLite backend') @staticmethod - def drop_table(cursor, model, table, cascade=False): + def drop_table(model, table, cascade=False): + cursor = Transaction().connection.cursor() cursor.execute('DELETE from ir_model_data where ' 'model = \'%s\'' % model) diff --git a/trytond/backend/table.py b/trytond/backend/table.py index afdeb6bae9..887d415af8 100644 --- a/trytond/backend/table.py +++ b/trytond/backend/table.py @@ -7,15 +7,13 @@ class TableHandlerInterface(object): Define generic interface to handle database table ''' - def __init__(self, cursor, model, module_name=None, history=False): + def __init__(self, model, module_name=None, history=False): ''' - :param cursor: the database cursor :param model: the Model linked to the table :param module_name: the module name :param history: a boolean to define if it is a history table ''' super(TableHandlerInterface, self).__init__() - self.cursor = cursor if history: self.table_name = model._table + '__history' else: @@ -29,44 +27,40 @@ class TableHandlerInterface(object): self.history = history @staticmethod - def table_exist(cursor, table_name): + def table_exist(table_name): ''' Table exist - :param cursor: the database cursor :param table_name: the table name :return: a boolean ''' raise NotImplementedError @staticmethod - def table_rename(cursor, old_name, new_name): + def table_rename(old_name, new_name): ''' Rename table - :param cursor: the database cursor :param old_name: the old table name :param new_name: the new table name ''' raise NotImplementedError @staticmethod - def sequence_exist(cursor, sequence_name): + def sequence_exist(sequence_name): ''' Sequence exist - :param cursor: the database cursor :param sequence_name: the sequence name :return: a boolean ''' raise NotImplementedError @staticmethod - def sequence_rename(cursor, old_name, new_name): + def sequence_rename(old_name, new_name): ''' Rename sequence - :param cursor: the database cursor :param old_name: the old sequence name :param new_name: the new sequence name ''' @@ -206,7 +200,7 @@ class TableHandlerInterface(object): raise NotImplementedError @staticmethod - def drop_table(cursor, model, table, cascade=False): + def drop_table(model, table, cascade=False): ''' Remove a table and clean ir_model_data from the given model. diff --git a/trytond/cache.py b/trytond/cache.py index a23998740e..f9a445b645 100644 --- a/trytond/cache.py +++ b/trytond/cache.py @@ -43,11 +43,10 @@ class Cache(object): return key def get(self, key, default=None): - cursor = Transaction().cursor + dbname = Transaction().database.name key = self._key(key) with self._lock: - cache = self._cache.setdefault(cursor.dbname, - LRUDict(self.size_limit)) + cache = self._cache.setdefault(dbname, LRUDict(self.size_limit)) try: result = cache[key] = cache.pop(key) return result @@ -55,11 +54,10 @@ class Cache(object): return default def set(self, key, value): - cursor = Transaction().cursor + dbname = Transaction().database.name key = self._key(key) with self._lock: - cache = self._cache.setdefault(cursor.dbname, - LRUDict(self.size_limit)) + cache = self._cache.setdefault(dbname, LRUDict(self.size_limit)) try: cache[key] = value except TypeError: @@ -67,15 +65,15 @@ class Cache(object): return value def clear(self): - cursor = Transaction().cursor - Cache.reset(cursor.dbname, self._name) + dbname = Transaction().database.name + Cache.reset(dbname, self._name) with self._lock: - self._cache[cursor.dbname] = LRUDict(self.size_limit) + self._cache[dbname] = LRUDict(self.size_limit) @staticmethod def clean(dbname): - with Transaction().new_cursor(): - cursor = Transaction().cursor + with Transaction().new_transaction() as transaction,\ + transaction.connection.cursor() as cursor: table = Table('ir_cache') cursor.execute(*table.select(table.timestamp, table.name)) timestamps = {} @@ -97,25 +95,24 @@ class Cache(object): @staticmethod def resets(dbname): - with Transaction().new_cursor(): - cursor = Transaction().cursor - table = Table('ir_cache') - with Cache._resets_lock: - Cache._resets.setdefault(dbname, set()) - for name in Cache._resets[dbname]: - cursor.execute(*table.select(table.name, + table = Table('ir_cache') + with Transaction().new_transaction() as transaction,\ + transaction.connection.cursor() as cursor,\ + Cache._resets_lock: + Cache._resets.setdefault(dbname, set()) + for name in Cache._resets[dbname]: + cursor.execute(*table.select(table.name, + where=table.name == name)) + if cursor.fetchone(): + # It would be better to insert only + cursor.execute(*table.update([table.timestamp], + [CurrentTimestamp()], where=table.name == name)) - if cursor.fetchone(): - # It would be better to insert only - cursor.execute(*table.update([table.timestamp], - [CurrentTimestamp()], - where=table.name == name)) - else: - cursor.execute(*table.insert( - [table.timestamp, table.name], - [[CurrentTimestamp(), name]])) - Cache._resets[dbname].clear() - cursor.commit() + else: + cursor.execute(*table.insert( + [table.timestamp, table.name], + [[CurrentTimestamp(), name]])) + Cache._resets[dbname].clear() @classmethod def drop(cls, dbname): diff --git a/trytond/convert.py b/trytond/convert.py index 48a4eebef2..be6a377ead 100644 --- a/trytond/convert.py +++ b/trytond/convert.py @@ -43,7 +43,7 @@ class MenuitemTagHandler: self.xml_id = None def startElement(self, name, attributes): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() values = {} @@ -354,7 +354,7 @@ class Fs2bdAccessor: models = Model.browse(ids) for model in models: if model.id in self.browserecord[module][model_name]: - for cache in Transaction().cursor.cache.values(): + for cache in Transaction().cache.values(): for cache in (cache, cache.get('_language_cache', {}).values()): if (model_name in cache @@ -783,7 +783,7 @@ def post_import(pool, module, to_delete): """ Remove the records that are given in to_delete. """ - cursor = Transaction().cursor + transaction = Transaction() mdata_delete = [] ModelData = pool.get("ir.model.data") @@ -810,9 +810,9 @@ def post_import(pool, module, to_delete): logger.warning( 'Could not delete id %d of model %s because model no ' 'longer exists.', db_id, model) - cursor.commit() + transaction.commit() except Exception: - cursor.rollback() + transaction.rollback() logger.error( 'Could not delete id: %d of model %s\n' 'There should be some relation ' @@ -826,7 +826,7 @@ def post_import(pool, module, to_delete): 'active': False, }) except Exception: - cursor.rollback() + transaction.rollback() logger.error( 'Could not inactivate id: %d of model %s\n', db_id, model, exc_info=True) @@ -834,6 +834,6 @@ def post_import(pool, module, to_delete): # Clean model_data: if mdata_delete: ModelData.delete(mdata_delete) - cursor.commit() + transaction.commit() return True diff --git a/trytond/ir/action.py b/trytond/ir/action.py index 5ff7e7fcbf..7c84d51fbd 100644 --- a/trytond/ir/action.py +++ b/trytond/ir/action.py @@ -129,7 +129,7 @@ class ActionKeyword(ModelSQL, ModelView): TableHandler = backend.get('TableHandler') super(ActionKeyword, cls).__register__(module_name) - table = TableHandler(Transaction().cursor, cls, module_name) + table = TableHandler(cls, module_name) table.index_action(['keyword', 'model'], 'add') def get_groups(self, name): @@ -311,9 +311,12 @@ class ActionMixin(ModelSQL): for field in later: del values[field] action_values['type'] = cls.default_type() - cursor = Transaction().cursor - if cursor.nextid(cls._table): - cursor.setnextid(cls._table, cursor.currid(Action._table)) + transaction = Transaction() + database = transaction.database + cursor = transaction.connection.cursor() + if database.nextid(transaction.connection, cls._table): + database.setnextid(transaction.connection, cls._table, + database.currid(transaction.connection, Action._table)) if 'action' not in values: action, = Action.create([action_values]) values['action'] = action.id @@ -323,7 +326,8 @@ class ActionMixin(ModelSQL): cursor.execute(*ir_action.update( [ir_action.id], [action.id], where=ir_action.id == record.id)) - cursor.update_auto_increment(cls._table, action.id) + transaction.database.update_auto_increment( + transaction.connection, cls._table, action.id) record = cls(action.id) new_records.append(record) cls.write([record], later) @@ -481,8 +485,9 @@ class ActionReport(ActionMixin, ModelSQL, ModelView): TableHandler = backend.get('TableHandler') super(ActionReport, cls).__register__(module_name) - cursor = Transaction().cursor - table = TableHandler(cursor, cls, module_name) + transaction = Transaction() + cursor = transaction.connection.cursor() + table = TableHandler(cls, module_name) action_report = cls.__table__() # Migration from 1.0 report_name_uniq has been removed @@ -503,7 +508,7 @@ class ActionReport(ActionMixin, ModelSQL, ModelView): cls.write(cls.browse(ids), {'extension': 'odt'}) table.drop_column("output_format") - TableHandler.dropTable(cursor, 'ir.action.report.outputformat', + TableHandler.dropTable('ir.action.report.outputformat', 'ir_action_report_outputformat') # Migrate from 2.0 remove required on extension @@ -517,7 +522,7 @@ class ActionReport(ActionMixin, ModelSQL, ModelView): # report_content_custom to remove base64 encoding if (table.column_exist('report_content_data') and table.column_exist('report_content_custom')): - limit = cursor.IN_MAX + limit = transaction.database.IN_MAX cursor.execute(*action_report.select( Count(action_report.id))) report_count, = cursor.fetchone() @@ -705,12 +710,12 @@ class ActionActWindow(ActionMixin, ModelSQL, ModelView): @classmethod def __register__(cls, module_name): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() TableHandler = backend.get('TableHandler') act_window = cls.__table__() super(ActionActWindow, cls).__register__(module_name) - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 2.0: new search_value format cursor.execute(*act_window.update( @@ -903,7 +908,7 @@ class ActionActWindowView(ModelSQL, ModelView): def __register__(cls, module_name): TableHandler = backend.get('TableHandler') super(ActionActWindowView, cls).__register__(module_name) - table = TableHandler(Transaction().cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 1.0 remove multi table.drop_column('multi') diff --git a/trytond/ir/attachment.py b/trytond/ir/attachment.py index 8b3427b866..1f23e338e9 100644 --- a/trytond/ir/attachment.py +++ b/trytond/ir/attachment.py @@ -58,11 +58,11 @@ class Attachment(ResourceMixin, ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() super(Attachment, cls).__register__(module_name) - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) attachment = cls.__table__() # Migration from 1.4 res_model and res_id merged into resource @@ -86,7 +86,7 @@ class Attachment(ResourceMixin, ModelSQL, ModelView): return 0 def get_data(self, name): - db_name = Transaction().cursor.dbname + db_name = Transaction().database.name format_ = Transaction().context.pop('%s.%s' % (self.__name__, name), '') value = None @@ -116,9 +116,10 @@ class Attachment(ResourceMixin, ModelSQL, ModelView): def set_data(cls, attachments, name, value): if value is None: return - cursor = Transaction().cursor + transaction = Transaction() + cursor = transaction.connection.cursor() table = cls.__table__() - db_name = cursor.dbname + db_name = transaction.database.name directory = os.path.join(config.get('database', 'path'), db_name) if not os.path.isdir(directory): os.makedirs(directory, 0770) diff --git a/trytond/ir/cron.py b/trytond/ir/cron.py index 1a2960132c..59cd6f3dc0 100644 --- a/trytond/ir/cron.py +++ b/trytond/ir/cron.py @@ -79,11 +79,11 @@ class Cron(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() cron = cls.__table__() # Migration from 2.0: rename numbercall, doall and nextcall - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) table.column_rename('numbercall', 'number_calls') table.column_rename('doall', 'repeat_missed') table.column_rename('nextcall', 'next_call') @@ -186,7 +186,7 @@ class Cron(ModelSQL, ModelView): now = datetime.datetime.now() with Transaction().start(db_name, 0) as transaction: Cache.clean(db_name) - transaction.cursor.lock(cls._table) + transaction.database.lock(transaction.connection, cls._table) crons = cls.search([ ('number_calls', '!=', 0), ('next_call', '<=', datetime.datetime.now()), @@ -202,7 +202,7 @@ class Cron(ModelSQL, ModelView): try: cron.run_once() except Exception: - transaction.cursor.rollback() + transaction.rollback() cron.send_error_message() next_call += cls.get_delta(cron) if number_calls > 0: @@ -214,8 +214,8 @@ class Cron(ModelSQL, ModelView): if not number_calls: cron.active = False cron.save() - transaction.cursor.commit() + transaction.commit() except Exception: - transaction.cursor.rollback() + transaction.rollback() logger.error('Running cron %s', cron.id, exc_info=True) Cache.resets(db_name) diff --git a/trytond/ir/lang.py b/trytond/ir/lang.py index 47d52dfdaf..f429ffef30 100644 --- a/trytond/ir/lang.py +++ b/trytond/ir/lang.py @@ -16,9 +16,8 @@ from ..tools import datetime_strftime from ..transaction import Transaction from ..pool import Pool from .time_locale import TIME_LOCALE -from ..backend.database import CursorInterface -CursorInterface.cache_keys.add('translate_name') +Transaction.cache_keys.add('translate_name') __all__ = [ 'Lang', diff --git a/trytond/ir/model.py b/trytond/ir/model.py index 01e0b11b98..0fe55cce5d 100644 --- a/trytond/ir/model.py +++ b/trytond/ir/model.py @@ -22,7 +22,7 @@ from ..pyson import Bool, Eval from ..rpc import RPC from .. import backend from ..protocols.jsonrpc import JSONDecoder, JSONEncoder -from ..tools import is_instance_method +from ..tools import is_instance_method, cursor_dict try: from ..tools.StringMatcher import StringMatcher except ImportError: @@ -84,7 +84,7 @@ class Model(ModelSQL, ModelView): def register(cls, model, module_name): pool = Pool() Property = pool.get('ir.property') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() ir_model = cls.__table__() cursor.execute(*ir_model.select(ir_model.id, @@ -265,7 +265,7 @@ class ModelField(ModelSQL, ModelView): def register(cls, model, module_name, model_id): pool = Pool() Model = pool.get('ir.model') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() ir_model_field = cls.__table__() ir_model = Model.__table__() @@ -280,7 +280,7 @@ class ModelField(ModelSQL, ModelView): ir_model_field.module.as_('module'), ir_model_field.help.as_('help'), where=ir_model.model == model.__name__)) - model_fields = dict((f['name'], f) for f in cursor.dictfetchall()) + model_fields = {f['name']: f for f in cursor_dict(cursor)} for field_name, field in model._fields.iteritems(): if hasattr(field, 'model_name'): @@ -374,7 +374,7 @@ class ModelField(ModelSQL, ModelView): else: model_ids.add(rec['model']) model_ids = list(model_ids) - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() model = Model.__table__() cursor.execute(*model.select(model.id, model.model, where=model.id.in_(model_ids))) @@ -449,11 +449,10 @@ class ModelAccess(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor super(ModelAccess, cls).__register__(module_name) - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 2.6 (model, group) no more unique table.drop_constraint('model_group_uniq') @@ -488,7 +487,7 @@ class ModelAccess(ModelSQL, ModelView): pool = Pool() Model = pool.get('ir.model') UserGroup = pool.get('res.user-res.group') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() user = Transaction().user model_access = cls.__table__() ir_model = Model.__table__() @@ -628,11 +627,10 @@ class ModelFieldAccess(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor super(ModelFieldAccess, cls).__register__(module_name) - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 2.6 (field, group) no more unique table.drop_constraint('field_group_uniq') @@ -669,7 +667,6 @@ class ModelFieldAccess(ModelSQL, ModelView): Model = pool.get('ir.model') ModelField = pool.get('ir.model.field') UserGroup = pool.get('res.user-res.group') - cursor = Transaction().cursor user = Transaction().user field_access = cls.__table__() ir_model = Model.__table__() @@ -687,6 +684,7 @@ class ModelFieldAccess(ModelSQL, ModelView): default = {} accesses = dict((m, default) for m in models) + cursor = Transaction().connection.cursor() cursor.execute(*field_access.join(model_field, condition=field_access.field == model_field.id ).join(ir_model, @@ -856,12 +854,12 @@ class ModelData(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() model_data = cls.__table__() super(ModelData, cls).__register__(module_name) - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 2.6: remove inherit if table.column_exist('inherit'): diff --git a/trytond/ir/module.py b/trytond/ir/module.py index d2312f24ab..f1ac15826d 100644 --- a/trytond/ir/module.py +++ b/trytond/ir/module.py @@ -97,12 +97,11 @@ class Module(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor # Migration from 3.6: remove double module old_table = 'ir_module_module' - if TableHandler.table_exist(cursor, old_table): - TableHandler.table_rename(cursor, old_table, cls._table) + if TableHandler.table_exist(old_table): + TableHandler.table_rename(old_table, cls._table) super(Module, cls).__register__(module_name) @@ -241,7 +240,7 @@ class Module(ModelSQL, ModelView): Dependency = pool.get('ir.module.dependency') module_table = Module.__table__() dep_table = Dependency.__table__() - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() for module in modules: cursor.execute(*dep_table.join(module_table, condition=(dep_table.module == module_table.id) @@ -344,12 +343,11 @@ class ModuleDependency(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor # Migration from 3.6: remove double module old_table = 'ir_module_module_dependency' - if TableHandler.table_exist(cursor, old_table): - TableHandler.table_rename(cursor, old_table, cls._table) + if TableHandler.table_exist(old_table): + TableHandler.table_rename(old_table, cls._table) super(ModuleDependency, cls).__register__(module_name) @@ -385,22 +383,22 @@ class ModuleConfigWizardItem(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() pool = Pool() ModelData = pool.get('ir.model.data') model_data = ModelData.__table__() # Migration from 3.6: remove double module old_table = 'ir_module_module_config_wizard_item' - if TableHandler.table_exist(cursor, old_table): - TableHandler.table_rename(cursor, old_table, cls._table) + if TableHandler.table_exist(old_table): + TableHandler.table_rename(old_table, cls._table) cursor.execute(*model_data.update( columns=[model_data.model], values=[cls.__name__], where=(model_data.model == 'ir.module.module.config_wizard.item'))) - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migrate from 2.2 remove name table.drop_column('name') @@ -533,8 +531,8 @@ class ModuleInstallUpgrade(Wizard): @classmethod def check_access(cls): - # Use new cursor to prevent lock when installing modules - with Transaction().new_cursor(): + # Use new transaction to prevent lock when installing modules + with Transaction().new_transaction(): super(ModuleInstallUpgrade, cls).check_access() @staticmethod @@ -559,7 +557,7 @@ class ModuleInstallUpgrade(Wizard): pool = Pool() Module = pool.get('ir.module') Lang = pool.get('ir.lang') - with Transaction().new_cursor() as transaction: + with Transaction().new_transaction(): modules = Module.search([ ('state', 'in', ['to upgrade', 'to remove', 'to install']), ]) @@ -568,7 +566,6 @@ class ModuleInstallUpgrade(Wizard): ('translatable', '=', True), ]) lang = [x.code for x in langs] - transaction.cursor.commit() if update: pool.init(update=update, lang=lang) return 'done' diff --git a/trytond/ir/property.py b/trytond/ir/property.py index 9764b81e7a..f4d0221cbb 100644 --- a/trytond/ir/property.py +++ b/trytond/ir/property.py @@ -34,7 +34,7 @@ class Property(ModelSQL, ModelView): models = cls._models_get_cache.get(None) if models: return models - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() model = Model.__table__() cursor.execute(*model.select(model.model, model.name, order_by=model.name.asc)) diff --git a/trytond/ir/rule.py b/trytond/ir/rule.py index f3dc849071..e78e14ab57 100644 --- a/trytond/ir/rule.py +++ b/trytond/ir/rule.py @@ -113,7 +113,7 @@ class Rule(ModelSQL, ModelView): def __register__(cls, module_name): TableHandler = backend.get('TableHandler') super(Rule, cls).__register__(module_name) - table = TableHandler(Transaction().cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 2.6: replace field, operator and operand by domain table.not_null_action('field', action='remove') @@ -181,7 +181,7 @@ class Rule(ModelSQL, ModelView): RuleGroup_Group = pool.get('ir.rule.group-res.group') User_Group = pool.get('res.user-res.group') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() rule_table = cls.__table__() rule_group = RuleGroup.__table__() rule_group_user = RuleGroup_User.__table__() diff --git a/trytond/ir/sequence.py b/trytond/ir/sequence.py index f687760e18..d551880318 100644 --- a/trytond/ir/sequence.py +++ b/trytond/ir/sequence.py @@ -103,8 +103,7 @@ class Sequence(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 2.0 rename number_next into number_next_internal table.column_rename('number_next', 'number_next_internal') @@ -117,7 +116,7 @@ class Sequence(ModelSQL, ModelView): for sequence in sequences: if sequence.type != 'incremental': continue - if not TableHandler.sequence_exist(cursor, + if not TableHandler.sequence_exist( sequence._sql_sequence_name): sequence.create_sql_sequence(sequence.number_next_internal) @@ -158,7 +157,7 @@ class Sequence(ModelSQL, ModelView): return Transaction().context.get('code') def get_number_next(self, name): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() sql_name = self._sql_sequence_name if sql_sequence and not self._strict: cursor.execute('SELECT ' @@ -258,7 +257,7 @@ class Sequence(ModelSQL, ModelView): def create_sql_sequence(self, number_next=None): 'Create the SQL sequence' - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() param = Flavor.get().param if self.type != 'incremental': return @@ -271,9 +270,9 @@ class Sequence(ModelSQL, ModelView): def update_sql_sequence(self, number_next=None): 'Update the SQL sequence' TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() param = Flavor.get().param - exist = TableHandler.sequence_exist(cursor, self._sql_sequence_name) + exist = TableHandler.sequence_exist(self._sql_sequence_name) if self.type != 'incremental': if exist: self.delete_sql_sequence() @@ -289,7 +288,7 @@ class Sequence(ModelSQL, ModelView): def delete_sql_sequence(self): 'Delete the SQL sequence' - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() if self.type != 'incremental': return cursor.execute('DROP SEQUENCE "%s"' @@ -319,7 +318,7 @@ class Sequence(ModelSQL, ModelView): def _get_sequence(cls, sequence): if sequence.type == 'incremental': if sql_sequence and not cls._strict: - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() cursor.execute('SELECT nextval(\'"%s"\')' % sequence._sql_sequence_name) number_next, = cursor.fetchone() @@ -382,5 +381,6 @@ class SequenceStrict(Sequence): @classmethod def get_id(cls, clause): - Transaction().cursor.lock(cls._table) + transaction = Transaction() + transaction.database.lock(transaction.connection, cls._table) return super(SequenceStrict, cls).get_id(clause) diff --git a/trytond/ir/session.py b/trytond/ir/session.py index 99e8e8a11d..c1c8c789dd 100644 --- a/trytond/ir/session.py +++ b/trytond/ir/session.py @@ -10,7 +10,6 @@ import datetime from trytond.model import ModelSQL, fields from trytond.config import config from .. import backend -from ..transaction import Transaction __all__ = [ 'Session', 'SessionWizard', @@ -34,7 +33,7 @@ class Session(ModelSQL): TableHandler = backend.get('TableHandler') super(Session, cls).__register__(module_name) - table = TableHandler(Transaction().cursor, cls, module_name) + table = TableHandler(cls, module_name) table.index_action('create_uid', 'add') @staticmethod diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index 119a80bf50..3ad4e6af1c 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -19,7 +19,7 @@ from sql.aggregate import Max from ..model import ModelView, ModelSQL, fields, Unique from ..wizard import Wizard, StateView, StateTransition, StateAction, \ Button -from ..tools import file_open, reduce_ids, grouped_slice +from ..tools import file_open, reduce_ids, grouped_slice, cursor_dict from .. import backend from ..pyson import PYSONEncoder, Eval from ..transaction import Transaction @@ -93,9 +93,10 @@ class Translation(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor + transaction = Transaction() + cursor = transaction.connection.cursor() ir_translation = cls.__table__() - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 1.8: new field src_md5 src_md5_exist = table.column_exist('src_md5') if not src_md5_exist: @@ -111,7 +112,7 @@ class Translation(ModelSQL, ModelView): # Migration from 1.8: fill new field src_md5 if not src_md5_exist: offset = 0 - limit = cursor.IN_MAX + limit = transaction.database.IN_MAX translations = True while translations: translations = cls.search([], offset=offset, limit=limit) @@ -121,7 +122,7 @@ class Translation(ModelSQL, ModelView): cls.write([translation], { 'src_md5': src_md5, }) - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) table.not_null_action('src_md5', action='add') # Migration from 2.2 and 2.8 @@ -129,12 +130,12 @@ class Translation(ModelSQL, ModelView): [-1], where=(ir_translation.res_id == Null) | (ir_translation.res_id == 0))) - table = TableHandler(Transaction().cursor, cls, module_name) + table = TableHandler(cls, module_name) table.index_action(['lang', 'type', 'name'], 'add') @classmethod def register_model(cls, model, module_name): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() ir_translation = cls.__table__() if not model.__doc__: @@ -173,7 +174,7 @@ class Translation(ModelSQL, ModelView): @classmethod def register_fields(cls, model, module_name): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() ir_translation = cls.__table__() # Prefetch field translations @@ -189,7 +190,7 @@ class Translation(ModelSQL, ModelView): & ir_translation.type.in_( ('field', 'help', 'selection')) & ir_translation.name.in_(names)))) - for trans in cursor.dictfetchall(): + for trans in cursor_dict(cursor): if trans['type'] == 'field': trans_fields[trans['name']] = trans elif trans['type'] == 'help': @@ -260,7 +261,7 @@ class Translation(ModelSQL, ModelView): @classmethod def register_error_messages(cls, model, module_name): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() ir_translation = cls.__table__() cursor.execute(*ir_translation.select( @@ -268,7 +269,7 @@ class Translation(ModelSQL, ModelView): where=((ir_translation.lang == 'en_US') & (ir_translation.type == 'error') & (ir_translation.name == model.__name__)))) - trans_error = dict((t['src'], t) for t in cursor.dictfetchall()) + trans_error = {t['src']: t for t in cursor_dict(cursor)} errors = model._get_error_messages() for error in set(errors): @@ -285,7 +286,7 @@ class Translation(ModelSQL, ModelView): @classmethod def register_wizard(cls, wizard, module_name): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() ir_translation = cls.__table__() # Prefetch button translations @@ -294,7 +295,7 @@ class Translation(ModelSQL, ModelView): where=((ir_translation.lang == 'en_US') & (ir_translation.type == 'wizard_button') & (ir_translation.name.like(wizard.__name__ + ',%'))))) - trans_buttons = dict((t['name'], t) for t in cursor.dictfetchall()) + trans_buttons = {t['name']: t for t in cursor_dict(cursor)} def update_insert_button(state_name, button): trans_name = '%s,%s,%s' % ( @@ -430,12 +431,13 @@ class Translation(ModelSQL, ModelView): else: to_fetch = ids if to_fetch: - cursor = Transaction().cursor + transaction = Transaction() + cursor = transaction.connection.cursor() table = cls.__table__() fuzzy_sql = table.fuzzy == False if Transaction().context.get('fuzzy_translation', False): fuzzy_sql = None - in_max = cursor.IN_MAX // 7 + in_max = transaction.database.IN_MAX // 7 for sub_to_fetch in grouped_slice(to_fetch, in_max): red_sql = reduce_ids(table.res_id, sub_to_fetch) where = And(((table.lang == lang), @@ -469,8 +471,8 @@ class Translation(ModelSQL, ModelView): ModelFields = pool.get('ir.model.field') Model = pool.get('ir.model') Config = pool.get('ir.configuration') - cursor = Transaction().cursor - in_max = cursor.IN_MAX + transaction = Transaction() + in_max = transaction.database.IN_MAX if len(ids) > in_max: for i in range(0, len(ids), in_max): @@ -611,7 +613,7 @@ class Translation(ModelSQL, ModelView): if trans != -1: return trans - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() table = cls.__table__() where = ((table.lang == lang) & (table.type == ttype) @@ -640,9 +642,10 @@ class Translation(ModelSQL, ModelView): ''' res = {} clause = [] - cursor = Transaction().cursor + transaction = Transaction() + cursor = transaction.connection.cursor() table = cls.__table__() - if len(args) > cursor.IN_MAX: + if len(args) > transaction.database.IN_MAX: for sub_args in grouped_slice(args): res.update(cls.get_sources(list(sub_args))) return res @@ -670,7 +673,7 @@ class Translation(ModelSQL, ModelView): where &= table.src == source clause.append(where) if clause: - in_max = cursor.IN_MAX // 7 + in_max = transaction.database.IN_MAX // 7 for sub_clause in grouped_slice(clause, in_max): cursor.execute(*table.select( table.lang, table.type, table.name, table.src, @@ -696,7 +699,7 @@ class Translation(ModelSQL, ModelView): ModelView._fields_view_get_cache.clear() vlist = [x.copy() for x in vlist] - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() table = cls.__table__() for vals in vlist: if not vals.get('module'): @@ -1017,7 +1020,7 @@ class TranslationSet(Wizard): if not reports: return - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() translation = Translation.__table__() for report in reports: cursor.execute(*translation.select( @@ -1026,9 +1029,7 @@ class TranslationSet(Wizard): & (translation.type == 'odt') & (translation.name == report.report_name) & (translation.module == report.module or ''))) - trans_reports = {} - for trans in cursor.dictfetchall(): - trans_reports[trans['src']] = trans + trans_reports = {t['src']: t for t in cursor_dict(cursor)} strings = [] @@ -1119,7 +1120,7 @@ class TranslationSet(Wizard): if not views: return - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() translation = Translation.__table__() for view in views: cursor.execute(*translation.select( @@ -1128,9 +1129,7 @@ class TranslationSet(Wizard): & (translation.type == 'view') & (translation.name == view.model) & (translation.module == view.module))) - trans_views = {} - for trans in cursor.dictfetchall(): - trans_views[trans['src']] = trans + trans_views = {t['src']: t for t in cursor_dict(cursor)} xml = (view.arch or '').strip() if not xml: @@ -1465,7 +1464,7 @@ class TranslationUpdate(Wizard): def do_update(self, action): pool = Pool() Translation = pool.get('ir.translation') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() translation = Translation.__table__() lang = self.start.language.code columns = [translation.name.as_('name'), @@ -1478,7 +1477,7 @@ class TranslationUpdate(Wizard): where=(translation.lang == lang) & translation.type.in_(self._source_types)))) to_create = [] - for row in cursor.dictfetchall(): + for row in cursor_dict(cursor): to_create.append({ 'name': row['name'], 'res_id': row['res_id'], @@ -1499,7 +1498,7 @@ class TranslationUpdate(Wizard): where=(translation.lang == lang) & translation.type.in_(self._ressource_types)))) to_create = [] - for row in cursor.dictfetchall(): + for row in cursor_dict(cursor): to_create.append({ 'name': row['name'], 'res_id': row['res_id'], @@ -1518,7 +1517,7 @@ class TranslationUpdate(Wizard): - translation.select(*columns, where=(translation.lang == lang) & translation.type.in_(self._updatable_types)))) - for row in cursor.dictfetchall(): + for row in cursor_dict(cursor): cursor.execute(*translation.update( [translation.fuzzy, translation.src], [True, row['src']], @@ -1541,7 +1540,7 @@ class TranslationUpdate(Wizard): & (translation.value != Null), group_by=translation.src)) - for row in cursor.dictfetchall(): + for row in cursor_dict(cursor): cursor.execute(*translation.update( [translation.fuzzy, translation.value], [True, row['value']], diff --git a/trytond/ir/trigger.py b/trytond/ir/trigger.py index 18b8e8e69e..29b468339b 100644 --- a/trytond/ir/trigger.py +++ b/trytond/ir/trigger.py @@ -74,8 +74,8 @@ class Trigger(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor - table = TableHandler(cursor, cls, module_name) + cursor = Transaction().connection.cursor() + table = TableHandler(cls, module_name) sql_table = cls.__table__() super(Trigger, cls).__register__(module_name) @@ -187,7 +187,7 @@ class Trigger(ModelSQL, ModelView): TriggerLog = pool.get('ir.trigger.log') Model = pool.get(trigger.model.model) ActionModel = pool.get(trigger.action_model.model) - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() trigger_log = TriggerLog.__table__() ids = map(int, records) @@ -218,7 +218,7 @@ class Trigger(ModelSQL, ModelView): red_sql = reduce_ids(trigger_log.record_id, sub_ids) cursor.execute(*trigger_log.select( trigger_log.record_id, Max(trigger_log.create_date), - where=red_sql & (trigger_log.trigger == trigger.id), + where=(red_sql & (trigger_log.trigger == trigger.id)), group_by=trigger_log.record_id)) delay = dict(cursor.fetchall()) for record_id in sub_ids: @@ -236,8 +236,8 @@ class Trigger(ModelSQL, ModelView): microseconds = int(timepart_full[1]) else: microseconds = 0 - delay[record_id] = datetime.datetime(year, month, day, - hours, minutes, seconds, microseconds) + delay[record_id] = datetime.datetime(year, month, + day, hours, minutes, seconds, microseconds) if (datetime.datetime.now() - delay[record_id] >= trigger.minimum_time_delay): new_ids.append(record_id) @@ -307,5 +307,5 @@ class TriggerLog(ModelSQL): TableHandler = backend.get('TableHandler') super(TriggerLog, cls).__register__(module_name) - table = TableHandler(Transaction().cursor, cls, module_name) + table = TableHandler(cls, module_name) table.index_action(['trigger', 'record_id'], 'add') diff --git a/trytond/ir/ui/menu.py b/trytond/ir/ui/menu.py index e7ee292958..00ac991f35 100644 --- a/trytond/ir/ui/menu.py +++ b/trytond/ir/ui/menu.py @@ -225,9 +225,9 @@ class UIMenu(ModelSQL, ModelView): pool = Pool() ActionKeyword = pool.get('ir.action.keyword') action_keywords = [] - cursor = Transaction().cursor - for i in range(0, len(menus), cursor.IN_MAX): - sub_menus = menus[i:i + cursor.IN_MAX] + transaction = Transaction() + for i in range(0, len(menus), transaction.database.IN_MAX): + sub_menus = menus[i:i + transaction.database.IN_MAX] action_keywords += ActionKeyword.search([ ('keyword', '=', 'tree_open'), ('model', 'in', [str(menu) for menu in sub_menus]), diff --git a/trytond/ir/ui/view.py b/trytond/ir/ui/view.py index 0542260e2d..8ca77aa396 100644 --- a/trytond/ir/ui/view.py +++ b/trytond/ir/ui/view.py @@ -85,8 +85,7 @@ class View(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 2.4 arch moved into data if table.column_exist('arch'): @@ -95,7 +94,7 @@ class View(ModelSQL, ModelView): super(View, cls).__register__(module_name) # New instance to refresh definition - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 1.0 arch no more required table.not_null_action('arch', action='remove') @@ -336,16 +335,14 @@ class ViewTreeState(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 2.8: table name changed - table.table_rename(cursor, 'ir_ui_view_tree_expanded_state', - cls._table) + table.table_rename('ir_ui_view_tree_expanded_state', cls._table) super(ViewTreeState, cls).__register__(module_name) - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) table.index_action(['model', 'domain', 'user', 'child_name'], 'add') @staticmethod diff --git a/trytond/model/fields/many2one.py b/trytond/model/fields/many2one.py index 7ed55598dd..f5bfaedac7 100644 --- a/trytond/model/fields/many2one.py +++ b/trytond/model/fields/many2one.py @@ -97,7 +97,7 @@ class Many2One(Field): return SQLType('INTEGER', 'INTEGER') def convert_domain_mptt(self, domain, tables): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() table, _ = tables[None] name, operator, ids = domain red_sql = reduce_ids(table.id, ids) diff --git a/trytond/model/fields/property.py b/trytond/model/fields/property.py index 4cac9078b8..e04da695d3 100644 --- a/trytond/model/fields/property.py +++ b/trytond/model/fields/property.py @@ -72,7 +72,7 @@ class Property(Function): Property = pool.get('ir.property') IrModel = pool.get('ir.model') Field = pool.get('ir.model.field') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() name, operator, value = domain @@ -135,7 +135,7 @@ class Property(Function): return [('id', 'in', [x[0] for x in props])] else: - other_ids = [x[0] for x in cursor.fetchall()] + other_ids = [x[0] for x in fetchall] res_ids = Model.search(['OR', ('id', 'in', [x[0] for x in props]), diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index 284cd244eb..0a35fb0a0f 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -13,7 +13,7 @@ from sql.aggregate import Count, Max from trytond.model import ModelStorage, ModelView from trytond.model import fields from trytond import backend -from trytond.tools import reduce_ids, grouped_slice +from trytond.tools import reduce_ids, grouped_slice, cursor_dict from trytond.const import OPERATORS from trytond.transaction import Transaction from trytond.pool import Pool @@ -127,7 +127,7 @@ class ModelSQL(ModelStorage): @classmethod def __register__(cls, module_name): sql_table = cls.__table__() - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() TableHandler = backend.get('TableHandler') super(ModelSQL, cls).__register__(module_name) @@ -137,10 +137,9 @@ class ModelSQL(ModelStorage): pool = Pool() # create/update table in the database - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) if cls._history: - history_table = TableHandler( - cursor, cls, module_name, history=True) + history_table = TableHandler(cls, module_name, history=True) history_table.index_action('id', action='add') for field_name, field in cls._fields.iteritems(): @@ -241,9 +240,8 @@ class ModelSQL(ModelStorage): def _update_history_table(cls): TableHandler = backend.get('TableHandler') if cls._history: - table = TableHandler(Transaction().cursor, cls) - history_table = TableHandler(Transaction().cursor, cls, - history=True) + table = TableHandler(cls) + history_table = TableHandler(cls, history=True) for column_name in table._columns: string = '' if column_name in cls._fields: @@ -309,7 +307,7 @@ class ModelSQL(ModelStorage): pool = Pool() ModelAccess = pool.get('ir.model.access') User = pool.get('res.user') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() ModelAccess.check(cls.__name__, 'read') @@ -339,7 +337,7 @@ class ModelSQL(ModelStorage): @classmethod def __insert_history(cls, ids, deleted=False): transaction = Transaction() - cursor = transaction.cursor + cursor = transaction.connection.cursor() if not cls._history: return user = transaction.user @@ -366,7 +364,7 @@ class ModelSQL(ModelStorage): cursor.execute(*history.insert(hcolumns, table.select(*columns, where=where))) else: - if cursor.has_multirow_insert(): + if transaction.database.has_multirow_insert(): cursor.execute(*history.insert(hcolumns, [[id_, CurrentTimestamp(), user] for id_ in sub_ids])) @@ -380,7 +378,7 @@ class ModelSQL(ModelStorage): if not cls._history: return transaction = Transaction() - cursor = transaction.cursor + cursor = transaction.connection.cursor() table = cls.__table__() history = cls.__table_history__() columns = [] @@ -449,7 +447,7 @@ class ModelSQL(ModelStorage): @classmethod def __check_timestamp(cls, ids): transaction = Transaction() - cursor = transaction.cursor + cursor = transaction.connection.cursor() table = cls.__table__() if not transaction.timestamp: return @@ -476,7 +474,7 @@ class ModelSQL(ModelStorage): def create(cls, vlist): DatabaseIntegrityError = backend.get('DatabaseIntegrityError') transaction = Transaction() - cursor = transaction.cursor + cursor = transaction.connection.cursor() pool = Pool() Translation = pool.get('ir.translation') Rule = pool.get('ir.rule') @@ -521,12 +519,13 @@ class ModelSQL(ModelStorage): insert_values.append(field.sql_format(value)) try: - if cursor.has_returning(): + if transaction.database.has_returning(): cursor.execute(*table.insert(insert_columns, [insert_values], [table.id])) id_new, = cursor.fetchone() else: - id_new = cursor.nextid(cls._table) + id_new = transaction.database.nextid( + transaction.connection, cls._table) if id_new: insert_columns.append(table.id) insert_values.append(id_new) @@ -535,10 +534,10 @@ class ModelSQL(ModelStorage): else: cursor.execute(*table.insert(insert_columns, [insert_values])) - id_new = cursor.lastid() + id_new = transaction.database.lastid(cursor) new_ids.append(id_new) except DatabaseIntegrityError, exception: - with Transaction().new_cursor(), \ + with Transaction().new_transaction(), \ Transaction().set_context(_check_access=False): cls.__raise_integrity_error(exception, values) raise @@ -608,7 +607,8 @@ class ModelSQL(ModelStorage): mode='read'): fields_names.append(field_name) super(ModelSQL, cls).read(ids, fields_names=fields_names) - cursor = Transaction().cursor + transaction = Transaction() + cursor = Transaction().connection.cursor() if not ids: return [] @@ -634,12 +634,12 @@ class ModelSQL(ModelStorage): table = cls.__table__() table_query = cls.table_query() - in_max = cursor.IN_MAX + in_max = transaction.database.IN_MAX history_order = None history_clause = None history_limit = None if (cls._history - and Transaction().context.get('_datetime') + and transaction.context.get('_datetime') and not table_query): in_max = 1 table = cls.__table_history__() @@ -678,8 +678,8 @@ class ModelSQL(ModelStorage): where &= dom_exp cursor.execute(*from_.select(*columns, where=where, order_by=history_order, limit=history_limit)) - dictfetchall = cursor.dictfetchall() - if not len(dictfetchall) == len({}.fromkeys(sub_ids)): + fetchall = list(cursor_dict(cursor)) + if not len(fetchall) == len({}.fromkeys(sub_ids)): if domain: where = red_sql if history_clause: @@ -693,7 +693,7 @@ class ModelSQL(ModelStorage): if rowcount == len({}.fromkeys(sub_ids)): cls.raise_user_error('access_error', cls.__name__) cls.raise_user_error('read_error', cls.__name__) - result.extend(dictfetchall) + result.extend(fetchall) else: result = [{'id': x} for x in ids] @@ -835,7 +835,7 @@ class ModelSQL(ModelStorage): def write(cls, records, values, *args): DatabaseIntegrityError = backend.get('DatabaseIntegrityError') transaction = Transaction() - cursor = transaction.cursor + cursor = transaction.connection.cursor() pool = Pool() Translation = pool.get('ir.translation') Config = pool.get('ir.configuration') @@ -912,8 +912,8 @@ class ModelSQL(ModelStorage): cursor.execute(*table.update(columns, update_values, where=red_sql)) except DatabaseIntegrityError, exception: - with Transaction().new_cursor(), \ - Transaction().set_context(_check_access=False): + with Transaction().new_transaction() as transaction, \ + transaction.set_context(_check_access=False): cls.__raise_integrity_error(exception, values, values.keys()) raise @@ -945,7 +945,7 @@ class ModelSQL(ModelStorage): def delete(cls, records): DatabaseIntegrityError = backend.get('DatabaseIntegrityError') transaction = Transaction() - cursor = transaction.cursor + cursor = transaction.connection.cursor() pool = Pool() Translation = pool.get('ir.translation') Rule = pool.get('ir.rule') @@ -1070,7 +1070,7 @@ class ModelSQL(ModelStorage): try: cursor.execute(*table.delete(where=red_sql)) except DatabaseIntegrityError, exception: - with Transaction().new_cursor(): + with Transaction().new_transaction(): cls.__raise_integrity_error(exception, {}) raise @@ -1086,7 +1086,7 @@ class ModelSQL(ModelStorage): pool = Pool() Rule = pool.get('ir.rule') transaction = Transaction() - cursor = transaction.cursor + cursor = transaction.connection.cursor() # Get domain clauses tables, expression = cls.search_domain(domain) @@ -1146,8 +1146,8 @@ class ModelSQL(ModelStorage): return select cursor.execute(*select) - rows = cursor.dictfetchmany(cursor.IN_MAX) - cache = cursor.get_cache() + rows = list(cursor_dict(cursor, transaction.database.IN_MAX)) + cache = transaction.get_cache() if cls.__name__ not in cache: cache[cls.__name__] = LRUDict(cache_size()) delete_records = transaction.delete_records.setdefault(cls.__name__, @@ -1195,7 +1195,7 @@ class ModelSQL(ModelStorage): # Can not cache the history value if we are not sure to have fetch all # the rows for each records if (not (cls._history and transaction.context.get('_datetime')) - or len(rows) < cursor.IN_MAX): + or len(rows) < transaction.database.IN_MAX): rows = list(filter_history(rows)) keys = None for data in islice(rows, 0, cache.size_limit): @@ -1215,9 +1215,9 @@ class ModelSQL(ModelStorage): del data[k] cache[cls.__name__].setdefault(data['id'], {}).update(data) - if len(rows) >= cursor.IN_MAX: + if len(rows) >= transaction.database.IN_MAX: if (cls._history - and Transaction().context.get('_datetime') + and transaction.context.get('_datetime') and not query): columns = columns[:3] else: @@ -1225,7 +1225,7 @@ class ModelSQL(ModelStorage): cursor.execute(*table.select(*columns, where=expression, order_by=order_by, limit=limit, offset=offset)) - rows = filter_history(cursor.dictfetchall()) + rows = filter_history(list(cursor_dict(cursor))) return cls.browse([x['id'] for x in rows]) @@ -1278,7 +1278,7 @@ class ModelSQL(ModelStorage): @classmethod def _update_mptt(cls, field_names, list_ids, values=None): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() count = None for field_name, ids in zip(field_names, list_ids): field = cls._fields[field_name] @@ -1319,7 +1319,7 @@ class ModelSQL(ModelStorage): ''' Rebuild left, right value for the tree. ''' - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() table = cls.__table__() right = left + 1 @@ -1347,7 +1347,7 @@ class ModelSQL(ModelStorage): - the value (right - left - 1) / 2 will not give the number of children node ''' - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() table = cls.__table__() left = Column(table, left) right = Column(table, right) @@ -1401,8 +1401,9 @@ class ModelSQL(ModelStorage): @classmethod def validate(cls, records): super(ModelSQL, cls).validate(records) - cursor = Transaction().cursor - if cursor.has_constraint(): + transaction = Transaction() + cursor = transaction.connection.cursor() + if transaction.database.has_constraint(): return # Works only for a single transaction ids = map(int, records) @@ -1411,7 +1412,7 @@ class ModelSQL(ModelStorage): if isinstance(sql, Unique): columns = [Column(table, c.name) for c in sql.columns] columns.insert(0, table.id) - in_max = cursor.IN_MAX // (len(columns) + 1) + in_max = transaction.database.IN_MAX // (len(columns) + 1) for sub_ids in grouped_slice(ids, in_max): red_sql = reduce_ids(table.id, sub_ids) diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index e2873248f6..cfffe87bf7 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -166,8 +166,8 @@ class ModelStorage(Model): if local_cache: local_cache.clear() - # Clean cursor cache - for cache in Transaction().cursor.cache.itervalues(): + # Clean transaction cache + for cache in Transaction().cache.itervalues(): if cls.__name__ in cache: for record in all_records: if record.id in cache[cls.__name__]: @@ -220,8 +220,8 @@ class ModelStorage(Model): # Increase transaction counter Transaction().counter += 1 - # Clean cursor cache - for cache in Transaction().cursor.cache.values(): + # Clean transaction cache + for cache in Transaction().cache.values(): for cache in (cache, cache.get('_language_cache', {}).values()): if cls.__name__ in cache: for record in records: @@ -1155,7 +1155,7 @@ class ModelStorage(Model): def __init__(self, id=None, **kwargs): _ids = kwargs.pop('_ids', None) _local_cache = kwargs.pop('_local_cache', None) - self._cursor = Transaction().cursor + self._transaction = Transaction() self._user = Transaction().user self._context = Transaction().context if id is not None: @@ -1166,7 +1166,7 @@ class ModelStorage(Model): else: self._ids = [id] - self._cursor_cache = self._cursor.get_cache() + self._transaction_cache = self._transaction.get_cache() if _local_cache is not None: self._local_cache = _local_cache @@ -1178,7 +1178,7 @@ class ModelStorage(Model): @property def _cache(self): - cache = self._cursor_cache + cache = self._transaction_cache if self.__name__ not in cache: cache[self.__name__] = LRUDict(cache_size()) return cache[self.__name__] @@ -1275,7 +1275,8 @@ class ModelStorage(Model): index = self._ids.index(self.id) ids = chain(islice(self._ids, index, None), islice(self._ids, 0, max(index - 1, 0))) - ids = islice(unique(ifilter(filter_, ids)), self._cursor.IN_MAX) + ids = islice(unique(ifilter(filter_, ids)), + self._transaction.database.IN_MAX) def instantiate(field, value, data): if field._type in ('many2one', 'one2one', 'reference'): @@ -1320,14 +1321,14 @@ class ModelStorage(Model): model2ids = {} model2cache = {} # Read the data - with Transaction().set_cursor(self._cursor), \ + with Transaction().set_current_transaction(self._transaction), \ Transaction().set_user(self._user), \ Transaction().set_context(self._context): if self.id in self._cache and name in self._cache[self.id]: # Use values from cache ids = islice(chain(islice(self._ids, index, None), islice(self._ids, 0, max(index - 1, 0))), - self._cursor.IN_MAX) + self._transaction.database.IN_MAX) ffields = {name: ffields[name]} read_data = [{'id': i, name: self._cache[i][name]} for i in ids @@ -1424,11 +1425,11 @@ class ModelStorage(Model): save_values = {} to_create = [] to_write = [] - cursor = records[0]._cursor + transaction = records[0]._transaction user = records[0]._user context = records[0]._context for record in records: - assert cursor == record._cursor + assert transaction == record._transaction assert user == record._user assert context == record._context save_values[record] = record._save_values @@ -1440,7 +1441,7 @@ class ModelStorage(Model): to_write.append(record) transaction = Transaction() try: - with transaction.set_cursor(cursor), \ + with transaction.set_current_transaction(transaction), \ transaction.set_user(user), \ transaction.set_context(context): if to_create: diff --git a/trytond/modules/__init__.py b/trytond/modules/__init__.py index c8a43af86b..4fd2ad4ccb 100644 --- a/trytond/modules/__init__.py +++ b/trytond/modules/__init__.py @@ -205,92 +205,92 @@ def load_module_graph(graph, pool, update=None, lang=None): update = [] modules_todo = [] models_to_update_history = set() - cursor = Transaction().cursor - modules = [x.name for x in graph] - cursor.execute(*ir_module.select(ir_module.name, ir_module.state, - where=ir_module.name.in_(modules))) - module2state = dict(cursor.fetchall()) + with Transaction().connection.cursor() as cursor: + modules = [x.name for x in graph] + cursor.execute(*ir_module.select(ir_module.name, ir_module.state, + where=ir_module.name.in_(modules))) + module2state = dict(cursor.fetchall()) - for package in graph: - module = package.name - if module not in MODULES: - continue - logger.info(module) - classes = pool.setup(module) - package_state = module2state.get(module, 'uninstalled') - if (is_module_to_install(module, update) - or package_state in ('to install', 'to upgrade')): - if package_state not in ('to install', 'to upgrade'): - if package_state == 'installed': - package_state = 'to upgrade' - elif package_state != 'to remove': - package_state = 'to install' - for child in package.childs: - module2state[child.name] = package_state - for type in classes.keys(): - for cls in classes[type]: - logger.info('%s:register %s', module, cls.__name__) - cls.__register__(module) - for model in classes['model']: - if hasattr(model, '_history'): - models_to_update_history.add(model.__name__) - - # Instanciate a new parser for the package: - tryton_parser = convert.TrytondXmlHandler(pool=pool, module=module, - module_state=package_state) - - for filename in package.info.get('xml', []): - filename = filename.replace('/', os.sep) - logger.info('%s:loading %s', module, filename) - # Feed the parser with xml content: - with tools.file_open(OPJ(module, filename), 'rb') as fp: - tryton_parser.parse_xmlstream(fp) - - modules_todo.append((module, list(tryton_parser.to_delete))) - - localedir = '%s/%s' % (package.info['directory'], 'locale') - for filename in itertools.chain( - iglob('%s/*.po' % localedir), - iglob('%s/override/*.po' % localedir)): - filename = filename.replace('/', os.sep) - lang2 = os.path.splitext(os.path.basename(filename))[0] - if lang2 not in lang: - continue - logger.info('%s:loading %s', module, - filename[len(package.info['directory']) + 1:]) - Translation = pool.get('ir.translation') - Translation.translation_import(lang2, module, filename) - - if package_state == 'to remove': + for package in graph: + module = package.name + if module not in MODULES: continue - cursor.execute(*ir_module.select(ir_module.id, - where=(ir_module.name == package.name))) - try: - module_id, = cursor.fetchone() - cursor.execute(*ir_module.update([ir_module.state], - ['installed'], where=(ir_module.id == module_id))) - except TypeError: - cursor.execute(*ir_module.insert( - [ir_module.create_uid, ir_module.create_date, - ir_module.name, ir_module.state], - [[0, CurrentTimestamp(), package.name, 'installed']])) - module2state[package.name] = 'installed' + logger.info(module) + classes = pool.setup(module) + package_state = module2state.get(module, 'uninstalled') + if (is_module_to_install(module, update) + or package_state in ('to install', 'to upgrade')): + if package_state not in ('to install', 'to upgrade'): + if package_state == 'installed': + package_state = 'to upgrade' + elif package_state != 'to remove': + package_state = 'to install' + for child in package.childs: + module2state[child.name] = package_state + for type in classes.keys(): + for cls in classes[type]: + logger.info('%s:register %s', module, cls.__name__) + cls.__register__(module) + for model in classes['model']: + if hasattr(model, '_history'): + models_to_update_history.add(model.__name__) - cursor.commit() + # Instanciate a new parser for the package: + tryton_parser = convert.TrytondXmlHandler(pool=pool, + module=module, module_state=package_state) - for model_name in models_to_update_history: - model = pool.get(model_name) - if model._history: - logger.info('history:update %s', model.__name__) - model._update_history_table() + for filename in package.info.get('xml', []): + filename = filename.replace('/', os.sep) + logger.info('%s:loading %s', module, filename) + # Feed the parser with xml content: + with tools.file_open(OPJ(module, filename), 'rb') as fp: + tryton_parser.parse_xmlstream(fp) - # Vacuum : - while modules_todo: - (module, to_delete) = modules_todo.pop() - convert.post_import(pool, module, to_delete) + modules_todo.append((module, list(tryton_parser.to_delete))) - cursor.commit() + localedir = '%s/%s' % (package.info['directory'], 'locale') + for filename in itertools.chain( + iglob('%s/*.po' % localedir), + iglob('%s/override/*.po' % localedir)): + filename = filename.replace('/', os.sep) + lang2 = os.path.splitext(os.path.basename(filename))[0] + if lang2 not in lang: + continue + logger.info('%s:loading %s', module, + filename[len(package.info['directory']) + 1:]) + Translation = pool.get('ir.translation') + Translation.translation_import(lang2, module, filename) + + if package_state == 'to remove': + continue + cursor.execute(*ir_module.select(ir_module.id, + where=(ir_module.name == package.name))) + try: + module_id, = cursor.fetchone() + cursor.execute(*ir_module.update([ir_module.state], + ['installed'], where=(ir_module.id == module_id))) + except TypeError: + cursor.execute(*ir_module.insert( + [ir_module.create_uid, ir_module.create_date, + ir_module.name, ir_module.state], + [[0, CurrentTimestamp(), package.name, + 'installed'], + ])) + module2state[package.name] = 'installed' + + Transaction().connection.commit() + + for model_name in models_to_update_history: + model = pool.get(model_name) + if model._history: + logger.info('history:update %s', model.__name__) + model._update_history_table() + + # Vacuum : + while modules_todo: + (module, to_delete) = modules_todo.pop() + convert.post_import(pool, module, to_delete) def get_module_list(): @@ -367,63 +367,58 @@ def load_modules(database_name, pool, update=None, lang=None): def _load_modules(): global res TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor - # Migration from 3.6: remove double module - old_table = 'ir_module_module' - new_table = 'ir_module' - if TableHandler.table_exist(cursor, old_table): - TableHandler.table_rename(cursor, old_table, new_table) - if update: - cursor.execute(*ir_module.select(ir_module.name, - where=ir_module.state.in_(('installed', 'to install', - 'to upgrade', 'to remove')))) - else: - cursor.execute(*ir_module.select(ir_module.name, - where=ir_module.state.in_(('installed', 'to upgrade', - 'to remove')))) - module_list = [name for (name,) in cursor.fetchall()] - if update: - module_list += update - graph = create_graph(module_list)[0] + with Transaction().connection.cursor() as cursor: + # Migration from 3.6: remove double module + old_table = 'ir_module_module' + new_table = 'ir_module' + if TableHandler.table_exist(old_table): + TableHandler.table_rename(old_table, new_table) + if update: + cursor.execute(*ir_module.select(ir_module.name, + where=ir_module.state.in_(('installed', 'to install', + 'to upgrade', 'to remove')))) + else: + cursor.execute(*ir_module.select(ir_module.name, + where=ir_module.state.in_(('installed', 'to upgrade', + 'to remove')))) + module_list = [name for (name,) in cursor.fetchall()] + if update: + module_list += update + graph = create_graph(module_list)[0] - try: load_module_graph(graph, pool, update, lang) - except Exception: - cursor.rollback() - raise - if update: - cursor.execute(*ir_module.select(ir_module.name, - where=(ir_module.state == 'to remove'))) - fetchall = cursor.fetchall() - if fetchall: - for (mod_name,) in fetchall: - # TODO check if ressource not updated by the user - cursor.execute(*ir_model_data.select(ir_model_data.model, - ir_model_data.db_id, - where=(ir_model_data.module == mod_name), - order_by=ir_model_data.id.desc)) - for rmod, rid in cursor.fetchall(): - Model = pool.get(rmod) - Model.delete([Model(rid)]) - cursor.commit() - cursor.execute(*ir_module.update([ir_module.state], - ['uninstalled'], + if update: + cursor.execute(*ir_module.select(ir_module.name, where=(ir_module.state == 'to remove'))) - cursor.commit() - res = False + fetchall = cursor.fetchall() + if fetchall: + for (mod_name,) in fetchall: + # TODO check if ressource not updated by the user + cursor.execute(*ir_model_data.select( + ir_model_data.model, ir_model_data.db_id, + where=(ir_model_data.module == mod_name), + order_by=ir_model_data.id.desc)) + for rmod, rid in cursor.fetchall(): + Model = pool.get(rmod) + Model.delete([Model(rid)]) + Transaction().connection.commit() + cursor.execute(*ir_module.update([ir_module.state], + ['uninstalled'], + where=(ir_module.state == 'to remove'))) + Transaction().connection.commit() + res = False - Module = pool.get('ir.module') - Module.update_list() - cursor.commit() + Module = pool.get('ir.module') + Module.update_list() Cache.resets(database_name) - if not Transaction().cursor: + if not Transaction().connection: with Transaction().start(database_name, 0): _load_modules() else: - with Transaction().new_cursor(), \ + with Transaction().new_transaction(), \ Transaction().set_user(0), \ Transaction().reset_context(): _load_modules() diff --git a/trytond/pool.py b/trytond/pool.py index 3ad6176db2..6101967eb6 100644 --- a/trytond/pool.py +++ b/trytond/pool.py @@ -55,7 +55,7 @@ class Pool(object): def __new__(cls, database_name=None): if database_name is None: - database_name = Transaction().cursor.database_name + database_name = Transaction().database.name result = cls._instances.get(database_name) if result: return result @@ -69,7 +69,7 @@ class Pool(object): def __init__(self, database_name=None): if database_name is None: - database_name = Transaction().cursor.database_name + database_name = Transaction().database.name self.database_name = database_name @staticmethod diff --git a/trytond/protocols/dispatcher.py b/trytond/protocols/dispatcher.py index 4061fbf26f..e69699a7ce 100644 --- a/trytond/protocols/dispatcher.py +++ b/trytond/protocols/dispatcher.py @@ -34,7 +34,7 @@ def dispatch(host, port, protocol, database_name, user, session, object_type, if method == 'login': try: database = Database(database_name).connect() - cursor = database.cursor() + cursor = database.get_connection().cursor() cursor.close() except Exception: return False @@ -87,7 +87,7 @@ def dispatch(host, port, protocol, database_name, user, session, object_type, if not config.getboolean('database', 'list'): raise Exception('AccessDenied') with Transaction().start(None, 0, close=True) as transaction: - return transaction.database.list(transaction.cursor) + return transaction.database.list() elif method == 'create': return create(*args, **kwargs) elif method == 'restore': @@ -168,33 +168,25 @@ def dispatch(host, port, protocol, database_name, user, session, object_type, else: result = [rpc.result(meth(i, *c_args, **c_kwargs)) for i in inst] - if not rpc.readonly: - transaction.cursor.commit() except DatabaseOperationalError: - transaction.cursor.rollback() if count and not rpc.readonly: + transaction.rollback() continue raise except (NotLogged, ConcurrencyException, UserError, UserWarning): logger.debug(log_message, *log_args, exc_info=True) - transaction.cursor.rollback() raise except Exception: logger.error(log_message, *log_args, exc_info=True) - transaction.cursor.rollback() raise Cache.resets(database_name) - with Transaction().start(database_name, 0) as transaction: - pool = Pool(database_name) - Session = pool.get('ir.session') - try: + try: + with Transaction().start(database_name, 0) as transaction: + pool = Pool(database_name) + Session = pool.get('ir.session') Session.reset(session) - except DatabaseOperationalError: - logger.debug('Reset session failed', exc_info=True) - # Silently fail when reseting session - transaction.cursor.rollback() - else: - transaction.cursor.commit() + except DatabaseOperationalError: + logger.debug('Reset session failed', exc_info=True) logger.debug('Result: %s', result) return result @@ -216,14 +208,13 @@ def create(database_name, password, lang, admin_password): try: with Transaction().start(None, 0, close=True, autocommit=True) \ as transaction: - transaction.database.create(transaction.cursor, database_name) - transaction.cursor.commit() + transaction.database.create(transaction.connection, database_name) - with Transaction().start(database_name, 0) as transaction: - Database.init(transaction.cursor) - transaction.cursor.execute(*ir_configuration.insert( + with Transaction().start(database_name, 0) as transaction,\ + transaction.connection.cursor() as cursor: + Database(database_name).init() + cursor.execute(*ir_configuration.insert( [ir_configuration.language], [[lang]])) - transaction.cursor.commit() pool = Pool(database_name) pool.init(update=['res', 'ir'], lang=[lang]) @@ -244,7 +235,6 @@ def create(database_name, password, lang, admin_password): Module = pool.get('ir.module') if Module: Module.update_list() - transaction.cursor.commit() res = True except Exception: logger.error('CREATE DB: %s failed', database_name, exc_info=True) @@ -257,16 +247,15 @@ def create(database_name, password, lang, admin_password): def drop(database_name, password): Database = backend.get('Database') security.check_super(password) - Database(database_name).close() + database = Database(database_name) + database.close() # Sleep to let connections close time.sleep(1) with Transaction().start(None, 0, close=True, autocommit=True) \ as transaction: - cursor = transaction.cursor try: - Database.drop(cursor, database_name) - cursor.commit() + database.drop(transaction.connection, database_name) except Exception: logger.error('DROP DB: %s failed', database_name, exc_info=True) raise @@ -296,9 +285,7 @@ def restore(database_name, password, data, update=False): Database = backend.get('Database') security.check_super(password) try: - database = Database(database_name).connect() - cursor = database.cursor() - cursor.close(close=True) + Database(database_name).connect() existing = True except Exception: existing = False @@ -307,8 +294,8 @@ def restore(database_name, password, data, update=False): Database.restore(database_name, data) logger.info('RESTORE DB: %s', database_name) if update: - with Transaction().start(database_name, 0) as transaction: - cursor = transaction.cursor + with Transaction().start(database_name, 0) as transaction,\ + transaction.connection.cursor() as cursor: cursor.execute(*ir_lang.select(ir_lang.code, where=ir_lang.translatable)) lang = [x[0] for x in cursor.fetchall()] diff --git a/trytond/res/ir.py b/trytond/res/ir.py index 5ac6782cfc..754a6226a6 100644 --- a/trytond/res/ir.py +++ b/trytond/res/ir.py @@ -2,7 +2,6 @@ # this repository contains the full copyright notices and license terms. from ..model import ModelSQL, fields from .. import backend -from ..transaction import Transaction from ..pool import Pool, PoolMeta __all__ = [ @@ -24,13 +23,12 @@ class UIMenuGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor # Migration from 1.0 table name change - TableHandler.table_rename(cursor, 'ir_ui_menu_group_rel', cls._table) - TableHandler.sequence_rename(cursor, 'ir_ui_menu_group_rel_id_seq', + TableHandler.table_rename('ir_ui_menu_group_rel', cls._table) + TableHandler.sequence_rename('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(cursor, cls, module_name) + table = TableHandler(cls, module_name) table.column_rename('menu_id', 'menu') table.column_rename('gid', 'group') super(UIMenuGroup, cls).__register__(module_name) @@ -66,13 +64,12 @@ class ActionGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor # Migration from 1.0 table name change - TableHandler.table_rename(cursor, 'ir_action_group_rel', cls._table) - TableHandler.sequence_rename(cursor, 'ir_action_group_rel_id_seq', + TableHandler.table_rename('ir_action_group_rel', cls._table) + TableHandler.sequence_rename('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(cursor, cls, module_name) + table = TableHandler(cls, module_name) table.column_rename('action_id', 'action') table.column_rename('gid', 'group') super(ActionGroup, cls).__register__(module_name) @@ -121,13 +118,11 @@ class ModelFieldGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor # Migration from 1.0 table name change - TableHandler.table_rename(cursor, 'ir_model_field_group_rel', - cls._table) - TableHandler.sequence_rename(cursor, 'ir_model_field_group_rel_id_seq', + TableHandler.table_rename('ir_model_field_group_rel', cls._table) + TableHandler.sequence_rename('ir_model_field_group_rel_id_seq', cls._table + '_id_seq') - table = TableHandler(cursor, cls, module_name) + 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') table.column_rename('group_id', 'group') @@ -181,14 +176,13 @@ class RuleGroupGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor # Migration from 1.0 table name change - TableHandler.table_rename(cursor, 'group_rule_group_rel', cls._table) - TableHandler.sequence_rename(cursor, 'group_rule_group_rel_id_seq', + TableHandler.table_rename('group_rule_group_rel', cls._table) + TableHandler.sequence_rename('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(cursor, cls, module_name) + table = TableHandler(cls, module_name) table.column_rename('rule_group_id', 'rule_group') table.column_rename('group_id', 'group') super(RuleGroupGroup, cls).__register__(module_name) @@ -205,14 +199,13 @@ class RuleGroupUser(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor # Migration from 1.0 table name change - TableHandler.table_rename(cursor, 'user_rule_group_rel', cls._table) - TableHandler.sequence_rename(cursor, 'user_rule_group_rel_id_seq', + TableHandler.table_rename('user_rule_group_rel', cls._table) + TableHandler.sequence_rename('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(cursor, cls, module_name) + table = TableHandler(cls, module_name) table.column_rename('rule_group_id', 'rule_group') table.column_rename('user_id', 'user') super(RuleGroupUser, cls).__register__(module_name) diff --git a/trytond/res/user.py b/trytond/res/user.py index e2ac62769d..0c2fd3327a 100644 --- a/trytond/res/user.py +++ b/trytond/res/user.py @@ -112,9 +112,9 @@ class User(ModelSQL, ModelView): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() super(User, cls).__register__(module_name) - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 1.6 @@ -243,7 +243,7 @@ class User(ModelSQL, ModelView): args.extend((users, cls._convert_vals(values))) super(User, cls).write(*args) # Clean cursor cache as it could be filled by domain_get - for cache in Transaction().cursor.cache.itervalues(): + for cache in Transaction().cache.itervalues(): if cls.__name__ in cache: for user in all_users: if user.id in cache[cls.__name__]: @@ -450,7 +450,7 @@ class User(ModelSQL, ModelView): result = cls._get_login_cache.get(login) if result: return result - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() table = cls.__table__() cursor.execute(*table.select(table.id, table.password_hash, where=(table.login == login) & (table.active == True))) @@ -543,7 +543,7 @@ class LoginAttempt(ModelSQL): def __register__(cls, module_name): TableHandler = backend.get('TableHandler') super(LoginAttempt, cls).__register__(module_name) - table = TableHandler(Transaction().cursor, cls, module_name) + table = TableHandler(cls, module_name) # Migration from 2.8: remove user table.drop_column('user') @@ -562,13 +562,13 @@ class LoginAttempt(ModelSQL): @classmethod def remove(cls, login): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() table = cls.__table__() cursor.execute(*table.delete(where=table.login == login)) @classmethod def count(cls, login): - cursor = Transaction().cursor + cursor = Transaction().connection.cursor() table = cls.__table__() cursor.execute(*table.select(Count(Literal(1)), where=(table.login == login) @@ -618,13 +618,12 @@ class UserGroup(ModelSQL): @classmethod def __register__(cls, module_name): TableHandler = backend.get('TableHandler') - cursor = Transaction().cursor # Migration from 1.0 table name change - TableHandler.table_rename(cursor, 'res_group_user_rel', cls._table) - TableHandler.sequence_rename(cursor, 'res_group_user_rel_id_seq', + TableHandler.table_rename('res_group_user_rel', cls._table) + TableHandler.sequence_rename('res_group_user_rel_id_seq', cls._table + '_id_seq') # Migration from 2.0 uid and gid rename into user and group - table = TableHandler(cursor, cls, module_name) + table = TableHandler(cls, module_name) table.column_rename('uid', 'user') table.column_rename('gid', 'group') super(UserGroup, cls).__register__(module_name) diff --git a/trytond/security.py b/trytond/security.py index 70d594e7ae..40baad64b9 100644 --- a/trytond/security.py +++ b/trytond/security.py @@ -20,24 +20,22 @@ def _get_pool(dbname): def login(dbname, loginname, password, cache=True): - with Transaction().start(dbname, 0) as transaction: + with Transaction().start(dbname, 0): pool = _get_pool(dbname) User = pool.get('res.user') user_id = User.get_login(loginname, password) - transaction.cursor.commit() if user_id: if not cache: return user_id - with Transaction().start(dbname, user_id) as transaction: + with Transaction().start(dbname, user_id): Session = pool.get('ir.session') session, = Session.create([{}]) - transaction.cursor.commit() return user_id, session.key return False def logout(dbname, user, session): - with Transaction().start(dbname, 0) as transaction: + with Transaction().start(dbname, 0): pool = _get_pool(dbname) Session = pool.get('ir.session') sessions = Session.search([ @@ -48,7 +46,6 @@ def logout(dbname, user, session): session, = sessions name = session.create_uid.login Session.delete(sessions) - transaction.cursor.commit() return name @@ -73,4 +70,4 @@ def check(dbname, user, session): else: return user finally: - transaction.cursor.commit() + transaction.commit() diff --git a/trytond/server.py b/trytond/server.py index 2fa2505107..36f892633b 100644 --- a/trytond/server.py +++ b/trytond/server.py @@ -78,18 +78,20 @@ class TrytonServer(object): if not self.options.update: self.start_servers() + Database = backend.get('Database') for db_name in self.options.database_names: init[db_name] = False try: - with Transaction().start(db_name, 0) as transaction: - cursor = transaction.cursor + with Transaction().start(db_name, 0) as transaction,\ + transaction.connection.cursor() as cursor: + database = Database(db_name) + database.connect() if self.options.update: - if not cursor.test(): + if not database.test(): self.logger.info("init db") - backend.get('Database').init(cursor) + database.init(cursor) init[db_name] = True - cursor.commit() - elif not cursor.test(): + elif not database.test(): raise Exception("'%s' is not a Tryton database!" % db_name) except Exception: @@ -98,9 +100,11 @@ class TrytonServer(object): for db_name in self.options.database_names: if self.options.update: - with Transaction().start(db_name, 0) as transaction: - cursor = transaction.cursor - if not cursor.test(): + with Transaction().start(db_name, 0) as transaction,\ + transaction.connection.cursor() as cursor: + database = Database(db_name) + database.connect() + if not database.test(): raise Exception("'%s' is not a Tryton database!" % db_name) lang = Table('ir_lang') @@ -145,7 +149,6 @@ class TrytonServer(object): User.write([admin], { 'password': password, }) - transaction.cursor.commit() if self.options.update: self.logger.info('Update/Init succeed!') diff --git a/trytond/tests/test_access.py b/trytond/tests/test_access.py index 9df96f8a83..be67436a0c 100644 --- a/trytond/tests/test_access.py +++ b/trytond/tests/test_access.py @@ -384,7 +384,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): TestAccess.read([test.id]) test.field1 test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # With field access @@ -400,7 +400,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): TestAccess.read([test.id]) test.field1 test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # One access disallowed for any group @@ -413,7 +413,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): self.assertRaises(UserError, TestAccess.read, [test.id]) self.assertRaises(UserError, getattr, test, 'field1') test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # Two access rules with one group allowed @@ -429,7 +429,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): TestAccess.read([test.id]) test.field1 test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # Two access rules with both allowed @@ -441,7 +441,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): TestAccess.read([test.id]) test.field1 test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # Two access rules with any group allowed @@ -453,7 +453,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): TestAccess.read([test.id]) test.field1 test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # Two access rules with both disallowed @@ -466,7 +466,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): self.assertRaises(UserError, TestAccess.read, [test.id]) self.assertRaises(UserError, getattr, test, 'field1') test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # One access disallowed for one group @@ -477,7 +477,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): self.assertRaises(UserError, TestAccess.read, [test.id]) self.assertRaises(UserError, getattr, test, 'field1') test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # One access allowed for one group @@ -489,7 +489,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): TestAccess.read([test.id]) test.field1 test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # One access allowed for one other group @@ -502,7 +502,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): TestAccess.read([test.id]) test.field1 test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # One access disallowed for one other group @@ -514,7 +514,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): TestAccess.read([test.id]) test.field1 test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # Two access rules on both fields allowed @@ -536,7 +536,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): TestAccess.read([test.id]) test.field1 test.field2 - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # Two access rules on both fields one allowed and one disallowed @@ -549,7 +549,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): self.assertRaises(UserError, TestAccess.read, [test.id]) test.field1 self.assertRaises(UserError, getattr, test, 'field2') - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) # Two access rules on both fields disallowed @@ -563,7 +563,7 @@ class ModelFieldAccessTestCase(unittest.TestCase): self.assertRaises(UserError, TestAccess.read, [test.id]) self.assertRaises(UserError, getattr, test, 'field1') self.assertRaises(UserError, getattr, test, 'field2') - transaction.cursor.cache.clear() + transaction.cache.clear() test = TestAccess(test.id) @with_transaction(context=_context) diff --git a/trytond/tests/test_fields.py b/trytond/tests/test_fields.py index fa8387979e..10b0b78896 100644 --- a/trytond/tests/test_fields.py +++ b/trytond/tests/test_fields.py @@ -300,7 +300,7 @@ class FieldsTestCase(unittest.TestCase): # We should catch UserError but mysql does not raise an # IntegrityError but an OperationalError self.assertRaises(Exception, IntegerRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() integer5, = IntegerRequired.create([{ 'integer': 0, @@ -308,7 +308,7 @@ class FieldsTestCase(unittest.TestCase): self.assert_(integer5) self.assertEqual(integer5.integer, 0) - transaction.cursor.rollback() + transaction.rollback() @with_transaction() def test_integer_with_domain(self): @@ -496,7 +496,7 @@ class FieldsTestCase(unittest.TestCase): }) self.assertRaises(UserError, FloatRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() float5, = FloatRequired.create([{ 'float': 0.0, @@ -742,7 +742,7 @@ class FieldsTestCase(unittest.TestCase): }) self.assertRaises(UserError, NumericRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() numeric5, = NumericRequired.create([{ 'numeric': Decimal(0), @@ -1044,12 +1044,12 @@ class FieldsTestCase(unittest.TestCase): self.assertEqual(char2.char, 'Test') self.assertRaises(UserError, CharRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, CharRequired.create, [{ 'char': '', }]) - transaction.cursor.rollback() + transaction.rollback() char5, = CharRequired.create([{ 'char': 'Test', @@ -1068,7 +1068,7 @@ class FieldsTestCase(unittest.TestCase): self.assertRaises(Exception, CharSize.write, [char6], { 'char': 'foobar', }) - transaction.cursor.rollback() + transaction.rollback() char7, = Char.create([{ 'char': u'é', @@ -1299,7 +1299,7 @@ class FieldsTestCase(unittest.TestCase): self.assertEqual(text2.text, 'Test') self.assertRaises(UserError, TextRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() text5, = TextRequired.create([{ 'text': 'Test', @@ -1573,7 +1573,7 @@ class FieldsTestCase(unittest.TestCase): self.assertEqual(date5.date, datetime.date(2009, 1, 1)) self.assertRaises(UserError, DateRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() date6, = DateRequired.create([{ 'date': today, @@ -1829,7 +1829,7 @@ class FieldsTestCase(unittest.TestCase): datetime.datetime(2009, 1, 1, 12, 0, 0)) self.assertRaises(UserError, DatetimeRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() datetime6, = DatetimeRequired.create([{ 'datetime': today, @@ -2080,7 +2080,7 @@ class FieldsTestCase(unittest.TestCase): self.assertEqual(time5.time, datetime.time(12, 0)) self.assertRaises(UserError, TimeRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() time6, = TimeRequired.create([{ 'time': evening, @@ -2197,17 +2197,17 @@ class FieldsTestCase(unittest.TestCase): 'name': 'one2one3', 'one2one': target1.id, }]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, One2one.write, [one2one2], { 'one2one': target1.id, }) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, One2oneRequired.create, [{ 'name': 'one2one3', }]) - transaction.cursor.rollback() + transaction.rollback() target3, = One2oneTarget.create([{ 'name': 'target3', @@ -2226,7 +2226,7 @@ class FieldsTestCase(unittest.TestCase): 'name': 'one2one4', 'one2one': target4.id, }]) - transaction.cursor.rollback() + transaction.rollback() target5, = One2oneTarget.create([{ 'name': 'domain', @@ -2434,12 +2434,12 @@ class FieldsTestCase(unittest.TestCase): ]) self.assertEqual(targets, []) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, One2manyRequired.create, [{ 'name': 'origin3', }]) - transaction.cursor.rollback() + transaction.rollback() origin3_id, = One2manyRequired.create([{ 'name': 'origin3', @@ -2642,12 +2642,12 @@ class FieldsTestCase(unittest.TestCase): ]) self.assertEqual(targets, []) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, Many2manyRequired.create, [{ 'name': 'origin3', }]) - transaction.cursor.rollback() + transaction.rollback() origin3_id, = Many2manyRequired.create([{ 'name': 'origin3', @@ -2663,7 +2663,7 @@ class FieldsTestCase(unittest.TestCase): 'name': str(i), } for i in range(6)]) - transaction.cursor.rollback() + transaction.rollback() @with_transaction() def test_many2many_tree(self): @@ -2846,7 +2846,7 @@ class FieldsTestCase(unittest.TestCase): self.assertRaises(UserError, ReferenceRequired.create, [{ 'name': 'reference4', }]) - transaction.cursor.rollback() + transaction.rollback() target4, = ReferenceTarget.create([{ 'name': 'target4_id', @@ -2942,7 +2942,7 @@ class FieldsTestCase(unittest.TestCase): Property.write([prop_b], {'char': 'Test'}) self.assertEqual(prop_b.char, 'Test') - transaction.cursor.rollback() + transaction.rollback() # Test Many2One char_a, = Char.create([{'char': 'Test'}]) @@ -2985,7 +2985,7 @@ class FieldsTestCase(unittest.TestCase): Property.write([prop_b], {'many2one': char_a.id}) self.assertEqual(prop_b.many2one, char_a) - transaction.cursor.rollback() + transaction.rollback() # Test Numeric prop_a, = Property.create([{'numeric': Decimal('1.1')}]) @@ -3069,7 +3069,7 @@ class FieldsTestCase(unittest.TestCase): Property.write([prop_b], {'numeric': Decimal('3.11')}) self.assertEqual(prop_b.numeric, Decimal('3.11')) - transaction.cursor.rollback() + transaction.rollback() # Test Selection prop_a, = Property.create([{'selection': 'option_a'}]) @@ -3173,11 +3173,11 @@ class FieldsTestCase(unittest.TestCase): [{'select': 'hexa', 'dyn_select': '3'}]) self.assertRaises(UserError, SelectionRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, SelectionRequired.create, [{'select': None}]) - transaction.cursor.rollback() + transaction.rollback() selection6, = SelectionRequired.create([{'select': 'latin'}]) self.assert_(selection6) @@ -3239,7 +3239,7 @@ class FieldsTestCase(unittest.TestCase): self.assert_(dict3.dico == {'a': 1}) self.assertRaises(UserError, DictRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() dict4, = DictRequired.create([{'dico': dict(a=1)}]) self.assert_(dict4.dico == {'a': 1}) @@ -3276,7 +3276,7 @@ class FieldsTestCase(unittest.TestCase): self.assert_(bin3.binary == fields.Binary.cast(b'default')) self.assertRaises(UserError, BinaryRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() bin4, = BinaryRequired.create([{ 'binary': fields.Binary.cast(b'baz'), @@ -3332,7 +3332,7 @@ class FieldsTestCase(unittest.TestCase): self.assertTrue(all(x.many2one.value <= y.many2one.value for x, y in zip(search, search[1:]))) - transaction.cursor.rollback() + transaction.rollback() target1, target2 = Many2oneTarget.create([ {'value': 1}, @@ -3356,7 +3356,7 @@ class FieldsTestCase(unittest.TestCase): ('many2one.value', '=', 1), ]), [search1]) - transaction.cursor.rollback() + transaction.rollback() for model in ['test.many2one_tree', 'test.many2one_mptt']: pool = Pool() @@ -3413,7 +3413,7 @@ class FieldsTestCase(unittest.TestCase): [('many2one', 'not parent_of', clause)]) self.assertEqual(result, not_(test)) - transaction.cursor.rollback() + transaction.rollback() @with_transaction() def test_timedelta(self): @@ -3621,7 +3621,7 @@ class FieldsTestCase(unittest.TestCase): }) self.assertRaises(UserError, TimedeltaRequired.create, [{}]) - transaction.cursor.rollback() + transaction.rollback() timedelta6, = TimedeltaRequired.create([{ 'timedelta': day, diff --git a/trytond/tests/test_history.py b/trytond/tests/test_history.py index d355c7722b..a8cd77b68a 100644 --- a/trytond/tests/test_history.py +++ b/trytond/tests/test_history.py @@ -21,12 +21,13 @@ class HistoryTestCase(unittest.TestCase): def tearDown(self): pool = Pool() History = pool.get('test.history') - cursor = Transaction().cursor + transaction = Transaction() + cursor = transaction.connection.cursor() table = History.__table__() history_table = History.__table_history__() cursor.execute(*table.delete()) cursor.execute(*history_table.delete()) - cursor.commit() + transaction.commit() @with_transaction() def test_read(self): @@ -42,21 +43,21 @@ class HistoryTestCase(unittest.TestCase): history_id = history.id first = history.create_date - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 2 history.save() second = history.write_date - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 3 history.save() third = history.write_date - transaction.cursor.commit() + transaction.commit() for timestamp, value in [ (first, 1), @@ -92,14 +93,14 @@ class HistoryTestCase(unittest.TestCase): self.assertEqual(first, second) - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 3 history.save() third = history.write_date - transaction.cursor.commit() + transaction.commit() for timestamp, value in [ (first, 2), @@ -121,21 +122,21 @@ class HistoryTestCase(unittest.TestCase): history_id = history.id first = history.create_date - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 2 history.save() second = history.write_date - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 3 history.save() third = history.write_date - transaction.cursor.commit() + transaction.commit() revisions = History.history_revisions([history_id]) self.assertEqual(revisions, [ @@ -156,28 +157,28 @@ class HistoryTestCase(unittest.TestCase): history_id = history.id first = history.create_date - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 2 history.save() - transaction.cursor.commit() + transaction.commit() History.restore_history([history_id], first) history = History(history_id) self.assertEqual(history.value, 1) - transaction.cursor.rollback() + transaction.rollback() History.restore_history([history_id], datetime.datetime.min) self.assertRaises(UserError, History.read, [history_id]) - transaction.cursor.rollback() + transaction.rollback() History.delete([History(history_id)]) - transaction.cursor.commit() + transaction.commit() History.restore_history([history_id], datetime.datetime.max) self.assertRaises(UserError, History.read, [history_id]) @@ -193,20 +194,20 @@ class HistoryTestCase(unittest.TestCase): history.save() history_id = history.id - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 2 history.save() second = history.write_date - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 3 history.save() - transaction.cursor.commit() + transaction.commit() History.restore_history_before([history_id], second) history = History(history_id) @@ -231,13 +232,13 @@ class HistoryTestCase(unittest.TestCase): self.assertEqual(first, second) - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 3 history.save() - transaction.cursor.commit() + transaction.commit() History.restore_history([history_id], first) history = History(history_id) @@ -255,14 +256,14 @@ class HistoryTestCase(unittest.TestCase): history.save() first_id = history.id first_stamp = history.create_date - transaction.cursor.commit() + transaction.commit() history = History(value=2) history.save() second_id = history.id second_stamp = history.create_date - transaction.cursor.commit() + transaction.commit() first, second = History.search([], order=order) @@ -272,7 +273,7 @@ class HistoryTestCase(unittest.TestCase): first.value = 3 first.save() third_stamp = first.write_date - transaction.cursor.commit() + transaction.commit() results = [ (first_stamp, [first]), @@ -285,14 +286,14 @@ class HistoryTestCase(unittest.TestCase): with Transaction().set_context(_datetime=timestamp): records = History.search([], order=order) self.assertEqual(records, instances) - transaction.cursor.rollback() + transaction.rollback() to_delete, _ = History.search([], order=order) self.assertEqual(to_delete.id, second.id) History.delete([to_delete]) - transaction.cursor.commit() + transaction.commit() results = [ (first_stamp, [first]), @@ -306,7 +307,7 @@ class HistoryTestCase(unittest.TestCase): from_test=True): records = History.search([], order=order) self.assertEqual(records, instances) - transaction.cursor.rollback() + transaction.rollback() @unittest.skipUnless(backend.name() == 'postgresql', 'CURRENT_TIMESTAMP as transaction_timestamp is specific to postgresql') @@ -326,7 +327,7 @@ class HistoryTestCase(unittest.TestCase): second_stamp = history.write_date self.assertEqual(first_stamp, second_stamp) - transaction.cursor.commit() + transaction.commit() results = [ (second_stamp, [history], [4]), @@ -340,7 +341,7 @@ class HistoryTestCase(unittest.TestCase): records = History.search([], order=order) self.assertEqual(records, instances) self.assertEqual([x.value for x in records], values) - transaction.cursor.rollback() + transaction.rollback() @with_transaction() def test_browse(self): @@ -365,7 +366,7 @@ class HistoryTestCase(unittest.TestCase): history.stamp = first_stamp history.save() - transaction.cursor.commit() + transaction.commit() history = History(history_id) history.value = 2 @@ -379,7 +380,7 @@ class HistoryTestCase(unittest.TestCase): second_stamp = line_a.write_date - transaction.cursor.commit() + transaction.commit() history = History(history_id) self.assertEqual(history.value, 2) @@ -403,53 +404,53 @@ class HistoryTestCase(unittest.TestCase): @with_transaction() def test_search_cursor_max(self): - 'Test search with number of history entries at cursor.IN_MAX' + 'Test search with number of history entries at database.IN_MAX' pool = Pool() History = pool.get('test.history') transaction = Transaction() - cursor = transaction.cursor + database = transaction.database history = History(value=-1) history.save() - for history.value in range(cursor.IN_MAX + 1): + for history.value in range(database.IN_MAX + 1): history.save() with transaction.set_context(_datetime=datetime.datetime.max): record, = History.search([]) - self.assertEqual(record.value, cursor.IN_MAX) + self.assertEqual(record.value, database.IN_MAX) @with_transaction() def test_search_cursor_max_entries(self): - 'Test search for skipping first history entries at cursor.IN_MAX' + 'Test search for skipping first history entries at database.IN_MAX' pool = Pool() History = pool.get('test.history') transaction = Transaction() - cursor = transaction.cursor + database = transaction.database for i in xrange(0, 2): history = History(value=-1) history.save() - for history.value in range(cursor.IN_MAX + 1): + for history.value in range(database.IN_MAX + 1): history.save() with transaction.set_context(_datetime=datetime.datetime.max): records = History.search([]) - self.assertEqual({r.value for r in records}, {cursor.IN_MAX}) + self.assertEqual({r.value for r in records}, {database.IN_MAX}) self.assertEqual(len(records), 2) @with_transaction() def test_search_cursor_max_histories(self): - 'Test search with number of histories at cursor.IN_MAX' + 'Test search with number of histories at database.IN_MAX' pool = Pool() History = pool.get('test.history') transaction = Transaction() - cursor = transaction.cursor + database = transaction.database - n = cursor.IN_MAX + 1 + n = database.IN_MAX + 1 History.create([{'value': 1}] * n) with transaction.set_context(_datetime=datetime.datetime.max): diff --git a/trytond/tests/test_importdata.py b/trytond/tests/test_importdata.py index 2123749e1d..748ffcc4a0 100644 --- a/trytond/tests/test_importdata.py +++ b/trytond/tests/test_importdata.py @@ -89,7 +89,7 @@ class ImportDataTestCase(unittest.TestCase): self.assertRaises(UserError, IntegerRequired.import_data, ['integer'], [['']]) - transaction.cursor.rollback() + transaction.rollback() self.assertEqual(IntegerRequired.import_data(['integer'], [['1'], ['2']]), 2) @@ -154,7 +154,7 @@ class ImportDataTestCase(unittest.TestCase): self.assertRaises(UserError, FloatRequired.import_data, ['float'], [['']]) - transaction.cursor.rollback() + transaction.rollback() self.assertEqual(FloatRequired.import_data(['float'], [['1.1'], ['2.2']]), 2) @@ -216,7 +216,7 @@ class ImportDataTestCase(unittest.TestCase): self.assertRaises(UserError, NumericRequired.import_data, ['numeric'], [['']]) - transaction.cursor.rollback() + transaction.rollback() self.assertEqual(NumericRequired.import_data(['numeric'], [['1.1'], ['2.2']]), 2) @@ -335,19 +335,19 @@ class ImportDataTestCase(unittest.TestCase): self.assertRaises(UserError, Many2one.import_data, ['many2one'], [['foo']]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, Many2one.import_data, ['many2one'], [['Duplicate']]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, Many2one.import_data, ['many2one:id'], [['foo']]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(Exception, Many2one.import_data, ['many2one:id'], [['tests.foo']]) - transaction.cursor.rollback() + transaction.rollback() @with_transaction() def test_many2many(self): @@ -383,19 +383,19 @@ class ImportDataTestCase(unittest.TestCase): self.assertRaises(UserError, Many2many.import_data, ['many2many'], [['foo']]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, Many2many.import_data, ['many2many'], [['Test 1,foo']]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, Many2many.import_data, ['many2many'], [['Duplicate']]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, Many2many.import_data, ['many2many'], [['Test 1,Duplicate']]) - transaction.cursor.rollback() + transaction.rollback() @with_transaction() def test_one2many(self): @@ -429,7 +429,7 @@ class ImportDataTestCase(unittest.TestCase): reference, = Reference.search([]) self.assertEqual(reference.reference.__name__, 'test.import_data.reference.selection') - transaction.cursor.rollback() + transaction.rollback() self.assertEqual(Reference.import_data(['reference:id'], [['test.import_data.reference.selection,' @@ -437,13 +437,13 @@ class ImportDataTestCase(unittest.TestCase): reference, = Reference.search([]) self.assertEqual(reference.reference.__name__, 'test.import_data.reference.selection') - transaction.cursor.rollback() + transaction.rollback() self.assertEqual(Reference.import_data(['reference'], [['']]), 1) reference, = Reference.search([]) self.assertEqual(reference.reference, None) - transaction.cursor.rollback() + transaction.rollback() self.assertEqual(Reference.import_data(['reference'], [['test.import_data.reference.selection,Test'], @@ -451,26 +451,26 @@ class ImportDataTestCase(unittest.TestCase): for reference in Reference.search([]): self.assertEqual(reference.reference.__name__, 'test.import_data.reference.selection') - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, Reference.import_data, ['reference'], [['test.import_data.reference.selection,foo']]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, Reference.import_data, ['reference'], [['test.import_data.reference.selection,Duplicate']]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(UserError, Reference.import_data, ['reference:id'], [['test.import_data.reference.selection,foo']]) - transaction.cursor.rollback() + transaction.rollback() self.assertRaises(Exception, Reference.import_data, ['reference:id'], [['test.import_data.reference.selection,test.foo']]) - transaction.cursor.rollback() + transaction.rollback() def suite(): diff --git a/trytond/tests/test_mixins.py b/trytond/tests/test_mixins.py index 6448f11338..a65f36a045 100644 --- a/trytond/tests/test_mixins.py +++ b/trytond/tests/test_mixins.py @@ -23,7 +23,7 @@ class UrlTestCase(unittest.TestCase): "Test model URLs" pool = Pool() UrlObject = pool.get('test.urlobject') - db_name = Transaction().database.database_name + db_name = Transaction().database.name self.assertEqual(UrlObject.__url__, 'tryton://%s/%s/model/test.urlobject' % ( @@ -38,7 +38,7 @@ class UrlTestCase(unittest.TestCase): "Test wizard URLs" pool = Pool() UrlWizard = pool.get('test.test_wizard', type='wizard') - db_name = Transaction().database.database_name + db_name = Transaction().database.name self.assertEqual(UrlWizard.__url__, 'tryton://%s/%s/wizard/test.test_wizard' % ( diff --git a/trytond/tests/test_modelsql.py b/trytond/tests/test_modelsql.py index 0697d5d106..bf42a01c5d 100644 --- a/trytond/tests/test_modelsql.py +++ b/trytond/tests/test_modelsql.py @@ -44,7 +44,7 @@ class ModelSQLTestCase(unittest.TestCase): self.assertTrue(key not in err.message, msg) else: self.fail('UserError should be caught') - transaction.cursor.rollback() + transaction.rollback() @with_transaction() def test_check_timestamp(self): @@ -52,11 +52,10 @@ class ModelSQLTestCase(unittest.TestCase): pool = Pool() ModelsqlTimestamp = pool.get('test.modelsql.timestamp') transaction = Transaction() - # cursor must be committed between each changes otherwise NOW() returns - # always the same timestamp. - cursor = transaction.cursor + # transaction must be committed between each changes otherwise NOW() + # returns always the same timestamp. record, = ModelsqlTimestamp.create([{}]) - cursor.commit() + transaction.commit() timestamp = ModelsqlTimestamp.read([record.id], ['_timestamp'])[0]['_timestamp'] @@ -66,7 +65,7 @@ class ModelSQLTestCase(unittest.TestCase): time.sleep(1) ModelsqlTimestamp.write([record], {}) - cursor.commit() + transaction.commit() transaction.timestamp[str(record)] = timestamp self.assertRaises(ConcurrencyException, @@ -78,9 +77,9 @@ class ModelSQLTestCase(unittest.TestCase): transaction.timestamp.pop(str(record), None) ModelsqlTimestamp.write([record], {}) - cursor.commit() + transaction.commit() ModelsqlTimestamp.delete([record]) - cursor.commit() + transaction.commit() def suite(): diff --git a/trytond/tests/test_transaction.py b/trytond/tests/test_transaction.py index b9fa67e5e2..3d156ce49e 100644 --- a/trytond/tests/test_transaction.py +++ b/trytond/tests/test_transaction.py @@ -16,17 +16,6 @@ def empty_transaction(*args, **kwargs): return True -def manipulate_cursor(*args, **kwargs): - ''' - Just start a transaction in the context manager and close the cursor - during the transaction so that the cursor.close in the stop fails - ''' - with Transaction().start(*args, **kwargs) as transaction: - transaction.cursor.close() - transaction.cursor = None - return True - - class TransactionTestCase(unittest.TestCase): 'Test the Transaction Context manager' @@ -43,14 +32,6 @@ class TransactionTestCase(unittest.TestCase): context=CONTEXT) self.assertTrue(empty_transaction(DB_NAME, USER, context=CONTEXT)) - def test_cursorclose(self): - '''Manipulate the cursor during the transaction so that - the close in transaction stop fails. - Ensure that this does not affect opening of another transaction''' - self.assertRaises( - Exception, manipulate_cursor, DB_NAME, USER, context=CONTEXT) - self.assertTrue(empty_transaction(DB_NAME, USER, context=CONTEXT)) - def test_set_user(self): 'Test set_user' with Transaction().start(DB_NAME, USER, context=CONTEXT) \ @@ -84,6 +65,16 @@ class TransactionTestCase(unittest.TestCase): with Transaction().set_user(2): self.assertEqual(transaction.user, 2) + def test_stacked_transactions(self): + 'Test that transactions are stacked / unstacked correctly' + with Transaction().start(DB_NAME, USER, context=CONTEXT) \ + as transaction: + with transaction.new_transaction() as new_transaction: + self.assertIsNot(new_transaction, transaction) + self.assertIsNot(Transaction(), transaction) + self.assertIs(Transaction(), new_transaction) + self.assertIs(Transaction(), transaction) + def suite(): return unittest.TestLoader().loadTestsFromTestCase(TransactionTestCase) diff --git a/trytond/tests/test_trigger.py b/trytond/tests/test_trigger.py index 98d5e28a98..1d89838b9f 100644 --- a/trytond/tests/test_trigger.py +++ b/trytond/tests/test_trigger.py @@ -46,7 +46,7 @@ class TriggerTestCase(unittest.TestCase): } self.assert_(Trigger.create([values])) - transaction.cursor.rollback() + transaction.rollback() # on_exclusive for i in range(1, 4): @@ -57,14 +57,14 @@ class TriggerTestCase(unittest.TestCase): combination_values['on_%s' % mode] = True self.assertRaises(UserError, Trigger.create, [combination_values]) - transaction.cursor.rollback() + transaction.rollback() # check_condition condition_values = values.copy() condition_values['condition'] = '=' self.assertRaises(UserError, Trigger.create, [condition_values]) - transaction.cursor.rollback() + transaction.rollback() # Restart the cache on the get_triggers method of ir.trigger Trigger._get_triggers_cache.clear() diff --git a/trytond/tests/test_tryton.py b/trytond/tests/test_tryton.py index 6574f6b0ce..e5647bc358 100644 --- a/trytond/tests/test_tryton.py +++ b/trytond/tests/test_tryton.py @@ -60,15 +60,15 @@ def install_module(name): return Module.install(modules) - transaction.cursor.commit() + transaction.commit() InstallUpgrade = POOL.get('ir.module.install_upgrade', type='wizard') instance_id, _, _ = InstallUpgrade.create() - transaction.cursor.commit() + transaction.commit() InstallUpgrade(instance_id).transition_upgrade() InstallUpgrade.delete(instance_id) - transaction.cursor.commit() + transaction.commit() def with_transaction(user=1, context=None): @@ -78,7 +78,7 @@ def with_transaction(user=1, context=None): transaction = Transaction() with transaction.start(DB_NAME, user, context=context): result = func(*args, **kwargs) - transaction.cursor.rollback() + transaction.rollback() # Drop the cache as the transaction is rollbacked Cache.drop(DB_NAME) return result @@ -275,10 +275,7 @@ class ModuleTestCase(unittest.TestCase): def db_exist(): Database = backend.get('Database') database = Database().connect() - cursor = database.cursor() - databases = database.list(cursor) - cursor.close() - return DB_NAME in databases + return DB_NAME in database.list() def create_db(): diff --git a/trytond/tools/__init__.py b/trytond/tools/__init__.py index c5c917050a..5854f45760 100644 --- a/trytond/tools/__init__.py +++ b/trytond/tools/__init__.py @@ -1,5 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. +from itertools import izip + from .misc import * from .datetime_strftime import * from .decimal_ import * @@ -8,3 +10,13 @@ from .decimal_ import * class ClassProperty(property): def __get__(self, cls, owner): return self.fget.__get__(None, owner)() + + +def cursor_dict(cursor, size=None): + size = cursor.arraysize if size is None else size + while True: + rows = cursor.fetchmany(size) + if not rows: + break + for row in rows: + yield {d[0]: v for d, v in izip(cursor.description, row)} diff --git a/trytond/tools/misc.py b/trytond/tools/misc.py index a41c8822be..df29c3af9b 100644 --- a/trytond/tools/misc.py +++ b/trytond/tools/misc.py @@ -374,7 +374,7 @@ def grouped_slice(records, count=None): 'Grouped slice' from trytond.transaction import Transaction if count is None: - count = Transaction().cursor.IN_MAX + count = Transaction().database.IN_MAX for i in xrange(0, len(records), count): yield islice(records, i, i + count) diff --git a/trytond/transaction.py b/trytond/transaction.py index ef4ca63fd8..e4b398703e 100644 --- a/trytond/transaction.py +++ b/trytond/transaction.py @@ -3,20 +3,8 @@ from threading import local from sql import Flavor -from trytond.tools.singleton import Singleton from trytond import backend - - -class _TransactionManager(object): - ''' - Manage transaction start/stop - ''' - - def __enter__(self): - return Transaction() - - def __exit__(self, type, value, traceback): - Transaction().stop() +from trytond.config import config class _AttributeManager(object): @@ -35,30 +23,25 @@ class _AttributeManager(object): setattr(Transaction(), name, value) -class _CursorManager(object): - ''' - Manage cursor of transaction - ''' +class _Local(local): - def __init__(self, cursor): - self.cursor = cursor - - def __enter__(self): - return Transaction() - - def __exit__(self, type, value, traceback): - Transaction().cursor.close() - Transaction().cursor = self.cursor + def __init__(self): + # Transaction stack control + self.transactions = [] -class Transaction(local): +class Transaction(object): ''' Control the transaction ''' - __metaclass__ = Singleton - cursor = None + _local = _Local() + + cache_keys = {'language', 'fuzzy_translation', '_datetime'} + database = None + readonly = False + connection = None close = None user = None context = None @@ -67,6 +50,24 @@ class Transaction(local): delete = None # TODO check to merge with delete_records timestamp = None + def __new__(cls, new=False): + transactions = cls._local.transactions + if new or not transactions: + instance = super(Transaction, cls).__new__(cls) + instance.cache = {} + transactions.append(instance) + else: + instance = transactions[-1] + return instance + + def get_cache(self): + from trytond.cache import LRUDict + keys = tuple(((key, self.context[key]) + for key in sorted(self.cache_keys) + if key in self.context)) + return self.cache.setdefault((self.user, keys), + LRUDict(config.getint('cache', 'model'))) + def start(self, database_name, user, readonly=False, context=None, close=False, autocommit=False): ''' @@ -75,7 +76,6 @@ class Transaction(local): Database = backend.get('Database') assert self.user is None assert self.database is None - assert self.cursor is None assert self.close is None assert self.context is None if not database_name: @@ -83,11 +83,11 @@ class Transaction(local): else: database = Database(database_name).connect() Flavor.set(Database.flavor) - cursor = database.cursor(readonly=readonly, - autocommit=autocommit) self.user = user self.database = database - self.cursor = cursor + self.readonly = readonly + self.connection = database.get_connection(readonly=readonly, + autocommit=autocommit) self.close = close self.context = context or {} self.create_records = {} @@ -95,24 +95,32 @@ class Transaction(local): self.delete = {} self.timestamp = {} self.counter = 0 - return _TransactionManager() + return self - def stop(self): - ''' - Stop transaction - ''' - try: - self.cursor.close(close=self.close) - finally: - self.cursor = None - self.database = None - self.close = None - self.user = None - self.context = None - self.create_records = None - self.delete_records = None - self.delete = None - self.timestamp = None + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + transactions = self._local.transactions + if transactions.count(self) == 1: + if type is None and not self.readonly: + self.commit() + try: + self.rollback() + self.database.put_connection(self.connection) + finally: + self.database = None + self.readonly = False + self.connection = None + self.close = None + self.user = None + self.context = None + self.create_records = None + self.delete_records = None + self.delete = None + self.timestamp = None + current_instance = transactions.pop() + assert current_instance is self def set_context(self, context=None, **kwargs): if context is None: @@ -143,17 +151,23 @@ class Transaction(local): self.user = user return manager - def set_cursor(self, cursor): - manager = _AttributeManager(cursor=self.cursor) - self.cursor = cursor - return manager + def set_current_transaction(self, transaction): + self._local.transactions.append(transaction) + return transaction - def new_cursor(self, autocommit=False, readonly=False): - Database = backend.get('Database') - manager = _CursorManager(self.cursor) - database = Database(self.cursor.database_name).connect() - self.cursor = database.cursor(autocommit=autocommit, readonly=readonly) - return manager + def new_transaction(self, autocommit=False, readonly=False): + transaction = Transaction(new=True) + return transaction.start(self.database.name, self.user, + context=self.context, close=self.close, readonly=readonly, + autocommit=autocommit) + + def commit(self): + self.connection.commit() + + def rollback(self): + for cache in self.cache.itervalues(): + cache.clear() + self.connection.rollback() @property def language(self): diff --git a/trytond/url.py b/trytond/url.py index b65cda0466..865558777d 100644 --- a/trytond/url.py +++ b/trytond/url.py @@ -34,7 +34,7 @@ class URLAccessor(object): raise NotImplementedError url_part['name'] = cls.__name__ - url_part['database'] = Transaction().cursor.database_name + url_part['database'] = Transaction().database.name local_part = urllib.quote('%(database)s/%(type)s/%(name)s' % url_part) if isinstance(inst, Model) and inst.id: