Ticket #2705: for_update_1.2.0-final.patch.diff

File for_update_1.2.0-final.patch.diff, 12.2 KB (added by ftmo, 5 years ago)

based on the file for_update_11366_cdestigter.diff

  • django/db/models/sql/compiler.py

     
    114114                        result.append('LIMIT %d' % val)
    115115                result.append('OFFSET %d' % self.query.low_mark)
    116116
     117        if self.query.select_for_update and self.connection.features.has_select_for_update:
     118            nowait = self.query.select_for_update_nowait and self.connection.features.has_select_for_update
     119            result.append("%s" % self.connection.ops.for_update_sql(nowait=nowait))
     120
    117121        return ' '.join(result), tuple(params)
    118122
    119123    def as_nested_sql(self):
  • django/db/models/sql/query.py

     
    1111from django.utils.tree import Node
    1212from django.utils.datastructures import SortedDict
    1313from django.utils.encoding import force_unicode
    14 from django.db import connections, DEFAULT_DB_ALIAS
     14from django.db import connections, DEFAULT_DB_ALIAS, DatabaseError
    1515from django.db.models import signals
    1616from django.db.models.fields import FieldDoesNotExist
    1717from django.db.models.query_utils import select_related_descend, InvalidQuery
     
    2323    ExtraWhere, AND, OR)
    2424from django.core.exceptions import FieldError
    2525
    26 __all__ = ['Query', 'RawQuery']
     26__all__ = ['Query', 'RawQuery', 'LockNotAvailable']
    2727
     28
     29class LockNotAvailable(DatabaseError):
     30    '''
     31    Raised when a query fails because a lock was not available.
     32    '''
     33    pass
     34
     35
    2836class RawQuery(object):
    2937    """
    3038    A single raw SQL query
     
    8492
    8593    def _execute_query(self):
    8694        self.cursor = connections[self.using].cursor()
    87         self.cursor.execute(self.sql, self.params)
     95        try:
     96            self.cursor.execute(self.sql, self.params)
     97        except DatabaseError, e:
     98            if self.connection.features.has_select_for_update_nowait and self.connection.ops.signals_lock_not_available(e):
     99                raise LockNotAvailable(*e.args)
     100            raise
    88101
    89 
    90102class Query(object):
    91103    """
    92104    A single SQL query.
     
    131143        self.order_by = []
    132144        self.low_mark, self.high_mark = 0, None  # Used for offset/limit
    133145        self.distinct = False
     146        self.select_for_update = False
     147        self.select_for_update_nowait = False
    134148        self.select_related = False
    135149        self.related_select_cols = []
    136150
     
    259273        obj.order_by = self.order_by[:]
    260274        obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
    261275        obj.distinct = self.distinct
     276        obj.select_for_update = self.select_for_update
     277        obj.select_for_update_nowait = self.select_for_update_nowait
    262278        obj.select_related = self.select_related
    263279        obj.related_select_cols = []
    264280        obj.aggregates = deepcopy(self.aggregates, memo=memo)
     
    359375
    360376        query.clear_ordering(True)
    361377        query.clear_limits()
     378        query.select_for_update = False
    362379        query.select_related = False
    363380        query.related_select_cols = []
    364381        query.related_select_fields = []
  • django/db/models/manager.py

     
    164164    def order_by(self, *args, **kwargs):
    165165        return self.get_query_set().order_by(*args, **kwargs)
    166166
     167    def select_for_update(self, *args, **kwargs):
     168        return self.get_query_set().select_for_update(*args, **kwargs)
     169
    167170    def select_related(self, *args, **kwargs):
    168171        return self.get_query_set().select_related(*args, **kwargs)
    169172
  • django/db/models/__init__.py

     
    1111from django.db.models.fields.subclassing import SubfieldBase
    1212from django.db.models.fields.files import FileField, ImageField
    1313from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel
     14from django.db.models.sql.query import LockNotAvailable
    1415from django.db.models import signals
    1516
    1617# Admin stages.
  • django/db/models/query.py

     
    424424        del_query._for_write = True
    425425
    426426        # Disable non-supported fields.
     427        del_query.query.select_for_update = False
    427428        del_query.query.select_related = False
    428429        del_query.query.clear_ordering()
    429430
     
    585586        else:
    586587            return self._filter_or_exclude(None, **filter_obj)
    587588
     589    def select_for_update(self, **kwargs):
     590        """
     591        Returns a new QuerySet instance that will select objects with a
     592        FOR UPDATE lock.
     593        """
     594        # Default to false for nowait
     595        nowait = kwargs.pop('nowait', False)
     596        obj = self._clone()
     597        obj.query.select_for_update = True
     598        obj.query.select_for_update_nowait = nowait
     599        return obj
     600
    588601    def select_related(self, *fields, **kwargs):
    589602        """
    590603        Returns a new QuerySet instance that will select related objects.
  • django/db/backends/mysql/base.py

     
    124124    allows_group_by_pk = True
    125125    related_fields_match_type = True
    126126    allow_sliced_subqueries = False
     127    has_select_for_update = True
     128    has_select_for_update_nowait = False
    127129
    128130class DatabaseOperations(BaseDatabaseOperations):
    129131    compiler_module = "django.db.backends.mysql.compiler"
     132    signals_deadlock = lambda self, e: e.args[0] == ER.LOCK_DEADLOCK
    130133
    131134    def date_extract_sql(self, lookup_type, field_name):
    132135        # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
  • django/db/backends/oracle/base.py

     
    5050    uses_savepoints = True
    5151    can_return_id_from_insert = True
    5252    allow_sliced_subqueries = False
     53    has_select_for_update = True
     54    has_select_for_update_nowait = True
    5355
    5456
    5557class DatabaseOperations(BaseDatabaseOperations):
     
    279281                                           'column': column_name})
    280282        return output
    281283
     284    def signals_deadlock(self, exception):
     285        return exception.args[0].code == 60
     286
     287    def signals_lock_not_available(self, exception):
     288        return exception.args[0].code == 54
     289
    282290    def start_transaction_sql(self):
    283291        return ''
    284292
  • django/db/backends/__init__.py

     
    9696    # integer primary keys.
    9797    related_fields_match_type = False
    9898    allow_sliced_subqueries = True
     99    has_select_for_update = False
     100    has_select_for_update_nowait = False
    99101
    100102class BaseDatabaseOperations(object):
    101103    """
     
    185187        ordering.
    186188        """
    187189        return []
     190    def for_update_sql(self, nowait=False):
     191        """
     192        Return FOR UPDATE SQL clause to lock row for update
     193        """
     194        if nowait:
     195            nowaitstr = ' NOWAIT'
     196        else:
     197            nowaitstr = ''
     198        return 'FOR UPDATE' + nowaitstr
    188199
    189200    def fulltext_search_sql(self, field_name):
    190201        """
  • django/db/backends/postgresql_psycopg2/base.py

     
    1919try:
    2020    import psycopg2 as Database
    2121    import psycopg2.extensions
     22    from psycopg2 import errorcodes
    2223except ImportError, e:
    2324    from django.core.exceptions import ImproperlyConfigured
    2425    raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e)
     
    6768class DatabaseFeatures(BaseDatabaseFeatures):
    6869    needs_datetime_string_cast = False
    6970    can_return_id_from_insert = False
     71    has_select_for_update = True
     72    has_select_for_update_nowait = True
    7073
    7174class DatabaseOperations(PostgresqlDatabaseOperations):
     75    signals_deadlock = lambda self, e: e.pgcode == errorcodes.DEADLOCK_DETECTED
     76    signals_lock_not_available = lambda self, e: e.pgcode == errorcodes.LOCK_NOT_AVAILABLE
    7277    def last_executed_query(self, cursor, sql, params):
    7378        # With psycopg2, cursor objects have a "query" attribute that is the
    7479        # exact query sent to the database. See docs here:
  • docs/ref/models/querysets.txt

     
    963963    # queries the database with the 'backup' alias
    964964    >>> Entry.objects.using('backup')
    965965
     966``select_for_update(nowait=False)``
     967~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     968         
     969Returns a queryset that will lock rows until the end of the transaction, 
     970generating a SELECT ... FOR UPDATE statement on supported databases.
     971         
     972For example::
     973         
     974    entries = Entry.objects.select_for_update().filter(author=request.user)
     975 
     976All matched entries will be locked until the end of the transaction block, 
     977meaning that other transactions will be prevented from changing or acquiring 
     978locks on them.
     979 
     980Usually, if another transaction has already acquired a lock on one of the 
     981selected rows, the query will block until the lock is released. If this is 
     982not the behaviour you want, call ``select_for_update(nowait=True)``. This will 
     983make the call non-blocking. If a conflicting lock is already acquired by 
     984another transaction, ``django.db.models.LockNotAvailable`` will be raised when 
     985the queryset is evaluated.
     986 
     987Using blocking locks on a database can lead to deadlocks. This occurs when two 
     988concurrent transactions are both waiting on a lock the other transaction 
     989already holds. To deal with deadlocks, wrap your views that use 
     990``select_for_update(nowait=False)`` with the 
     991``django.views.decorators.deadlock.handle_deadlocks`` decorator. 
     992 
     993For example::
     994 
     995    from django.db import transaction
     996    from django.views.decorators.deadlock import handle_deadlocks
     997 
     998    @handle_deadlocks(max_retries=2)
     999    @transaction.commit_on_success
     1000    def my_view(request):
     1001        ...
     1002 
     1003If the database engine detects a deadlock involving ``my_view`` and decides 
     1004to abort its transaction, it will be automatically retried. If deadlocks keep 
     1005occurring after two repeated attempts, 
     1006``django.views.decorators.DeadlockError`` will be raised, which can be 
     1007propagated to the user or handled in a middleware.
     1008 
     1009Currently the ``postgresql_psycopg2``, ``oracle``, and ``mysql``
     1010database backends support ``select_for_update()`` but MySQL has no
     1011support for the ``nowait`` argument. Other backends will simply
     1012generate queries as if ``select_for_update()`` had not been used.
    9661013
    9671014QuerySet methods that do not return QuerySets
    9681015---------------------------------------------
  • docs/ref/databases.txt

     
    345345column types have a maximum length restriction of 255 characters, regardless
    346346of whether ``unique=True`` is specified or not.
    347347
     348Row locking with ``QuerySet.select_for_update()``
     349-------------------------------------------------
     350 
     351MySQL does not support the NOWAIT option to the SELECT ... FOR UPDATE 
     352statement. However, you may call the ``select_for_update()`` method of a 
     353queryset with ``nowait=True``. In that case, the argument will be silently 
     354discarded and the generated query will block until the requested lock can be 
     355acquired.
     356
    348357.. _sqlite-notes:
    349358
    350359SQLite notes
Back to Top