Code

Ticket #2705: for_update_7513.2.patch

File for_update_7513.2.patch, 7.9 KB (added by sakyamuni, 6 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