From eeaa2a7f8073153e5bb838ff5ace51215d18d68a Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 28 Sep 2011 13:42:29 +0200 Subject: [PATCH] [FIX] ir_sequence: do the FOR UPDATE NO WAIT only in the No-gap sequence. bzr revid: vmt@openerp.com-20110928114229-1g6r5et5frj26wam --- openerp/addons/base/ir/ir_sequence.py | 14 +++-- tests/test_ir_sequence.py | 74 ++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/ir/ir_sequence.py b/openerp/addons/base/ir/ir_sequence.py index 086d48ac44d..c4d1a6f988b 100644 --- a/openerp/addons/base/ir/ir_sequence.py +++ b/openerp/addons/base/ir/ir_sequence.py @@ -153,7 +153,7 @@ class ir_sequence(openerp.osv.osv.osv): """ # TODO there is no access rights check on the sequence itself. res = self._select_by_code_or_id(cr, uid, sequence_code_or_id, - code_or_id, context) + code_or_id, False, context) if not res: return False @@ -164,6 +164,9 @@ class ir_sequence(openerp.osv.osv.osv): """ % res['id']) res['number_next'] = cr.fetchone() else: + # Read again with FOR UPDATE NO WAIT. + res = self._select_by_code_or_id(cr, uid, sequence_code_or_id, + code_or_id, True, context) cr.execute(""" UPDATE ir_sequence SET number_next=number_next+number_increment @@ -187,7 +190,8 @@ class ir_sequence(openerp.osv.osv.osv): """ return self.get_id(cr, uid, code, 'code', context) - def _select_by_code_or_id(self, cr, uid, sequence_code_or_id, code_or_id, context=None): + def _select_by_code_or_id(self, cr, uid, sequence_code_or_id, code_or_id, + for_update_no_wait, context=None): """ Read a sequence object. There is no access rights check on the sequence itself. @@ -195,6 +199,7 @@ class ir_sequence(openerp.osv.osv.osv): assert code_or_id in ('code', 'id') res_company = self.pool.get('res.company') company_ids = res_company.search(cr, uid, [], context=context) + funw = 'FOR UPDATE NOWAIT' if for_update_no_wait else '' cr.execute(""" SELECT id, number_next, prefix, suffix, padding, implementation FROM ir_sequence @@ -203,8 +208,9 @@ class ir_sequence(openerp.osv.osv.osv): AND (company_id in %%s or company_id is NULL) ORDER BY company_id, id LIMIT 1 - FOR UPDATE NOWAIT - """ % code_or_id, (sequence_code_or_id, tuple(company_ids))) + %s + """ % (code_or_id, funw), + (sequence_code_or_id, tuple(company_ids))) return cr.dictfetchone() def _create_sequence(self, cr, id, number_increment, number_next): diff --git a/tests/test_ir_sequence.py b/tests/test_ir_sequence.py index ef7e6784daa..0fc7aec5b50 100644 --- a/tests/test_ir_sequence.py +++ b/tests/test_ir_sequence.py @@ -7,6 +7,7 @@ # > OPENERP_ADDONS_PATH='../../../addons/trunk' OPENERP_PORT=8069 \ # OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence import os +import psycopg2 import time import unittest2 import xmlrpclib @@ -26,7 +27,8 @@ def registry(model): def cursor(): return openerp.modules.registry.RegistryManager.get(DB).db.cursor() -class test_ir_sequence(unittest2.TestCase): +class test_ir_sequence_standard(unittest2.TestCase): + """ A few tests for a 'Standard' (i.e. PostgreSQL) sequence. """ def test_ir_sequence_create(self): """ Try to create a sequence object. """ @@ -52,6 +54,76 @@ class test_ir_sequence(unittest2.TestCase): finally: cr.close() + def test_ir_sequence_draw(self): + """ Try to draw a number. """ + cr = cursor() + try: + n = registry('ir.sequence').get(cr, ADMIN_USER_ID, 'test_sequence_type', {}) + assert n + cr.commit() + finally: + cr.close() + + def test_ir_sequence_draw_twice(self): + """ Try to draw a number from two transactions. """ + cr0 = cursor() + cr1 = cursor() + try: + n0 = registry('ir.sequence').get(cr0, ADMIN_USER_ID, 'test_sequence_type', {}) + assert n0 + n1 = registry('ir.sequence').get(cr1, ADMIN_USER_ID, 'test_sequence_type', {}) + assert n1 + cr0.commit() + cr1.commit() + finally: + cr0.close() + cr1.close() + +class test_ir_sequence_no_gap(unittest2.TestCase): + """ Copy of the previous tests for a 'No gap' sequence. """ + + def test_ir_sequence_create_no_gap(self): + """ Try to create a sequence object. """ + cr = cursor() + try: + d = dict(code='test_sequence_type_2', name='Test sequence type') + c = registry('ir.sequence.type').create(cr, ADMIN_USER_ID, d, {}) + assert c + d = dict(code='test_sequence_type_2', name='Test sequence', + implementation='no_gap') + c = registry('ir.sequence').create(cr, ADMIN_USER_ID, d, {}) + assert c + cr.commit() + finally: + cr.close() + + def test_ir_sequence_draw_no_gap(self): + """ Try to draw a number. """ + cr = cursor() + try: + n = registry('ir.sequence').get(cr, ADMIN_USER_ID, 'test_sequence_type_2', {}) + assert n + cr.commit() + finally: + cr.close() + + def test_ir_sequence_draw_twice_no_gap(self): + """ Try to draw a number from two transactions. + This is expected to not work. + """ + cr0 = cursor() + cr1 = cursor() + try: + msg_re = '^could not obtain lock on row in relation "ir_sequence"$' + with self.assertRaisesRegexp(psycopg2.OperationalError, msg_re): + n0 = registry('ir.sequence').get(cr0, ADMIN_USER_ID, 'test_sequence_type_2', {}) + assert n0 + n1 = registry('ir.sequence').get(cr1, ADMIN_USER_ID, 'test_sequence_type_2', {}) + finally: + cr0.close() + cr1.close() + + if __name__ == '__main__': unittest2.main()