Flags (BitFields)¶
There are many different Global Satellite Navigation Systems (GNSS) in operation today:
GNSS receivers may understand or be configured to track one or more of these systems. If we wanted to build a data model of a GNSS receiver we would want to know which systems it can track. In Django we might do this using a collection of boolean fields like this:
from django.db import models
class GNSSReceiverBasic(models.Model):
gps = models.BooleanField(default=False)
glonass = models.BooleanField(default=False)
galileo = models.BooleanField(default=False)
beidou = models.BooleanField(default=False)
qzss = models.BooleanField(default=False)
irnss = models.BooleanField(default=False)
sbas = models.BooleanField(default=False)
class Meta:
# we can create an index for all fields, which will speed up queries for
# exact matches on these fields
indexes = [
models.Index(fields=[
'gps',
'glonass',
'galileo',
'beidou',
'qzss',
'irnss',
'sbas'
])
]
Which would allow us to check for receiver compatibility and filter requirements like this:
receiver1 = GNSSReceiverBasic.objects.create(
gps=True,
glonass=True
)
receiver2 = GNSSReceiverBasic.objects.create(
gps=True,
glonass=True,
galileo=True,
beidou=True
)
# check for GPS and BEIDOU
assert not (receiver1.gps and receiver1.beidou)
assert receiver2.gps and receiver2.beidou
# get all receives that have at least GPS and BEIDOU
qry = GNSSReceiverBasic.objects.filter(Q(gps=True) & Q(beidou=True))
assert receiver1 not in qry
assert receiver2 in qry
# get all receivers that have either GPS or BEIDOU
qry = GNSSReceiverBasic.objects.filter(Q(gps=True) | Q(beidou=True))
assert receiver1 in qry
assert receiver2 in qry
This works pretty well. As our data scales though the waste of using an entire column for each boolean can add up. We can do better by using a single column as a bit field.
Python has a built-in enum.IntFlag
type that is used to represent bit fields. Bit fields
are useful for storing multiple boolean values in a single column. This is much more space
efficient. EnumField
supports enum.IntFlag
types out of the box. We could rewrite our GNSS receiver model like this:
from django.db import models
from enum import IntFlag
from django_enum import EnumField
class Constellation(IntFlag):
# fmt: off
GPS = 1 << 0 # 1
GLONASS = 1 << 1 # 2
GALILEO = 1 << 2 # 4
BEIDOU = 1 << 3 # 8
QZSS = 1 << 4 # 16
IRNSS = 1 << 5 # 32
# fmt: on
class GNSSReceiver(models.Model):
constellations = EnumField(Constellation, db_index=True)
And use it like this:
receiver1 = GNSSReceiver.objects.create(
constellations=Constellation.GPS | Constellation.GLONASS
)
receiver2 = GNSSReceiver.objects.create(
constellations=(
Constellation.GPS |
Constellation.GLONASS |
Constellation.GALILEO |
Constellation.BEIDOU
)
)
wanted = Constellation.GPS | Constellation.BEIDOU
# check for GPS and BEIDOU
assert not (
Constellation.GPS in receiver1.constellations and
Constellation.BEIDOU in receiver1.constellations
)
assert (
Constellation.GPS in receiver2.constellations and
Constellation.BEIDOU in receiver2.constellations
)
# we can treat IntFlags like bit masks so we can also check for having at
# least GPS and BEIDOU like this:
assert not wanted & receiver1.constellations == wanted
assert wanted & receiver2.constellations == wanted
# get all receives that have at least GPS and BEIDOU
qry = GNSSReceiver.objects.filter(constellations__has_all=wanted)
assert receiver1 not in qry
assert receiver2 in qry
# get all receivers that have either GPS or BEIDOU
qry = GNSSReceiver.objects.filter(constellations__has_any=wanted)
assert receiver1 in qry
assert receiver2 in qry
The bit field model is much more compact and it does better in
space efficiency and query performance. We also get some additional lookup
types for EnumField
that represent flags: has_all and
has_any.
The defined indexes are not necessarily used in our examples. They may be partially engaged for the has_all lookups but not for has_any. Our flag lookups perform optimized bit mask operations in the database. The bit field example will out perform the boolean example because it consumes much less memory while doing table scans with particular improvements in hard to index has_any queries.