Ticket #2705: for_update_7513.2.patch

File for_update_7513.2.patch, 7.9 KB (added by sakyamuni, 7 years ago)

Added NOWAIT support (raises exception with mysql), included KBS'es fix to get sqlite to work

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

     
    6767        self.order_by = []
    6868        self.low_mark, self.high_mark = 0, None  # Used for offset/limit
    6969        self.distinct = False
     70        self.select_for_update = False
     71        self.select_for_update_nowait = False
    7072        self.select_related = False
    7173        self.related_select_cols = []
    7274
     
    173175        obj.order_by = self.order_by[:]
    174176        obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
    175177        obj.distinct = self.distinct
     178        obj.select_for_update = self.select_for_update
     179        obj.select_for_update_nowait = self.select_for_update_nowait
    176180        obj.select_related = self.select_related
    177181        obj.related_select_cols = []
    178182        obj.max_depth = self.max_depth
     
    211215        obj = self.clone()
    212216        obj.clear_ordering(True)
    213217        obj.clear_limits()
     218        obj.select_for_update = False
    214219        obj.select_related = False
    215220        obj.related_select_cols = []
    216221        obj.related_select_fields = []
     
    290295                        result.append('LIMIT %d' % val)
    291296                result.append('OFFSET %d' % self.low_mark)
    292297
     298        if self.select_for_update:
     299            result.append("%s" % self.connection.ops.for_update_sql(nowait=self.select_for_update_nowait))
     300
    293301        params.extend(self.extra_params)
    294302        return ' '.join(result), tuple(params)
    295303
  • django/db/models/manager.py

     
    108108    def order_by(self, *args, **kwargs):
    109109        return self.get_query_set().order_by(*args, **kwargs)
    110110
     111    def select_for_update(self, *args, **kwargs):
     112        return self.get_query_set().select_for_update(*args, **kwargs)
     113       
    111114    def select_related(self, *args, **kwargs):
    112115        return self.get_query_set().select_related(*args, **kwargs)
    113116
  • django/db/models/query.py

     
    267267        del_query = self._clone()
    268268
    269269        # Disable non-supported fields.
     270        del_query.query.select_for_update = False
    270271        del_query.query.select_related = False
    271272        del_query.query.clear_ordering()
    272273
     
    402403        else:
    403404            return self._filter_or_exclude(None, **filter_obj)
    404405
     406    def select_for_update(self, **kwargs):
     407        """
     408        Returns a new QuerySet instance that will select objects with a
     409        FOR UPDATE lock.
     410        """
     411        # Default to false for nowait
     412        nowait = kwargs.pop('nowait', False)
     413        obj = self._clone()
     414        obj.query.select_for_update = True
     415        obj.query.select_for_update_nowait = nowait
     416        return obj
     417
    405418    def select_related(self, *fields, **kwargs):
    406419        """
    407420        Returns a new QuerySet instance that will select related objects. If
  • django/db/backends/sqlite3/base.py

     
    5252        # function django_date_trunc that's registered in connect().
    5353        return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name)
    5454
     55    def for_update_sql(self):
     56        # sqlite does not support FOR UPDATE
     57        return ''
     58
    5559    def drop_foreignkey_sql(self):
    5660        return ""
    5761
     
    171175        return bool(re.search(re_pattern, re_string))
    172176    except:
    173177        return False
     178
  • django/db/backends/mysql/base.py

     
    8989    def fulltext_search_sql(self, field_name):
    9090        return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
    9191
     92    def for_update_sql(self, nowait=False):
     93        """
     94        Return FOR UPDATE SQL clause to lock row for update
     95
     96        Currently mysql ignores NOWAIT
     97        """
     98        if nowait:
     99            raise NotImplementedError("NOWAIT option for SELECT ... FOR UPDATE not implemented with mysql");
     100        BaseDatabaseWrapper.for_update_sql(self,nowait=False)
     101
    92102    def limit_offset_sql(self, limit, offset=None):
    93103        # 'LIMIT 20,40'
    94104        sql = "LIMIT "
  • django/db/backends/__init__.py

     
    160160        """
    161161        return cursor.lastrowid
    162162
     163    def for_update_sql(self, nowait=False):
     164        """
     165        Return FOR UPDATE SQL clause to lock row for update
     166        """
     167        if nowait:
     168            nowaitstr = ' NOWAIT'
     169        else:
     170            nowaitstr = ''
     171        return 'FOR UPDATE' + nowaitstr
     172
    163173    def limit_offset_sql(self, limit, offset=None):
    164174        """
    165175        Returns a LIMIT/OFFSET SQL clause, given a limit and optional offset.
  • tests/regressiontests/queries/models.py

     
    695695>>> Item.objects.exclude(~Q(tags__name='t1', name='one'), name='two')
    696696[<Item: four>, <Item: one>, <Item: three>]
    697697
     698Bug #2075
     699Added FOR UPDATE functionality
     700>>> from django.db import transaction
     701>>> @transaction.commit_manually
     702>>> def test_for_update():
     703>>>     t = Tag(name='for update test')
     704>>>     t.save()
     705>>>     transaction.commit()
     706>>>     tfound = Tag.objects.select_for_update().get(pk=t.id)
     707>>>     tfound.name = 'for update test 2'
     708>>>     tfound.save()
     709>>>     transaction.commit()
     710>>> test_for_update()
     711
    698712Bug #7095
    699713Updates that are filtered on the model being updated are somewhat tricky to get
    700714in MySQL. This exercises that case.
  • docs/db-api.txt

     
    10461046``extra()`` is new. Previously, you could attempt to pass parameters for
    10471047``select`` in the ``params`` argument, but it worked very unreliably.
    10481048
     1049select_for_update
     1050~~~~~~~~~~~~~~~~~
     1051
     1052**New in Django development version:**
     1053
     1054Lock rows returned from a query. Most databases allow you to exclusively
     1055lock rows with ``select_for_update()``. To do this call the method
     1056``select_for_update()`` to lock all retrieved objects::
     1057
     1058    entry = Entry.objects.select_for_update().get(pk=1)
     1059    ...
     1060    entry.save()
     1061
     1062All objects returned using ``select_for_update()`` will be locked with an
     1063exclusive lock which will remain locked until the transaction has finished.
     1064In the case of using middleware, the locks will be released when the view
     1065returns and the transaction is committed or rolled back. SQLite does not
     1066support an exclusive lock so this is simply ignored for SQLite. Other
     1067databases issue a ``FOR UPDATE``.
     1068
     1069If you would not like to wait for the lock then set the parameter nowait to
     1070True. In this case, it will grab the lock if it isn't already locked. If
     1071it is locked then it will throw an exception. This is not supported
     1072with mysql. Doing this with mysql will raise a ``NotImplemented`` exception.
     1073
     1074Note that all rows returned are locked.  If you retrieve multiple objects,
     1075all objects will be locked until the transaction is committed. Another
     1076process which does a ``select_for_update()`` on the same rows will wait until
     1077the transaction which has the locks is finished.
     1078
    10491079QuerySet methods that do not return QuerySets
    10501080---------------------------------------------
    10511081
Back to Top