Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use exists subquery to find deleted model instance pks #791

Merged
merged 2 commits into from
Jun 3, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 15 additions & 34 deletions reversion/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from django.core.serializers.base import DeserializationError
from django.db import IntegrityError, connections, models, router, transaction
from django.db.models.deletion import Collector
from django.db.models.expressions import RawSQL
from django.db.models.functions import Cast
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.functional import cached_property
from django.utils.translation import ugettext
Expand Down Expand Up @@ -114,12 +114,6 @@ class Meta:
ordering = ("-pk",)


class SubquerySQL(RawSQL):

def as_sql(self, compiler, connection):
return self.sql, self.params


class VersionQuerySet(models.QuerySet):

def get_for_model(self, model, model_db=None):
Expand All @@ -139,33 +133,22 @@ def get_for_object(self, obj, model_db=None):
return self.get_for_object_reference(obj.__class__, obj.pk, model_db=model_db)

def get_deleted(self, model, model_db=None):
# Try to do a faster JOIN.
model_db = model_db or router.db_for_write(model)
connection = connections[self.db]
if self.db == model_db and connection.vendor in ("sqlite", "postgresql", "oracle"):
content_type = _get_content_type(model, self.db)
subquery = SubquerySQL(
"""
SELECT MAX(V.{id})
FROM {version} V
LEFT JOIN {model} ON V.{object_id} = CAST({model}.{model_id} as {str})
WHERE
V.{db} = %s AND
V.{content_type_id} = %s AND
{model}.{model_id} IS NULL
GROUP BY V.{object_id}
""".format(
id=connection.ops.quote_name("id"),
version=connection.ops.quote_name(Version._meta.db_table),
model=connection.ops.quote_name(model._meta.db_table),
model_id=connection.ops.quote_name(model._meta.pk.db_column or model._meta.pk.attname),
object_id=connection.ops.quote_name("object_id"),
str=Version._meta.get_field("object_id").db_type(connection),
db=connection.ops.quote_name("db"),
content_type_id=connection.ops.quote_name("content_type_id"),
),
(model_db, content_type.id),
output_field=Version._meta.pk,
model_qs = (
model._default_manager
.using(model_db)
.annotate(_pk_to_object_id=Cast("pk", Version._meta.get_field("object_id")))
.filter(_pk_to_object_id=models.OuterRef("object_id"))
)
subquery = (
self.get_for_model(model, model_db=model_db)
.annotate(pk_not_exists=~models.Exists(model_qs))
.filter(pk_not_exists=True)
.values("object_id")
.annotate(latest_pk=models.Max("pk"))
.values("latest_pk")
)
else:
# We have to use a slow subquery.
Expand All @@ -177,9 +160,7 @@ def get_deleted(self, model, model_db=None):
latest_pk=models.Max("pk")
).order_by().values_list("latest_pk", flat=True)
# Perform the subquery.
return self.filter(
pk__in=subquery,
)
return self.filter(pk__in=subquery)

def get_unique(self):
last_key = None
Expand Down