changeset 0ac9a6f5748b in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=0ac9a6f5748b
description:
        Allow to lock records for update

        issue8012
        review45901002
diffstat:

 CHANGELOG                      |   1 +
 doc/ref/models/models.rst      |   6 ++++++
 trytond/model/modelsql.py      |  20 +++++++++++++++++++-
 trytond/tests/modelsql.py      |   6 ++++++
 trytond/tests/test_modelsql.py |  20 ++++++++++++++++++++
 5 files changed, 52 insertions(+), 1 deletions(-)

diffs (119 lines):

diff -r e681112facef -r 0ac9a6f5748b CHANGELOG
--- a/CHANGELOG Wed Jan 30 09:36:21 2019 +0100
+++ b/CHANGELOG Wed Jan 30 09:45:59 2019 +0100
@@ -1,3 +1,4 @@
+* Allows to lock records for update
 * Remove _nocache on Transaction
 * Make Cache transactional
 * Allow to search on key's value of Dict
diff -r e681112facef -r 0ac9a6f5748b doc/ref/models/models.rst
--- a/doc/ref/models/models.rst Wed Jan 30 09:36:21 2019 +0100
+++ b/doc/ref/models/models.rst Wed Jan 30 09:45:59 2019 +0100
@@ -463,6 +463,12 @@
                 },
             }
 
+Dual methods:
+
+.. classmethod:: ModelSQL.lock(records)
+
+    Take a lock for update on the records.
+
 Constraint
 ==========
 
diff -r e681112facef -r 0ac9a6f5748b trytond/model/modelsql.py
--- a/trytond/model/modelsql.py Wed Jan 30 09:36:21 2019 +0100
+++ b/trytond/model/modelsql.py Wed Jan 30 09:45:59 2019 +0100
@@ -6,7 +6,7 @@
 from functools import wraps
 
 from sql import (Table, Column, Literal, Desc, Asc, Expression, Null,
-    NullsFirst, NullsLast)
+    NullsFirst, NullsLast, For)
 from sql.functions import CurrentTimestamp, Extract
 from sql.conditionals import Coalesce
 from sql.operators import Or, And, Operator, Equal
@@ -26,6 +26,7 @@
 
 from .modelstorage import (cache_size, is_leaf,
     ValidationError, RequiredValidationError, AccessError)
+from .descriptors import dualmethod
 
 
 class ForeignKeyError(ValidationError):
@@ -1552,6 +1553,23 @@
                     if cursor.fetchone():
                         raise SQLConstraintError(gettext(error))
 
+    @dualmethod
+    def lock(cls, records):
+        transaction = Transaction()
+        database = transaction.database
+        connection = transaction.connection
+        table = cls.__table__()
+
+        if database.has_select_for():
+            for sub_records in grouped_slice(records):
+                where = reduce_ids(table.id, sub_records)
+                query = table.select(
+                    Literal(1), where=where, for_=For('UPDATE', nowait=True))
+                with connection.cursor() as cursor:
+                    cursor.execute(*query)
+        else:
+            database.lock(connection, cls._table)
+
 
 def convert_from(table, tables):
     # Don't nested joins as SQLite doesn't support
diff -r e681112facef -r 0ac9a6f5748b trytond/tests/modelsql.py
--- a/trytond/tests/modelsql.py Wed Jan 30 09:36:21 2019 +0100
+++ b/trytond/tests/modelsql.py Wed Jan 30 09:45:59 2019 +0100
@@ -122,6 +122,11 @@
             ]
 
 
+class ModelLock(ModelSQL):
+    'Model to test lock'
+    __name__ = 'test.modelsql.lock'
+
+
 def register(module):
     Pool.register(
         ModelSQLRead,
@@ -136,4 +141,5 @@
         ModelCheck,
         ModelUnique,
         ModelExclude,
+        ModelLock,
         module=module, type_='model')
diff -r e681112facef -r 0ac9a6f5748b trytond/tests/test_modelsql.py
--- a/trytond/tests/test_modelsql.py    Wed Jan 30 09:36:21 2019 +0100
+++ b/trytond/tests/test_modelsql.py    Wed Jan 30 09:45:59 2019 +0100
@@ -475,6 +475,26 @@
         with self.assertRaises(SQLConstraintError):
             Model.create([{'value': 42}, {'value': 42}])
 
+    @unittest.skipIf(backend.name() == 'sqlite',
+        'SQLite does not have lock at table level but on file')
+    @with_transaction()
+    def test_lock(self):
+        "Test lock"
+        pool = Pool()
+        Model = pool.get('test.modelsql.lock')
+        DatabaseOperationalError = backend.get('DatabaseOperationalError')
+        transaction = Transaction()
+        record_id = Model.create([{}])[0].id
+        transaction.commit()
+
+        with transaction.new_transaction():
+            record = Model(record_id)
+            record.lock()
+            with transaction.new_transaction():
+                record = Model(record_id)
+                with self.assertRaises(DatabaseOperationalError):
+                    record.lock()
+
 
 def suite():
     return unittest.TestLoader().loadTestsFromTestCase(ModelSQLTestCase)

Reply via email to