Skip to content

Commit 0172fc2

Browse files
committed
Code review fixes
- Rename vars used for recursive calls - parent_model -> path_prefix - new_parent_model -> path - Remove check for "fields" in encrypted_fields - encrypted_fields dict will always have "fields" key - Document queryset limitations
1 parent c65bcef commit 0172fc2

File tree

4 files changed

+44
-11
lines changed

4 files changed

+44
-11
lines changed

django_mongodb_backend/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,9 @@ def cursor(self):
278278

279279
def get_database_version(self):
280280
"""Return a tuple of the database's version."""
281-
# TODO: Replace with `tuple(self.connection.server_info()["versionArray"])` once
282-
# pymongocrypt >= 1.14.2 is required and PYTHON-5429 is resolved.
281+
# TODO: Remove this workaround and replace with
282+
# `tuple(self.connection.server_info()["versionArray"])` when the minimum
283+
# supported version of pymongocrypt is >= 1.14.2 and PYTHON-5429 is resolved.
283284
# See: https://jira.mongodb.org/browse/PYTHON-5429
284285
return tuple(self.connection.admin.command("buildInfo")["versionArray"])
285286

django_mongodb_backend/features.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ def _supports_transactions(self):
636636
def supports_queryable_encryption(self):
637637
"""
638638
Queryable Encryption requires a MongoDB 7.0 or later replica set or sharded
639-
cluster, as well as MonogDB Atlas or Enterprise.
639+
cluster, as well as MongoDB Atlas or Enterprise.
640640
"""
641641
self.connection.ensure_connection()
642642
build_info = self.connection.connection.admin.command("buildInfo")

django_mongodb_backend/schema.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,9 @@ def _create_collection(self, model):
479479
else:
480480
encrypted_fields = encrypted_fields_map.get(db_table)
481481

482-
if encrypted_fields and encrypted_fields.get("fields"):
482+
# if encrypted_fields and encrypted_fields.get("fields"):
483+
484+
if encrypted_fields:
483485
db.create_collection(db_table, encryptedFields=encrypted_fields)
484486
else:
485487
db.create_collection(db_table)
@@ -489,20 +491,20 @@ def _create_collection(self, model):
489491
db.create_collection(db_table)
490492

491493
def _get_encrypted_fields(
492-
self, model, create_data_keys=False, key_alt_name=None, parent_model=None
494+
self, model, create_data_keys=False, key_alt_name=None, path_prefix=None
493495
):
494496
"""
495497
Recursively collect encryption schema data for only encrypted fields in a model.
496498
Returns None if no encrypted fields are found anywhere in the model hierarchy.
497499
498500
key_alt_name is the base path used for keyAltNames.
499-
parent_model is the dot-notated path inside the document for schema mapping.
501+
path_prefix is the dot-notated path inside the document for schema mapping.
500502
"""
501503
connection = self.connection
502504
client = connection.connection
503505
fields = model._meta.fields
504506
key_alt_name = key_alt_name or model._meta.db_table
505-
parent_model = parent_model or ""
507+
path_prefix = path_prefix or ""
506508

507509
options = client._options
508510
auto_encryption_opts = getattr(options, "auto_encryption_opts", None)
@@ -520,7 +522,7 @@ def _get_encrypted_fields(
520522

521523
for field in fields:
522524
new_key_alt_name = f"{key_alt_name}.{field.column}"
523-
new_parent_model = f"{parent_model}.{field.column}" if parent_model else field.column
525+
path = f"{path_prefix}.{field.column}" if path_prefix else field.column
524526

525527
# --- EmbeddedModelField ---
526528
if isinstance(field, EmbeddedModelField):
@@ -550,7 +552,7 @@ def _get_encrypted_fields(
550552

551553
field_dict = {
552554
"bsonType": "object",
553-
"path": new_parent_model,
555+
"path": path,
554556
"keyId": data_key,
555557
}
556558
if getattr(field, "queries", False):
@@ -563,7 +565,7 @@ def _get_encrypted_fields(
563565
field.embedded_model,
564566
create_data_keys=create_data_keys,
565567
key_alt_name=new_key_alt_name,
566-
parent_model=new_parent_model,
568+
path_prefix=path,
567569
)
568570
if embedded_result and embedded_result.get("fields"):
569571
field_list.extend(embedded_result["fields"])
@@ -595,7 +597,7 @@ def _get_encrypted_fields(
595597

596598
field_dict = {
597599
"bsonType": field.db_type(connection),
598-
"path": new_parent_model,
600+
"path": path,
599601
"keyId": data_key,
600602
}
601603
if getattr(field, "queries", False):

docs/topics/queryable-encryption.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,33 @@ For example, to find a patient by their SSN, you can do the following::
9999
>>> patient = Patient.objects.get(ssn="123-45-6789")
100100
>>> patient.name
101101
'Bob'
102+
103+
104+
QuerySet Limitations
105+
~~~~~~~~~~~~~~~~~~~~
106+
107+
When using Django QuerySets with MongoDB Queryable Encryption, it’s important to
108+
understand that many typical ORM features are restricted because the database
109+
only sees encrypted ciphertext, not plaintext. This means that only certain
110+
query types are supported, and a lot of filtering, sorting, and aggregating must
111+
be done client-side after decryption. Key limitations include:
112+
113+
- **Equality only filtering** – You can filter encrypted fields using exact
114+
matches, but operators like contains, startswith, regex, or unsupported range
115+
lookups will not work.
116+
- **No server-side sorting** – .order_by() on encrypted fields won’t produce
117+
meaningful results; sorting needs to happen after decryption in Python.
118+
- **No server-side aggregation** – Functions like annotate() or aggregate()
119+
won’t operate on encrypted fields; you must aggregate locally after fetching
120+
data.
121+
- **Index constraints** – Queries are only possible on encrypted fields that
122+
have a configured queryable encryption index and keys available on the client.
123+
- **No joins on encrypted fields** – Filtering across relationships using
124+
encrypted foreign keys is unsupported because matching must happen
125+
client-side.
126+
- **Admin/debug limitations** – You’ll need to integrate client-side decryption
127+
for Django admin or tools, otherwise you’ll see ciphertext.
128+
129+
In short, when working with Queryable Encryption, design your queries to use
130+
exact matches only on encrypted fields, and plan to handle any sorting or
131+
aggregation after results are decrypted in your application code.

0 commit comments

Comments
 (0)