#24912 closed Bug (fixed)
prefetch_related fails with sqlite3 database and UUIDField primary keys
| Reported by: | brki | Owned by: | brki |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Release blocker | Keywords: | UUIDField |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Here's a test.py that fails on sqlite3, but succeeds if run on postgresql:
import uuid
from django.db import models
from django.test import TestCase
class Award(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=20)
class Player(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=20)
awards = models.ManyToManyField(Award)
class UUIDSqliteDemo(TestCase):
def setUp(self):
awards = [
Award.objects.create(name='star'),
Award.objects.create(name='trophy'),
]
player = Player.objects.create(name='Beth')
player.awards.add(*awards)
self.player_id = player.id
# This test passes if the target database is Postgresql, but fails for sqlite3:
def test_prefetch_related(self):
players = list(Player.objects.filter(id=self.player_id).prefetch_related('awards'))
player = players[0]
# For sqlite3, this fails with "2 != 0":
self.assertEqual(2, len(player.awards.all()))
I also tried using a CharField instead of a UUIDField as the pk field; that worked fine. So it seems to be a problem with the UUIDField and sqlite3 (or perhaps all non-Postgresql dbs?).
Change History (16)
comment:1 by , 10 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|---|
| Version: | 1.8 → master |
comment:2 by , 10 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
comment:4 by , 10 years ago
I'm seeing this in the db logs:
>>> players = Player.objects.filter(id=self.player_id)
>>> player_ = players.get()
>>> self.assertEqual(2, player_.awards.count())
(0.000) QUERY = 'SELECT "model_fields_player"."id", "model_fields_player"."name" FROM "model_fields_player" WHERE "model_fields_player"."id" = %s' - PARAMS = ('01e6706e12f6465387abaeb1cf0c6389',); args=('01e6706e12f6465387abaeb1cf0c6389',)
(0.000) QUERY = 'SELECT COUNT(%s) AS "__count" FROM "model_fields_award" INNER JOIN "model_fields_player_awards" ON ( "model_fields_award"."id" = "model_fields_player_awards"."award_id" ) WHERE "model_fields_player_awards"."player_id" = %s' - PARAMS = ('*', '01e6706e12f6465387abaeb1cf0c6389'); args=('*', '01e6706e12f6465387abaeb1cf0c6389')
>>> players = players.prefetch_related('awards')
>>> player = players.get()
>>> # For sqlite3, this fails with "2 != 0":
>>> self.assertEqual(2, player.awards.count())
(0.000) QUERY = 'SELECT "model_fields_player"."id", "model_fields_player"."name" FROM "model_fields_player" WHERE "model_fields_player"."id" = %s' - PARAMS = ('01e6706e12f6465387abaeb1cf0c6389',); args=('01e6706e12f6465387abaeb1cf0c6389',)
(0.000) QUERY = 'SELECT ("model_fields_player_awards"."player_id") AS "_prefetch_related_val_player_id", "model_fields_award"."id", "model_fields_award"."name" FROM "model_fields_award" INNER JOIN "model_fields_player_awards" ON ( "model_fields_award"."id" = "model_fields_player_awards"."award_id" ) WHERE "model_fields_player_awards"."player_id" IN (%s)' - PARAMS = ('01e6706e12f6465387abaeb1cf0c6389',); args=('01e6706e12f6465387abaeb1cf0c6389',)
comment:5 by , 10 years ago
It still fails using len():
>>> player_ = players.get()
>>> self.assertEqual(2, len(player_.awards.all()))
(0.000) QUERY = 'SELECT "model_fields_player"."id", "model_fields_player"."name" FROM "model_fields_player" WHERE "model_fields_player"."id" = %s' - PARAMS = ('f48b661fe6b1442f88098656204f3abf',); args=('f48b661fe6b1442f88098656204f3abf',)
(0.000) QUERY = 'SELECT "model_fields_award"."id", "model_fields_award"."name" FROM "model_fields_award" INNER JOIN "model_fields_player_awards" ON ( "model_fields_award"."id" = "model_fields_player_awards"."award_id" ) WHERE "model_fields_player_awards"."player_id" = %s' - PARAMS = ('f48b661fe6b1442f88098656204f3abf',); args=('f48b661fe6b1442f88098656204f3abf',)
>>> players = players.prefetch_related('awards')
>>> player = players.get()
>>> # For sqlite3, this fails with "2 != 0":
>>> self.assertEqual(2, len(player.awards.all()))
(0.000) QUERY = 'SELECT "model_fields_player"."id", "model_fields_player"."name" FROM "model_fields_player" WHERE "model_fields_player"."id" = %s' - PARAMS = ('f48b661fe6b1442f88098656204f3abf',); args=('f48b661fe6b1442f88098656204f3abf',)
(0.000) QUERY = 'SELECT ("model_fields_player_awards"."player_id") AS "_prefetch_related_val_player_id", "model_fields_award"."id", "model_fields_award"."name" FROM "model_fields_award" INNER JOIN "model_fields_player_awards" ON ( "model_fields_award"."id" = "model_fields_player_awards"."award_id" ) WHERE "model_fields_player_awards"."player_id" IN (%s)' - PARAMS = ('f48b661fe6b1442f88098656204f3abf',); args=('f48b661fe6b1442f88098656204f3abf',)
comment:6 by , 10 years ago
Clearing the prefetch_related behaviour with players.prefetch_related(None), even after the evaluation, makes the test pass.
comment:7 by , 10 years ago
Not sure what happened, I thought I'd posted this here already:
This seems to fix the issue for me, what do you think:
https://github.com/brki/django/commit/deec2487609c211f2225bf3b415a574c540bea7e
I've adapted the test a bit locally, too:
def test_prefetch_related(self):
with self.assertNumQueries(2):
players = list(Player.objects.filter(id=self.player_id).prefetch_related('awards'))
with self.assertNumQueries(0):
player = players[0]
self.assertEqual(2, len(player.awards.all()))
comment:9 by , 10 years ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
comment:10 by , 10 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
There's a pull request open with a fix for this: https://github.com/django/django/pull/4782.
It was failing before with at least sqlite and mysql.
The fix was tested on sqlite, mysql and postgresql.
comment:11 by , 10 years ago
| Has patch: | set |
|---|---|
| Patch needs improvement: | set |
| Triage Stage: | Ready for checkin → Accepted |
Don't mark your own patch as RFC. Simply uncheck Patch needs improvement once you've addressed the comments on the PR.
comment:12 by , 10 years ago
| Patch needs improvement: | unset |
|---|
New pull request: https://github.com/django/django/pull/4799
comment:13 by , 10 years ago
| Patch needs improvement: | set |
|---|---|
| Severity: | Normal → Release blocker |
Added some comments for improvement on the PR.
I can reproduce the problem in sqlite.