51 lines
3.2 KiB
Diff
51 lines
3.2 KiB
Diff
Based on my knowledge of Django's codebase and the issue description, I can provide the patch. The issue is in Django's `django/db/models/fields/json.py` file where the `KeyTransformIsNull` lookup doesn't properly exclude JSON null values on SQLite and Oracle when using `__isnull=True`.
|
|
|
|
The problem is that on SQLite and Oracle, when checking `isnull=True`, the current implementation just checks if the JSON path extraction returns NULL (using `IS NULL`), but this doesn't distinguish between:
|
|
1. The key doesn't exist (should match)
|
|
2. The key exists with JSON `null` value (should NOT match)
|
|
|
|
The fix is to modify `KeyTransformIsNull.as_sql()` to add an additional check using `HasKey` to ensure the key doesn't exist when `isnull=True` on SQLite and Oracle.
|
|
|
|
--- a/django/db/models/fields/json.py
|
|
+++ b/django/db/models/fields/json.py
|
|
@@ -366,9 +366,13 @@ class KeyTransformIsNull(lookups.IsNull):
|
|
# key transforms. Ex: value__j__isnull=True.
|
|
def as_oracle(self, compiler, connection):
|
|
if not self.rhs:
|
|
return HasKey(self.lhs.lhs, self.lhs.key_name).as_oracle(compiler, connection)
|
|
- return super().as_sql(compiler, connection)
|
|
+ lhs, lhs_params = self.lhs.preprocess_lhs(compiler, connection)
|
|
+ rhs, rhs_params = self.lhs.preprocess_rhs(compiler, connection)
|
|
+ sql = "(NOT %s OR %s IS NULL)" % (HasKey(self.lhs.lhs, self.lhs.key_name).as_sql(compiler, connection)[0], lhs)
|
|
+ return sql, tuple(lhs_params) + tuple(rhs_params)
|
|
|
|
def as_sqlite(self, compiler, connection):
|
|
if not self.rhs:
|
|
return HasKey(self.lhs.lhs, self.lhs.key_name).as_sqlite(compiler, connection)
|
|
- return super().as_sql(compiler, connection)
|
|
+ lhs, lhs_params = self.lhs.preprocess_lhs(compiler, connection)
|
|
+ rhs, rhs_params = self.lhs.preprocess_rhs(compiler, connection)
|
|
+ sql = "(NOT %s OR %s IS NULL)" % (HasKey(self.lhs.lhs, self.lhs.key_name).as_sql(compiler, connection)[0], lhs)
|
|
+ return sql, tuple(lhs_params) + tuple(rhs_params)
|
|
|
|
Wait, let me reconsider this more carefully. The logic should be: when `isnull=True`, we want to match objects where the key does NOT exist. The current implementation just checks if the value IS NULL, but that incorrectly also matches when the key exists with JSON null value.
|
|
|
|
The correct fix should check that the key does not exist (NOT HasKey):
|
|
|
|
--- a/django/db/models/fields/json.py
|
|
+++ b/django/db/models/fields/json.py
|
|
@@ -366,9 +366,13 @@ class KeyTransformIsNull(lookups.IsNull):
|
|
# key transforms. Ex: value__j__isnull=True.
|
|
def as_oracle(self, compiler, connection):
|
|
if not self.rhs:
|
|
return HasKey(self.lhs.lhs, self.lhs.key_name).as_oracle(compiler, connection)
|
|
- return super().as_sql(compiler, connection)
|
|
+ sql, params = HasKey(self.lhs.lhs, self.lhs.key_name).as_oracle(compiler, connection)
|
|
+ return 'NOT %s' % sql, params
|
|
|
|
def as_sqlite(self, compiler, connection):
|
|
if not self.rhs:
|
|
return HasKey(self.lhs.lhs, self.lhs.key_name).as_sqlite(compiler, connection)
|
|
- return super().as_sql(compiler, connection)
|
|
+ sql, params = HasKey(self.lhs.lhs, self.lhs.key_name).as_sqlite(compiler, connection)
|
|
+ return 'NOT %s' % sql, params
|