- Remove `_default_log_exceptions` of our `Cursor` class (unused except in one test) - Deprecated `serialized` args of `__init__` (Cursor class) and `cursor()` (of Connection class), our cursor is always serialized. - Remove `sql_log` attribute of Cursor class and replace it with appropriate code to do the same stuff dynamically. - Simplify some code - Update some docstring - Clean import task-2766494 closes odoo/odoo#85078 Signed-off-by: Rémy Voet <ryv@odoo.com>
209 lines
6.9 KiB
Python
209 lines
6.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import logging
|
|
from functools import partial
|
|
|
|
import psycopg2
|
|
from psycopg2.extensions import ISOLATION_LEVEL_REPEATABLE_READ
|
|
|
|
import odoo
|
|
from odoo.sql_db import TestCursor
|
|
from odoo.tests import common
|
|
from odoo.tests.common import BaseCase
|
|
|
|
ADMIN_USER_ID = common.ADMIN_USER_ID
|
|
|
|
def registry():
|
|
return odoo.registry(common.get_db_name())
|
|
|
|
|
|
class TestRealCursor(BaseCase):
|
|
|
|
def test_execute_bad_params(self):
|
|
"""
|
|
Try to use iterable but non-list or int params in query parameters.
|
|
"""
|
|
with registry().cursor() as cr:
|
|
with self.assertRaises(ValueError):
|
|
cr.execute("SELECT id FROM res_users WHERE login=%s", 'admin')
|
|
with self.assertRaises(ValueError):
|
|
cr.execute("SELECT id FROM res_users WHERE id=%s", 1)
|
|
with self.assertRaises(ValueError):
|
|
cr.execute("SELECT id FROM res_users WHERE id=%s", '1')
|
|
|
|
def test_using_closed_cursor(self):
|
|
with registry().cursor() as cr:
|
|
cr.close()
|
|
with self.assertRaises(psycopg2.InterfaceError):
|
|
cr.execute("SELECT 1")
|
|
|
|
def test_multiple_close_call_cursor(self):
|
|
cr = registry().cursor()
|
|
cr.close()
|
|
cr.close()
|
|
|
|
def test_transaction_isolation_cursor(self):
|
|
with registry().cursor() as cr:
|
|
self.assertEqual(cr.connection.isolation_level, ISOLATION_LEVEL_REPEATABLE_READ)
|
|
|
|
class TestTestCursor(common.TransactionCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
# make the registry in test mode
|
|
self.registry.enter_test_mode(self.cr)
|
|
self.addCleanup(self.registry.leave_test_mode)
|
|
# now we make a test cursor for self.cr
|
|
self.cr = self.registry.cursor()
|
|
self.addCleanup(self.cr.close)
|
|
self.env = odoo.api.Environment(self.cr, odoo.SUPERUSER_ID, {})
|
|
self.record = self.env['res.partner'].create({'name': 'Foo'})
|
|
|
|
def write(self, record, value):
|
|
record.ref = value
|
|
|
|
def flush(self, record):
|
|
record.flush_model(['ref'])
|
|
|
|
def check(self, record, value):
|
|
# make sure to fetch the field from the database
|
|
record.invalidate_recordset()
|
|
self.assertEqual(record.read(['ref'])[0]['ref'], value)
|
|
|
|
def test_single_cursor(self):
|
|
""" Check the behavior of a single test cursor. """
|
|
self.assertIsInstance(self.cr, TestCursor)
|
|
self.write(self.record, 'A')
|
|
self.cr.commit()
|
|
|
|
self.write(self.record, 'B')
|
|
self.cr.rollback()
|
|
self.check(self.record, 'A')
|
|
|
|
self.write(self.record, 'C')
|
|
self.cr.rollback()
|
|
self.check(self.record, 'A')
|
|
|
|
def test_sub_commit(self):
|
|
""" Check the behavior of a subcursor that commits. """
|
|
self.assertIsInstance(self.cr, TestCursor)
|
|
self.write(self.record, 'A')
|
|
self.cr.commit()
|
|
|
|
self.write(self.record, 'B')
|
|
self.flush(self.record)
|
|
|
|
# check behavior of a "sub-cursor" that commits
|
|
with self.registry.cursor() as cr:
|
|
self.assertIsInstance(cr, TestCursor)
|
|
record = self.record.with_env(self.env(cr=cr))
|
|
self.check(record, 'B')
|
|
self.write(record, 'C')
|
|
|
|
self.check(self.record, 'C')
|
|
|
|
self.cr.rollback()
|
|
self.check(self.record, 'A')
|
|
|
|
def test_sub_rollback(self):
|
|
""" Check the behavior of a subcursor that rollbacks. """
|
|
self.assertIsInstance(self.cr, TestCursor)
|
|
self.write(self.record, 'A')
|
|
self.cr.commit()
|
|
|
|
self.write(self.record, 'B')
|
|
self.flush(self.record)
|
|
|
|
# check behavior of a "sub-cursor" that rollbacks
|
|
with self.assertRaises(ValueError):
|
|
with self.registry.cursor() as cr:
|
|
self.assertIsInstance(cr, TestCursor)
|
|
record = self.record.with_env(self.env(cr=cr))
|
|
self.check(record, 'B')
|
|
self.write(record, 'C')
|
|
raise ValueError(42)
|
|
|
|
self.check(self.record, 'B')
|
|
|
|
self.cr.rollback()
|
|
self.check(self.record, 'A')
|
|
|
|
def test_interleaving(self):
|
|
"""If test cursors are retrieved independently it becomes possible for
|
|
the savepoint operations to be interleaved (especially as some are lazy
|
|
e.g. the request cursor, so cursors might be semantically nested but
|
|
technically interleaved), and for them to commit one another:
|
|
|
|
.. code-block:: sql
|
|
|
|
SAVEPOINT A
|
|
SAVEPOINT B
|
|
RELEASE SAVEPOINT A
|
|
RELEASE SAVEPOINT B -- "savepoint b does not exist"
|
|
"""
|
|
a = self.registry.cursor()
|
|
_b = self.registry.cursor()
|
|
# `a` should warn that it found un-closed cursor `b` when trying to close itself
|
|
with self.assertLogs('odoo.sql_db', level=logging.WARNING) as cm:
|
|
a.close()
|
|
[msg] = cm.output
|
|
self.assertIn('WARNING:odoo.sql_db:Found different un-closed cursor', msg)
|
|
# avoid a warning on teardown (when self.cr finds a still on the stack)
|
|
# as well as ensure the stack matches our expectations
|
|
self.assertEqual(a._cursors_stack.pop(), a)
|
|
|
|
class TestCursorHooks(common.TransactionCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.log = []
|
|
|
|
def prepare_hooks(self, cr):
|
|
self.log.clear()
|
|
cr.precommit.add(partial(self.log.append, 'preC'))
|
|
cr.postcommit.add(partial(self.log.append, 'postC'))
|
|
cr.prerollback.add(partial(self.log.append, 'preR'))
|
|
cr.postrollback.add(partial(self.log.append, 'postR'))
|
|
self.assertEqual(self.log, [])
|
|
|
|
def test_hooks_on_cursor(self):
|
|
cr = self.registry.cursor()
|
|
|
|
# check hook on commit()
|
|
self.prepare_hooks(cr)
|
|
cr.commit()
|
|
self.assertEqual(self.log, ['preC', 'postC'])
|
|
|
|
# check hook on flush(), then on rollback()
|
|
self.prepare_hooks(cr)
|
|
cr.flush()
|
|
self.assertEqual(self.log, ['preC'])
|
|
cr.rollback()
|
|
self.assertEqual(self.log, ['preC', 'preR', 'postR'])
|
|
|
|
# check hook on close()
|
|
self.prepare_hooks(cr)
|
|
cr.close()
|
|
self.assertEqual(self.log, ['preR', 'postR'])
|
|
|
|
def test_hooks_on_testcursor(self):
|
|
self.registry.enter_test_mode(self.cr)
|
|
self.addCleanup(self.registry.leave_test_mode)
|
|
|
|
cr = self.registry.cursor()
|
|
|
|
# check hook on commit(); post-commit hooks are ignored
|
|
self.prepare_hooks(cr)
|
|
cr.commit()
|
|
self.assertEqual(self.log, ['preC'])
|
|
|
|
# check hook on flush(), then on rollback()
|
|
self.prepare_hooks(cr)
|
|
cr.flush()
|
|
self.assertEqual(self.log, ['preC'])
|
|
cr.rollback()
|
|
self.assertEqual(self.log, ['preC', 'preR', 'postR'])
|
|
|
|
# check hook on close()
|
|
self.prepare_hooks(cr)
|
|
cr.close()
|
|
self.assertEqual(self.log, ['preR', 'postR'])
|