Skip to content

Instantly share code, notes, and snippets.

@MakersF
Last active December 17, 2015 06:04
Show Gist options
  • Save MakersF/9ce7002f1ce570e14bc0 to your computer and use it in GitHub Desktop.
Save MakersF/9ce7002f1ce570e14bc0 to your computer and use it in GitHub Desktop.
Example of (incomplete) implementation of reverse IN, or better the SQL for "value IN (column, colum, column, ..)". Basically working, but it's clumsy to use a filed to trigger the lookup and then ignore it, but still having the constraint of it being the same type of the querried fields
from django.db import models
from django.db.models import IntegerField, ForeignKey, SmallIntegerField
from mysite.db.ReverseIn import ReverseIn
models.Field.register_lookup(ReverseIn)
class TableB(models.Model):
id = SmallIntegerField(primary_key=True)
fieldB_A = SmallIntegerField()
class TableA(models.Model):
id = CharField(max_length=30, primary_key=True)
foreign1 = ForeignKey(TableB, related_name="neverused1") #indexed
foreign2 = ForeignKey(TableB, related_name="neverused2") #indexed
foreign3 = ForeignKey(TableB, related_name="neverused3") #indexed
fieldA_A = IntegerField()
>>> from teamcomp.models import TableA as ta
>>> from django.db.models import F
>>> q = ta.objects.filter(fieldA_A__revin=(1, (F("foreign1"),))).values("id")[:1]
>>> str(q.query)
'SELECT "TableA"."id" FROM "TableA" WHERE 1 IN ( ("TableA"."foreign1") ) LIMIT 1'
>>> q = ta.objects.filter(games__revin=(1, (F("foreign1"),F("foreign2"),F("foreign3")))).values("id")[:1]
>>> str(q.query)
'SELECT "TableA"."id" FROM "TableA"
WHERE 1 IN ( ("TableA"."foreign1"), ("TableA"."foreign2"), ("TableA"."foreign3") )
LIMIT 1'
>>> q = ta.objects.filter(games__revin=(641, (F("foreign1"),F("foreign2"),F("foreign3"))))\
.filter(games__revin=(320, (F("foreign1"),F("foreign2"),F("foreign3")))).values("id")[:1]
>>> str(q.query)
'SELECT "TableA"."id" FROM "TableA"
WHERE (641 IN ( ("TableA"."foreign1"), ("TableA"."foreign2"), ("TableA"."foreign3") )
AND 320 IN ( ("TableA"."foreign1"), ("TableA"."foreign2"), ("TableA"."foreign3") ))
LIMIT 1'
class ReverseIn(lookups.Lookup):
"""
Usage:
QuerySet.filter(columnA__revin=(value, (columnB, columnC, ...)))
Produces sql:
value in (columnB, columnC, ...) # NOTE: columnA is not put in the list, it's only used to validate the value
"""
lookup_name = 'revin'
def get_prep_lookup(self):
if not hasattr(self.rhs, '__getitem__'):
raise ValueError('ReverseIN only works with indexable objects. Got {} of type {}.'.format(self.rhs),type(self.rhs))
if not hasattr(self.rhs[1], '__iter__'):
raise ValueError('The item at index 1 of ReverseIn argument must support iteration. Got {} of type {}.'.format(self.rhs[1]),type(self.rhs[1]))
self._lhs_real = self.rhs[0]
self._rhs_real = self.rhs[1]
self.rhs = self.rhs[0]
return super().get_prep_lookup()
def process_value_as_rhs(self, value, compiler, connection):
old_rhs = self.rhs
try:
if isinstance(value, F):
value = value.resolve_expression(compiler.query)
self.rhs = value
return self.process_rhs(compiler, connection)
finally:
self.rhs = old_rhs
def as_sql(self, compiler, connection):
_lhs = self._lhs_real
_rhs = self._rhs_real
# need to process _lhs as if it was a rhs
lhs, lhs_params = self.process_value_as_rhs(_lhs, compiler, connection)
rhss, rhs_paramss = [], []
for rhs in _rhs:
# again we preccess this as rhs
sql, par = self.process_value_as_rhs(rhs, compiler, connection)
rhss.append(sql)
if par:
rhs_paramss.extend(par)
rhs_sql = ", ".join(rhss)
return "{} IN ( {} )".format(lhs, rhs_sql), lhs_params + rhs_paramss
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment