Follow PEP-0249 for Transaction and backends

issue4949
review19781002
This commit is contained in:
Nicolas Évrard
2016-03-03 21:51:57 +01:00
parent 3e13f3d004
commit 2fef479575
51 changed files with 1227 additions and 1331 deletions
+1
View File
@@ -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
+1
View File
@@ -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
+27 -18
View File
@@ -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/
+39 -109
View File
@@ -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]
+42 -68
View File
@@ -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,))
+150 -123
View File
@@ -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)
+88 -116
View File
@@ -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,
}
+133 -120
View File
@@ -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)
+83 -96
View File
@@ -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):
+42 -36
View File
@@ -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)
+6 -12
View File
@@ -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.
+26 -29
View File
@@ -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):
+7 -7
View File
@@ -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
+17 -12
View File
@@ -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')
+6 -5
View File
@@ -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)
+6 -6
View File
@@ -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)
+1 -2
View File
@@ -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',
+11 -13
View File
@@ -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'):
+12 -15
View File
@@ -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'
+1 -1
View File
@@ -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))
+2 -2
View File
@@ -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__()
+10 -10
View File
@@ -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)
+1 -2
View File
@@ -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
+34 -35
View File
@@ -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']],
+7 -7
View File
@@ -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')
+3 -3
View File
@@ -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]),
+5 -8
View File
@@ -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
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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]),
+43 -42
View File
@@ -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)
+14 -13
View File
@@ -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:
+120 -125
View File
@@ -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()
+2 -2
View File
@@ -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
+20 -33
View File
@@ -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()]
+15 -22
View File
@@ -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)
+10 -11
View File
@@ -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)
+4 -7
View File
@@ -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()
+13 -10
View File
@@ -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!')
+14 -14
View File
@@ -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)
+32 -32
View File
@@ -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,
+42 -41
View File
@@ -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):
+19 -19
View File
@@ -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():
+2 -2
View File
@@ -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' % (
+7 -8
View File
@@ -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():
+10 -19
View File
@@ -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)
+3 -3
View File
@@ -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()
+5 -8
View File
@@ -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():
+12
View File
@@ -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)}
+1 -1
View File
@@ -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)
+74 -60
View File
@@ -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):
+1 -1
View File
@@ -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: