Opened 3 years ago

Closed 3 years ago

#33260 closed Bug (fixed)

Crash when using query set select_for_update(of=(...)) with exists()

Reported by: Hannes Ljungberg Owned by: Hannes Ljungberg
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

For example specifying (self,) as an argument to of:

Person.objects.select_for_update(of=("self",)).exists()

Will crash with the following traceback:

Traceback (most recent call last):
  File "/tests/django/django/test/testcases.py", line 1305, in skip_wrapper
    return test_func(*args, **kwargs)
  File "/tests/django/tests/select_for_update/tests.py", line 599, in test_select_for_update_with_exists
    self.assertIs(Person.objects.select_for_update(of=("self",)).exists(), True)
  File "/tests/django/django/db/models/query.py", line 818, in exists
    return self.query.has_results(using=self.db)
  File "/tests/django/django/db/models/sql/query.py", line 546, in has_results
    return compiler.has_results()
  File "/tests/django/django/db/models/sql/compiler.py", line 1174, in has_results
    return bool(self.execute_sql(SINGLE))
  File "/tests/django/django/db/models/sql/compiler.py", line 1191, in execute_sql
    sql, params = self.as_sql()
  File "/tests/django/django/db/models/sql/compiler.py", line 612, in as_sql
    of=self.get_select_for_update_of_arguments(),
  File "/tests/django/django/db/models/sql/compiler.py", line 1087, in get_select_for_update_of_arguments
    col = _get_first_selected_col_from_model(klass_info)
  File "/tests/django/django/db/models/sql/compiler.py", line 1053, in _get_first_selected_col_from_model
    concrete_model = klass_info['model']._meta.concrete_model
TypeError: 'NoneType' object is not subscriptable

Selecting a related model like:

Person.objects.select_for_update(of=("born",)).exists()

Will crash with the following traceback:

Traceback (most recent call last):
  File "/tests/django/django/test/testcases.py", line 1305, in skip_wrapper
    return test_func(*args, **kwargs)
  File "/tests/django/tests/select_for_update/tests.py", line 599, in test_select_for_update_with_exists
    self.assertIs(Person.objects.select_for_update(of=("born")).exists(), True)
  File "/tests/django/django/db/models/query.py", line 818, in exists
    return self.query.has_results(using=self.db)
  File "/tests/django/django/db/models/sql/query.py", line 546, in has_results
    return compiler.has_results()
  File "/tests/django/django/db/models/sql/compiler.py", line 1174, in has_results
    return bool(self.execute_sql(SINGLE))
  File "/tests/django/django/db/models/sql/compiler.py", line 1191, in execute_sql
    sql, params = self.as_sql()
  File "/tests/django/django/db/models/sql/compiler.py", line 612, in as_sql
    of=self.get_select_for_update_of_arguments(),
  File "/tests/django/django/db/models/sql/compiler.py", line 1091, in get_select_for_update_of_arguments
    *klass_info.get('related_klass_infos', []),
AttributeError: 'NoneType' object has no attribute 'get'

With https://code.djangoproject.com/ticket/32888 in consideration we should probably just drop locking anything as no table columns are selected.

Thinking something like:

diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index d1009847e7..7f6251aa34 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -1081,6 +1081,8 @@ class SQLCompiler:
         invalid_names = []
         for name in self.query.select_for_update_of:
             klass_info = self.klass_info
+            if not klass_info:
+                continue
             if name == 'self':
                 col = _get_first_selected_col_from_model(klass_info)
             else:

Change History (4)

comment:1 by Mariusz Felisiak, 3 years ago

Triage Stage: UnreviewedAccepted

Thanks for the report. We can add an early return even before the loop:

if not self.klass_info:
    return []
result = []
invalid_names = []
for name in ...

comment:2 by Hannes Ljungberg, 3 years ago

Has patch: set

comment:3 by Mariusz Felisiak, 3 years ago

Triage Stage: AcceptedReady for checkin

comment:4 by Mariusz Felisiak <felisiak.mariusz@…>, 3 years ago

Resolution: fixed
Status: assignedclosed

In 25157033:

Fixed #33260 -- Fixed crash when chaining QuerySet.exists() after select_for_update(of=()).

Note: See TracTickets for help on using tickets.
Back to Top