Skip to content
Projeler
Gruplar
Parçacıklar
Yardım
Yükleniyor...
Oturum aç / Kaydol
Gezinmeyi değiştir
D
django
Proje
Proje
Ayrıntılar
Etkinlik
Cycle Analytics
Depo (repository)
Depo (repository)
Dosyalar
Kayıtlar (commit)
Dallar (branch)
Etiketler
Katkıda bulunanlar
Grafik
Karşılaştır
Grafikler
Konular (issue)
0
Konular (issue)
0
Liste
Pano
Etiketler
Kilometre Taşları
Birleştirme (merge) Talepleri
0
Birleştirme (merge) Talepleri
0
CI / CD
CI / CD
İş akışları (pipeline)
İşler
Zamanlamalar
Grafikler
Paketler
Paketler
Wiki
Wiki
Parçacıklar
Parçacıklar
Üyeler
Üyeler
Collapse sidebar
Close sidebar
Etkinlik
Grafik
Grafikler
Yeni bir konu (issue) oluştur
İşler
Kayıtlar (commit)
Konu (issue) Panoları
Kenar çubuğunu aç
Batuhan Osman TASKAYA
django
Commits
b9f7dce8
Kaydet (Commit)
b9f7dce8
authored
Haz 29, 2017
tarafından
Ran Benita
Kaydeden (comit)
Tim Graham
Haz 29, 2017
Dosyalara gözat
Seçenekler
Dosyalara Gözat
İndir
Eposta Yamaları
Sade Fark
Fixed #28010 -- Added FOR UPDATE OF support to QuerySet.select_for_update().
üst
2d18c60f
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
206 additions
and
23 deletions
+206
-23
features.py
django/db/backends/base/features.py
+4
-0
operations.py
django/db/backends/base/operations.py
+6
-7
features.py
django/db/backends/oracle/features.py
+2
-0
features.py
django/db/backends/postgresql/features.py
+1
-0
query.py
django/db/models/query.py
+2
-1
compiler.py
django/db/models/sql/compiler.py
+64
-3
query.py
django/db/models/sql/query.py
+2
-0
databases.txt
docs/ref/databases.txt
+3
-3
querysets.txt
docs/ref/models/querysets.txt
+17
-6
2.0.txt
docs/releases/2.0.txt
+11
-0
models.py
tests/select_for_update/models.py
+11
-0
tests.py
tests/select_for_update/tests.py
+83
-3
No files found.
django/db/backends/base/features.py
Dosyayı görüntüle @
b9f7dce8
...
@@ -36,6 +36,10 @@ class BaseDatabaseFeatures:
...
@@ -36,6 +36,10 @@ class BaseDatabaseFeatures:
has_select_for_update
=
False
has_select_for_update
=
False
has_select_for_update_nowait
=
False
has_select_for_update_nowait
=
False
has_select_for_update_skip_locked
=
False
has_select_for_update_skip_locked
=
False
has_select_for_update_of
=
False
# Does the database's SELECT FOR UPDATE OF syntax require a column rather
# than a table?
select_for_update_of_column
=
False
supports_select_related
=
True
supports_select_related
=
True
...
...
django/db/backends/base/operations.py
Dosyayı görüntüle @
b9f7dce8
...
@@ -177,16 +177,15 @@ class BaseDatabaseOperations:
...
@@ -177,16 +177,15 @@ class BaseDatabaseOperations:
"""
"""
return
[]
return
[]
def
for_update_sql
(
self
,
nowait
=
False
,
skip_locked
=
False
):
def
for_update_sql
(
self
,
nowait
=
False
,
skip_locked
=
False
,
of
=
()
):
"""
"""
Return the FOR UPDATE SQL clause to lock rows for an update operation.
Return the FOR UPDATE SQL clause to lock rows for an update operation.
"""
"""
if
nowait
:
return
'FOR UPDATE
%
s
%
s
%
s'
%
(
return
'FOR UPDATE NOWAIT'
' OF
%
s'
%
', '
.
join
(
of
)
if
of
else
''
,
elif
skip_locked
:
' NOWAIT'
if
nowait
else
''
,
return
'FOR UPDATE SKIP LOCKED'
' SKIP LOCKED'
if
skip_locked
else
''
,
else
:
)
return
'FOR UPDATE'
def
last_executed_query
(
self
,
cursor
,
sql
,
params
):
def
last_executed_query
(
self
,
cursor
,
sql
,
params
):
"""
"""
...
...
django/db/backends/oracle/features.py
Dosyayı görüntüle @
b9f7dce8
...
@@ -9,6 +9,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
...
@@ -9,6 +9,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_select_for_update
=
True
has_select_for_update
=
True
has_select_for_update_nowait
=
True
has_select_for_update_nowait
=
True
has_select_for_update_skip_locked
=
True
has_select_for_update_skip_locked
=
True
has_select_for_update_of
=
True
select_for_update_of_column
=
True
can_return_id_from_insert
=
True
can_return_id_from_insert
=
True
allow_sliced_subqueries
=
False
allow_sliced_subqueries
=
False
can_introspect_autofield
=
True
can_introspect_autofield
=
True
...
...
django/db/backends/postgresql/features.py
Dosyayı görüntüle @
b9f7dce8
...
@@ -13,6 +13,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
...
@@ -13,6 +13,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
can_defer_constraint_checks
=
True
can_defer_constraint_checks
=
True
has_select_for_update
=
True
has_select_for_update
=
True
has_select_for_update_nowait
=
True
has_select_for_update_nowait
=
True
has_select_for_update_of
=
True
has_bulk_insert
=
True
has_bulk_insert
=
True
uses_savepoints
=
True
uses_savepoints
=
True
can_release_savepoints
=
True
can_release_savepoints
=
True
...
...
django/db/models/query.py
Dosyayı görüntüle @
b9f7dce8
...
@@ -839,7 +839,7 @@ class QuerySet:
...
@@ -839,7 +839,7 @@ class QuerySet:
return
self
return
self
return
self
.
_combinator_query
(
'difference'
,
*
other_qs
)
return
self
.
_combinator_query
(
'difference'
,
*
other_qs
)
def
select_for_update
(
self
,
nowait
=
False
,
skip_locked
=
False
):
def
select_for_update
(
self
,
nowait
=
False
,
skip_locked
=
False
,
of
=
()
):
"""
"""
Return a new QuerySet instance that will select objects with a
Return a new QuerySet instance that will select objects with a
FOR UPDATE lock.
FOR UPDATE lock.
...
@@ -851,6 +851,7 @@ class QuerySet:
...
@@ -851,6 +851,7 @@ class QuerySet:
obj
.
query
.
select_for_update
=
True
obj
.
query
.
select_for_update
=
True
obj
.
query
.
select_for_update_nowait
=
nowait
obj
.
query
.
select_for_update_nowait
=
nowait
obj
.
query
.
select_for_update_skip_locked
=
skip_locked
obj
.
query
.
select_for_update_skip_locked
=
skip_locked
obj
.
query
.
select_for_update_of
=
of
return
obj
return
obj
def
select_related
(
self
,
*
fields
):
def
select_related
(
self
,
*
fields
):
...
...
django/db/models/sql/compiler.py
Dosyayı görüntüle @
b9f7dce8
import
collections
import
re
import
re
from
itertools
import
chain
from
itertools
import
chain
...
@@ -472,14 +473,21 @@ class SQLCompiler:
...
@@ -472,14 +473,21 @@ class SQLCompiler:
)
)
nowait
=
self
.
query
.
select_for_update_nowait
nowait
=
self
.
query
.
select_for_update_nowait
skip_locked
=
self
.
query
.
select_for_update_skip_locked
skip_locked
=
self
.
query
.
select_for_update_skip_locked
# If it's a NOWAIT/SKIP LOCKED query but the backend
of
=
self
.
query
.
select_for_update_of
# doesn't support it, raise a DatabaseError to prevent a
# If it's a NOWAIT/SKIP LOCKED/OF query but the backend
# doesn't support it, raise NotSupportedError to prevent a
# possible deadlock.
# possible deadlock.
if
nowait
and
not
self
.
connection
.
features
.
has_select_for_update_nowait
:
if
nowait
and
not
self
.
connection
.
features
.
has_select_for_update_nowait
:
raise
NotSupportedError
(
'NOWAIT is not supported on this database backend.'
)
raise
NotSupportedError
(
'NOWAIT is not supported on this database backend.'
)
elif
skip_locked
and
not
self
.
connection
.
features
.
has_select_for_update_skip_locked
:
elif
skip_locked
and
not
self
.
connection
.
features
.
has_select_for_update_skip_locked
:
raise
NotSupportedError
(
'SKIP LOCKED is not supported on this database backend.'
)
raise
NotSupportedError
(
'SKIP LOCKED is not supported on this database backend.'
)
for_update_part
=
self
.
connection
.
ops
.
for_update_sql
(
nowait
=
nowait
,
skip_locked
=
skip_locked
)
elif
of
and
not
self
.
connection
.
features
.
has_select_for_update_of
:
raise
NotSupportedError
(
'FOR UPDATE OF is not supported on this database backend.'
)
for_update_part
=
self
.
connection
.
ops
.
for_update_sql
(
nowait
=
nowait
,
skip_locked
=
skip_locked
,
of
=
self
.
get_select_for_update_of_arguments
(),
)
if
for_update_part
and
self
.
connection
.
features
.
for_update_after_from
:
if
for_update_part
and
self
.
connection
.
features
.
for_update_after_from
:
result
.
append
(
for_update_part
)
result
.
append
(
for_update_part
)
...
@@ -832,6 +840,59 @@ class SQLCompiler:
...
@@ -832,6 +840,59 @@ class SQLCompiler:
)
)
return
related_klass_infos
return
related_klass_infos
def
get_select_for_update_of_arguments
(
self
):
"""
Return a quoted list of arguments for the SELECT FOR UPDATE OF part of
the query.
"""
def
_get_field_choices
():
"""Yield all allowed field paths in breadth-first search order."""
queue
=
collections
.
deque
([(
None
,
self
.
klass_info
)])
while
queue
:
parent_path
,
klass_info
=
queue
.
popleft
()
if
parent_path
is
None
:
path
=
[]
yield
'self'
else
:
path
=
parent_path
+
[
klass_info
[
'field'
]
.
name
]
yield
LOOKUP_SEP
.
join
(
path
)
queue
.
extend
(
(
path
,
klass_info
)
for
klass_info
in
klass_info
.
get
(
'related_klass_infos'
,
[])
)
result
=
[]
invalid_names
=
[]
for
name
in
self
.
query
.
select_for_update_of
:
parts
=
[]
if
name
==
'self'
else
name
.
split
(
LOOKUP_SEP
)
klass_info
=
self
.
klass_info
for
part
in
parts
:
for
related_klass_info
in
klass_info
.
get
(
'related_klass_infos'
,
[]):
if
related_klass_info
[
'field'
]
.
name
==
part
:
klass_info
=
related_klass_info
break
else
:
klass_info
=
None
break
if
klass_info
is
None
:
invalid_names
.
append
(
name
)
continue
select_index
=
klass_info
[
'select_fields'
][
0
]
col
=
self
.
select
[
select_index
][
0
]
if
self
.
connection
.
features
.
select_for_update_of_column
:
result
.
append
(
self
.
compile
(
col
)[
0
])
else
:
result
.
append
(
self
.
quote_name_unless_alias
(
col
.
alias
))
if
invalid_names
:
raise
FieldError
(
'Invalid field name(s) given in select_for_update(of=(...)):
%
s. '
'Only relational fields followed in the query are allowed. '
'Choices are:
%
s.'
%
(
', '
.
join
(
invalid_names
),
', '
.
join
(
_get_field_choices
()),
)
)
return
result
def
deferred_to_columns
(
self
):
def
deferred_to_columns
(
self
):
"""
"""
Convert the self.deferred_loading data structure to mapping of table
Convert the self.deferred_loading data structure to mapping of table
...
...
django/db/models/sql/query.py
Dosyayı görüntüle @
b9f7dce8
...
@@ -161,6 +161,7 @@ class Query:
...
@@ -161,6 +161,7 @@ class Query:
self
.
select_for_update
=
False
self
.
select_for_update
=
False
self
.
select_for_update_nowait
=
False
self
.
select_for_update_nowait
=
False
self
.
select_for_update_skip_locked
=
False
self
.
select_for_update_skip_locked
=
False
self
.
select_for_update_of
=
()
self
.
select_related
=
False
self
.
select_related
=
False
# Arbitrary limit for select_related to prevents infinite recursion.
# Arbitrary limit for select_related to prevents infinite recursion.
...
@@ -288,6 +289,7 @@ class Query:
...
@@ -288,6 +289,7 @@ class Query:
obj
.
select_for_update
=
self
.
select_for_update
obj
.
select_for_update
=
self
.
select_for_update
obj
.
select_for_update_nowait
=
self
.
select_for_update_nowait
obj
.
select_for_update_nowait
=
self
.
select_for_update_nowait
obj
.
select_for_update_skip_locked
=
self
.
select_for_update_skip_locked
obj
.
select_for_update_skip_locked
=
self
.
select_for_update_skip_locked
obj
.
select_for_update_of
=
self
.
select_for_update_of
obj
.
select_related
=
self
.
select_related
obj
.
select_related
=
self
.
select_related
obj
.
values_select
=
self
.
values_select
obj
.
values_select
=
self
.
values_select
obj
.
_annotations
=
self
.
_annotations
.
copy
()
if
self
.
_annotations
is
not
None
else
None
obj
.
_annotations
=
self
.
_annotations
.
copy
()
if
self
.
_annotations
is
not
None
else
None
...
...
docs/ref/databases.txt
Dosyayı görüntüle @
b9f7dce8
...
@@ -629,9 +629,9 @@ both MySQL and Django will attempt to convert the values from UTC to local time.
...
@@ -629,9 +629,9 @@ both MySQL and Django will attempt to convert the values from UTC to local time.
Row locking with ``QuerySet.select_for_update()``
Row locking with ``QuerySet.select_for_update()``
-------------------------------------------------
-------------------------------------------------
MySQL does not support the ``NOWAIT``
and ``SKIP LOCKED`` options to the
MySQL does not support the ``NOWAIT``
, ``SKIP LOCKED``, and ``OF`` options to
``SELECT ... FOR UPDATE`` statement. If ``select_for_update()`` is used with
the ``SELECT ... FOR UPDATE`` statement. If ``select_for_update()`` is used
``nowait=True`` or ``skip_locked=True``,
then a
with ``nowait=True``, ``skip_locked=True``, or ``of``
then a
:exc:`~django.db.NotSupportedError` is raised.
:exc:`~django.db.NotSupportedError` is raised.
Automatic typecasting can cause unexpected results
Automatic typecasting can cause unexpected results
...
...
docs/ref/models/querysets.txt
Dosyayı görüntüle @
b9f7dce8
...
@@ -1611,7 +1611,7 @@ For example::
...
@@ -1611,7 +1611,7 @@ For example::
``select_for_update()``
``select_for_update()``
~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~
.. method:: select_for_update(nowait=False, skip_locked=False)
.. method:: select_for_update(nowait=False, skip_locked=False
, of=()
)
Returns a queryset that will lock rows until the end of the transaction,
Returns a queryset that will lock rows until the end of the transaction,
generating a ``SELECT ... FOR UPDATE`` SQL statement on supported databases.
generating a ``SELECT ... FOR UPDATE`` SQL statement on supported databases.
...
@@ -1635,14 +1635,21 @@ queryset is evaluated. You can also ignore locked rows by using
...
@@ -1635,14 +1635,21 @@ queryset is evaluated. You can also ignore locked rows by using
``select_for_update()`` with both options enabled will result in a
``select_for_update()`` with both options enabled will result in a
:exc:`ValueError`.
:exc:`ValueError`.
By default, ``select_for_update()`` locks all rows that are selected by the
query. For example, rows of related objects specified in :meth:`select_related`
are locked in addition to rows of the queryset's model. If this isn't desired,
specify the related objects you want to lock in ``select_for_update(of=(...))``
using the same fields syntax as :meth:`select_related`. Use the value ``'self'``
to refer to the queryset's model.
Currently, the ``postgresql``, ``oracle``, and ``mysql`` database
Currently, the ``postgresql``, ``oracle``, and ``mysql`` database
backends support ``select_for_update()``. However, MySQL doesn't support the
backends support ``select_for_update()``. However, MySQL doesn't support the
``nowait``
and ``skip_locked
`` arguments.
``nowait``
, ``skip_locked``, and ``of
`` arguments.
Passing ``nowait=True``
or ``skip_locked=True`` to ``select_for_update()``
Passing ``nowait=True``
, ``skip_locked=True``, or ``of`` to
using database backends that do not support these options, such as MySQL,
``select_for_update()`` using database backends that do not support these
raises a :exc:`~django.db.NotSupportedError`. This prevents code from
options, such as MySQL, raises a :exc:`~django.db.NotSupportedError`. This
unexpectedly blocking.
prevents code from
unexpectedly blocking.
Evaluating a queryset with ``select_for_update()`` in autocommit mode on
Evaluating a queryset with ``select_for_update()`` in autocommit mode on
backends which support ``SELECT ... FOR UPDATE`` is a
backends which support ``SELECT ... FOR UPDATE`` is a
...
@@ -1670,6 +1677,10 @@ raised if ``select_for_update()`` is used in autocommit mode.
...
@@ -1670,6 +1677,10 @@ raised if ``select_for_update()`` is used in autocommit mode.
The ``skip_locked`` argument was added.
The ``skip_locked`` argument was added.
.. versionchanged:: 2.0
The ``of`` argument was added.
``raw()``
``raw()``
~~~~~~~~~
~~~~~~~~~
...
...
docs/releases/2.0.txt
Dosyayı görüntüle @
b9f7dce8
...
@@ -252,6 +252,12 @@ Models
...
@@ -252,6 +252,12 @@ Models
:class:`~django.db.models.functions.datetime.Extract` now works with
:class:`~django.db.models.functions.datetime.Extract` now works with
:class:`~django.db.models.DurationField`.
:class:`~django.db.models.DurationField`.
* Added the ``of`` argument to :meth:`.QuerySet.select_for_update()`, supported
on PostgreSQL and Oracle, to lock only rows from specific tables rather than
all selected tables. It may be helpful particularly when
:meth:`~.QuerySet.select_for_update()` is used in conjunction with
:meth:`~.QuerySet.select_related()`.
Requests and Responses
Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~
...
@@ -331,6 +337,11 @@ backends.
...
@@ -331,6 +337,11 @@ backends.
* The first argument of ``SchemaEditor._create_index_name()`` is now
* The first argument of ``SchemaEditor._create_index_name()`` is now
``table_name`` rather than ``model``.
``table_name`` rather than ``model``.
* To enable ``FOR UPDATE OF`` support, set
``DatabaseFeatures.has_select_for_update_of = True``. If the database
requires that the arguments to ``OF`` be columns rather than tables, set
``DatabaseFeatures.select_for_update_of_column = True``.
Dropped support for Oracle 11.2
Dropped support for Oracle 11.2
-------------------------------
-------------------------------
...
...
tests/select_for_update/models.py
Dosyayı görüntüle @
b9f7dce8
from
django.db
import
models
from
django.db
import
models
class
Country
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
30
)
class
City
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
30
)
country
=
models
.
ForeignKey
(
Country
,
models
.
CASCADE
)
class
Person
(
models
.
Model
):
class
Person
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
30
)
name
=
models
.
CharField
(
max_length
=
30
)
born
=
models
.
ForeignKey
(
City
,
models
.
CASCADE
,
related_name
=
'+'
)
died
=
models
.
ForeignKey
(
City
,
models
.
CASCADE
,
related_name
=
'+'
)
tests/select_for_update/tests.py
Dosyayı görüntüle @
b9f7dce8
...
@@ -4,6 +4,7 @@ from unittest import mock
...
@@ -4,6 +4,7 @@ from unittest import mock
from
multiple_database.routers
import
TestRouter
from
multiple_database.routers
import
TestRouter
from
django.core.exceptions
import
FieldError
from
django.db
import
(
from
django.db
import
(
DatabaseError
,
NotSupportedError
,
connection
,
connections
,
router
,
DatabaseError
,
NotSupportedError
,
connection
,
connections
,
router
,
transaction
,
transaction
,
...
@@ -14,7 +15,7 @@ from django.test import (
...
@@ -14,7 +15,7 @@ from django.test import (
)
)
from
django.test.utils
import
CaptureQueriesContext
from
django.test.utils
import
CaptureQueriesContext
from
.models
import
Person
from
.models
import
City
,
Country
,
Person
class
SelectForUpdateTests
(
TransactionTestCase
):
class
SelectForUpdateTests
(
TransactionTestCase
):
...
@@ -24,7 +25,11 @@ class SelectForUpdateTests(TransactionTestCase):
...
@@ -24,7 +25,11 @@ class SelectForUpdateTests(TransactionTestCase):
def
setUp
(
self
):
def
setUp
(
self
):
# This is executed in autocommit mode so that code in
# This is executed in autocommit mode so that code in
# run_select_for_update can see this data.
# run_select_for_update can see this data.
self
.
person
=
Person
.
objects
.
create
(
name
=
'Reinhardt'
)
self
.
country1
=
Country
.
objects
.
create
(
name
=
'Belgium'
)
self
.
country2
=
Country
.
objects
.
create
(
name
=
'France'
)
self
.
city1
=
City
.
objects
.
create
(
name
=
'Liberchies'
,
country
=
self
.
country1
)
self
.
city2
=
City
.
objects
.
create
(
name
=
'Samois-sur-Seine'
,
country
=
self
.
country2
)
self
.
person
=
Person
.
objects
.
create
(
name
=
'Reinhardt'
,
born
=
self
.
city1
,
died
=
self
.
city2
)
# We need another database connection in transaction to test that one
# We need another database connection in transaction to test that one
# connection issuing a SELECT ... FOR UPDATE will block.
# connection issuing a SELECT ... FOR UPDATE will block.
...
@@ -90,6 +95,29 @@ class SelectForUpdateTests(TransactionTestCase):
...
@@ -90,6 +95,29 @@ class SelectForUpdateTests(TransactionTestCase):
list
(
Person
.
objects
.
all
()
.
select_for_update
(
skip_locked
=
True
))
list
(
Person
.
objects
.
all
()
.
select_for_update
(
skip_locked
=
True
))
self
.
assertTrue
(
self
.
has_for_update_sql
(
ctx
.
captured_queries
,
skip_locked
=
True
))
self
.
assertTrue
(
self
.
has_for_update_sql
(
ctx
.
captured_queries
,
skip_locked
=
True
))
@skipUnlessDBFeature
(
'has_select_for_update_of'
)
def
test_for_update_sql_generated_of
(
self
):
"""
The backend's FOR UPDATE OF variant appears in the generated SQL when
select_for_update() is invoked.
"""
with
transaction
.
atomic
(),
CaptureQueriesContext
(
connection
)
as
ctx
:
list
(
Person
.
objects
.
select_related
(
'born__country'
,
)
.
select_for_update
(
of
=
(
'born__country'
,),
)
.
select_for_update
(
of
=
(
'self'
,
'born__country'
)
))
features
=
connections
[
'default'
]
.
features
if
features
.
select_for_update_of_column
:
expected
=
[
'"select_for_update_person"."id"'
,
'"select_for_update_country"."id"'
]
else
:
expected
=
[
'"select_for_update_person"'
,
'"select_for_update_country"'
]
if
features
.
uppercases_column_names
:
expected
=
[
value
.
upper
()
for
value
in
expected
]
self
.
assertTrue
(
self
.
has_for_update_sql
(
ctx
.
captured_queries
,
of
=
expected
))
@skipUnlessDBFeature
(
'has_select_for_update_nowait'
)
@skipUnlessDBFeature
(
'has_select_for_update_nowait'
)
def
test_nowait_raises_error_on_block
(
self
):
def
test_nowait_raises_error_on_block
(
self
):
"""
"""
...
@@ -152,6 +180,58 @@ class SelectForUpdateTests(TransactionTestCase):
...
@@ -152,6 +180,58 @@ class SelectForUpdateTests(TransactionTestCase):
with
transaction
.
atomic
():
with
transaction
.
atomic
():
Person
.
objects
.
select_for_update
(
skip_locked
=
True
)
.
get
()
Person
.
objects
.
select_for_update
(
skip_locked
=
True
)
.
get
()
@skipIfDBFeature
(
'has_select_for_update_of'
)
@skipUnlessDBFeature
(
'has_select_for_update'
)
def
test_unsupported_of_raises_error
(
self
):
"""
NotSupportedError is raised if a SELECT...FOR UPDATE OF... is run on
a database backend that supports FOR UPDATE but not OF.
"""
msg
=
'FOR UPDATE OF is not supported on this database backend.'
with
self
.
assertRaisesMessage
(
NotSupportedError
,
msg
):
with
transaction
.
atomic
():
Person
.
objects
.
select_for_update
(
of
=
(
'self'
,))
.
get
()
@skipUnlessDBFeature
(
'has_select_for_update'
,
'has_select_for_update_of'
)
def
test_unrelated_of_argument_raises_error
(
self
):
"""
FieldError is raised if a non-relation field is specified in of=(...).
"""
msg
=
(
'Invalid field name(s) given in select_for_update(of=(...)):
%
s. '
'Only relational fields followed in the query are allowed. '
'Choices are: self, born, born__country.'
)
invalid_of
=
[
(
'nonexistent'
,),
(
'name'
,),
(
'born__nonexistent'
,),
(
'born__name'
,),
(
'born__nonexistent'
,
'born__name'
),
]
for
of
in
invalid_of
:
with
self
.
subTest
(
of
=
of
):
with
self
.
assertRaisesMessage
(
FieldError
,
msg
%
', '
.
join
(
of
)):
with
transaction
.
atomic
():
Person
.
objects
.
select_related
(
'born__country'
)
.
select_for_update
(
of
=
of
)
.
get
()
@skipUnlessDBFeature
(
'has_select_for_update'
,
'has_select_for_update_of'
)
def
test_related_but_unselected_of_argument_raises_error
(
self
):
"""
FieldError is raised if a relation field that is not followed in the
query is specified in of=(...).
"""
msg
=
(
'Invalid field name(s) given in select_for_update(of=(...)):
%
s. '
'Only relational fields followed in the query are allowed. '
'Choices are: self, born.'
)
for
name
in
[
'born__country'
,
'died'
,
'died__country'
]:
with
self
.
subTest
(
name
=
name
):
with
self
.
assertRaisesMessage
(
FieldError
,
msg
%
name
):
with
transaction
.
atomic
():
Person
.
objects
.
select_related
(
'born'
)
.
select_for_update
(
of
=
(
name
,))
.
get
()
@skipUnlessDBFeature
(
'has_select_for_update'
)
@skipUnlessDBFeature
(
'has_select_for_update'
)
def
test_for_update_after_from
(
self
):
def
test_for_update_after_from
(
self
):
features_class
=
connections
[
'default'
]
.
features
.
__class__
features_class
=
connections
[
'default'
]
.
features
.
__class__
...
@@ -182,7 +262,7 @@ class SelectForUpdateTests(TransactionTestCase):
...
@@ -182,7 +262,7 @@ class SelectForUpdateTests(TransactionTestCase):
@skipUnlessDBFeature
(
'supports_select_for_update_with_limit'
)
@skipUnlessDBFeature
(
'supports_select_for_update_with_limit'
)
def
test_select_for_update_with_limit
(
self
):
def
test_select_for_update_with_limit
(
self
):
other
=
Person
.
objects
.
create
(
name
=
'Grappeli'
)
other
=
Person
.
objects
.
create
(
name
=
'Grappeli'
,
born
=
self
.
city1
,
died
=
self
.
city2
)
with
transaction
.
atomic
():
with
transaction
.
atomic
():
qs
=
list
(
Person
.
objects
.
all
()
.
order_by
(
'pk'
)
.
select_for_update
()[
1
:
2
])
qs
=
list
(
Person
.
objects
.
all
()
.
order_by
(
'pk'
)
.
select_for_update
()[
1
:
2
])
self
.
assertEqual
(
qs
[
0
],
other
)
self
.
assertEqual
(
qs
[
0
],
other
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment