Ticket #2705: for_update_1.0.patch

File for_update_1.0.patch, 8.1 KB (added by jdemoor, 7 years ago)

Updated patch for Django 1.0, several fixes, tested with Postgres and SQLite.

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

     
    7171        self.order_by = []
    7272        self.low_mark, self.high_mark = 0, None  # Used for offset/limit
    7373        self.distinct = False
     74        self.select_for_update = False
     75        self.select_for_update_nowait = False
    7476        self.select_related = False
    7577        self.related_select_cols = []
    7678
     
    179181        obj.order_by = self.order_by[:]
    180182        obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
    181183        obj.distinct = self.distinct
     184        obj.select_for_update = self.select_for_update
     185        obj.select_for_update_nowait = self.select_for_update_nowait
    182186        obj.select_related = self.select_related
    183187        obj.related_select_cols = []
    184188        obj.max_depth = self.max_depth
     
    225229        obj = self.clone()
    226230        obj.clear_ordering(True)
    227231        obj.clear_limits()
     232        obj.select_for_update = False
    228233        obj.select_related = False
    229234        obj.related_select_cols = []
    230235        obj.related_select_fields = []
     
    305310                        result.append('LIMIT %d' % val)
    306311                result.append('OFFSET %d' % self.low_mark)
    307312
     313        if self.select_for_update:
     314            result.append("%s" % self.connection.ops.for_update_sql(nowait=self.select_for_update_nowait))
     315
    308316        params.extend(self.extra_params)
    309317        return ' '.join(result), tuple(params)
    310318
  • django/db/models/manager.py

     
    119119    def order_by(self, *args, **kwargs):
    120120        return self.get_query_set().order_by(*args, **kwargs)
    121121
     122    def select_for_update(self, *args, **kwargs):
     123        return self.get_query_set().select_for_update(*args, **kwargs)
     124       
    122125    def select_related(self, *args, **kwargs):
    123126        return self.get_query_set().select_related(*args, **kwargs)
    124127
  • django/db/models/query.py

     
    379379        del_query = self._clone()
    380380
    381381        # Disable non-supported fields.
     382        del_query.query.select_for_update = False
    382383        del_query.query.select_related = False
    383384        del_query.query.clear_ordering()
    384385
     
    518519        else:
    519520            return self._filter_or_exclude(None, **filter_obj)
    520521
     522    def select_for_update(self, **kwargs):
     523        """
     524        Returns a new QuerySet instance that will select objects with a
     525        FOR UPDATE lock.
     526        """
     527        # Default to false for nowait
     528        nowait = kwargs.pop('nowait', False)
     529        obj = self._clone()
     530        obj.query.select_for_update = True
     531        obj.query.select_for_update_nowait = nowait
     532        return obj
     533
    521534    def select_related(self, *fields, **kwargs):
    522535        """
    523536        Returns a new QuerySet instance that will select related objects.
  • django/db/backends/sqlite3/base.py

     
    6767        # function django_date_trunc that's registered in connect().
    6868        return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name)
    6969
     70    def for_update_sql(self, nowait=False):
     71        # sqlite does not support FOR UPDATE
     72        return ''
     73
    7074    def drop_foreignkey_sql(self):
    7175        return ""
    7276
     
    202206        return bool(re.search(re_pattern, re_string))
    203207    except:
    204208        return False
     209
  • django/db/backends/mysql/base.py

     
    136136    def fulltext_search_sql(self, field_name):
    137137        return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
    138138
     139    def for_update_sql(self, nowait=False):
     140        """
     141        Return FOR UPDATE SQL clause to lock row for update
     142        Currently mysql ignores NOWAIT
     143        """
     144        if nowait:
     145            raise NotImplementedError("NOWAIT option for SELECT ... FOR UPDATE not implemented with mysql");
     146        BaseDatabaseWrapper.for_update_sql(self,nowait=False)
     147
     148
    139149    def no_limit_value(self):
    140150        # 2**64 - 1, as recommended by the MySQL documentation
    141151        return 18446744073709551615L
  • django/db/backends/__init__.py

     
    143143        """
    144144        return '%s'
    145145
     146    def for_update_sql(self, nowait=False):
     147        """
     148        Return FOR UPDATE SQL clause to lock row for update
     149        """
     150        if nowait:
     151            nowaitstr = ' NOWAIT'
     152        else:
     153            nowaitstr = ''
     154        return 'FOR UPDATE' + nowaitstr
     155
    146156    def fulltext_search_sql(self, field_name):
    147157        """
    148158        Returns the SQL WHERE clause to use in order to perform a full-text
  • tests/regressiontests/queries/models.py

     
    227227    def __unicode__(self):
    228228        return self.name
    229229
     230from django.db import transaction
     231def test_for_update():
     232    t = Tag(name='forupdate')
     233    t.save()
     234    transaction.commit()
     235    tfound = Tag.objects.select_for_update().get(pk=t.id)
     236    tfound.name = 'forupdate2'
     237    tfound.save()
     238    transaction.commit()
     239test_for_update = transaction.commit_manually(test_for_update)
     240
    230241__test__ = {'API_TESTS':"""
    231242>>> t1 = Tag.objects.create(name='t1')
    232243>>> t2 = Tag.objects.create(name='t2', parent=t1)
     
    953964>>> len([x[2] for x in q.alias_map.values() if x[2] == q.LOUTER and q.alias_refcount[x[1]]])
    9549651
    955966
     967Bug #2075
     968Added FOR UPDATE functionality
     969>>> test_for_update()
     970>>> Tag.objects.get(name='forupdate2').delete()
    956971"""}
    957972
    958973# In Python 2.3 and the Python 2.6 beta releases, exceptions raised in __len__
     
    9861001[]
    9871002
    9881003"""
     1004
  • docs/ref/models/querysets.txt

     
    627627
    628628        Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
    629629
     630select_for_update
     631~~~~~~~~~~~~~~~~~
     632
     633**New in Django development version:**
     634
     635Lock rows returned from a query. Most databases allow you to exclusively
     636lock rows with ``select_for_update()``. To do this call the method
     637``select_for_update()`` to lock all retrieved objects::
     638
     639    entry = Entry.objects.select_for_update().get(pk=1)
     640    ...
     641    entry.save()
     642
     643All objects returned using ``select_for_update()`` will be locked with an
     644exclusive lock which will remain locked until the transaction has finished.
     645In the case of using middleware, the locks will be released when the view
     646returns and the transaction is committed or rolled back. SQLite does not
     647support an exclusive lock so this is simply ignored for SQLite. Other
     648databases issue a ``FOR UPDATE``.
     649
     650If you would not like to wait for the lock then set the parameter nowait to
     651True. In this case, it will grab the lock if it isn't already locked. If
     652it is locked then it will throw an exception. This is not supported
     653with mysql. Doing this with mysql will raise a ``NotImplemented`` exception.
     654
     655Note that all rows returned are locked.  If you retrieve multiple objects,
     656all objects will be locked until the transaction is committed. Another
     657process which does a ``select_for_update()`` on the same rows will wait until
     658the transaction which has the locks is finished.
     659
    630660QuerySet methods that do not return QuerySets
    631661---------------------------------------------
    632662
Back to Top