Skip to content

Commit 9daf1e4

Browse files
committed
Do not special-case JSONB contains for scalar keys.
Previously, passing a string/int/bool to BinaryJSONField.contains() would silently switch to using the EXISTS (?) operator to test for a key. This is somewhat bad behavior (#2984) and so the special-case is removed. For the old behavior of testing for presence of a key/item at the top-level, you should use the .has_key() method instead.
1 parent fff2edd commit 9daf1e4

File tree

2 files changed

+16
-15
lines changed

2 files changed

+16
-15
lines changed

playhouse/postgres_ext.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ def concat(self, rhs):
8585
return Expression(self.as_json(True), OP.CONCAT, rhs)
8686

8787
def contains(self, other):
88-
clone = self.as_json(True)
89-
if isinstance(other, (list, dict)):
90-
return Expression(clone, JSONB_CONTAINS, Json(other))
91-
return Expression(clone, JSONB_EXISTS, other)
88+
return Expression(self.as_json(True), JSONB_CONTAINS, Json(other))
89+
90+
def contained_by(self, other):
91+
return Expression(self.as_json(True), JSONB_CONTAINED_BY, Json(other))
9292

9393
def contains_any(self, *keys):
9494
return Expression(
@@ -332,11 +332,9 @@ class BinaryJSONField(IndexedFieldMixin, JSONField):
332332
__hash__ = Field.__hash__
333333

334334
def contains(self, other):
335-
if isinstance(other, (list, dict)):
336-
return Expression(self, JSONB_CONTAINS, Json(other))
337-
elif isinstance(other, JSONField):
335+
if isinstance(other, JSONField):
338336
return Expression(self, JSONB_CONTAINS, other)
339-
return Expression(cast_jsonb(self), JSONB_EXISTS, other)
337+
return Expression(self, JSONB_CONTAINS, Json(other))
340338

341339
def contained_by(self, other):
342340
return Expression(cast_jsonb(self), JSONB_CONTAINED_BY, Json(other))

tests/postgres_helpers.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def test_subscript_contains(self):
207207

208208
# 'k3' is mapped to another dictioary {'k4': [...]}. Therefore,
209209
# 'k3' is said to contain 'k4', but *not* ['k4'] or ['k4', 'k5'].
210-
self.assertObjects(D['k3'].contains('k4'), 0)
210+
self.assertObjects(D['k3'].has_key('k4'), 0)
211211
self.assertObjects(D['k3'].contains(['k4']))
212212
self.assertObjects(D['k3'].contains(['k4', 'k5']))
213213

@@ -235,7 +235,8 @@ def test_subscript_contains(self):
235235
self.assertObjects(D['k4'].contains_all('i1', 'i2'), 2)
236236

237237
# Check array indexes.
238-
self.assertObjects(D[2].contains('a3'), 1)
238+
self.assertObjects(D[2].has_key('a3'), 1)
239+
self.assertObjects(D[2].contains('a3'))
239240
self.assertObjects(D[0].contains('a1'), 1)
240241
self.assertObjects(D[0].contains('k1'))
241242

@@ -244,9 +245,10 @@ def test_contains(self):
244245
D = self.M.data
245246

246247
# Test for keys. 'k4' is both an object key and an array element.
247-
self.assertObjects(D.contains('k4'), 2, 5)
248-
self.assertObjects(D.contains('a1'), 1, 2)
249-
self.assertObjects(D.contains('k3'), 0)
248+
self.assertObjects(D.has_key('k4'), 2, 5)
249+
self.assertObjects(D.has_key('a1'), 1, 2)
250+
self.assertObjects(D.contains('a1'), 1)
251+
self.assertObjects(D.has_key('k3'), 0)
250252

251253
# We can test for multiple top-level keys/indexes.
252254
self.assertObjects(D.contains_all('a1', 'a2'), 1, 2)
@@ -265,8 +267,9 @@ def test_contains(self):
265267
self.assertObjects(D.contains([{'a3': 'a4'}]), 1)
266268

267269
# Check for simple keys.
268-
self.assertObjects(D.contains('a1'), 1, 2)
269-
self.assertObjects(D.contains('k3'), 0)
270+
self.assertObjects(D.contains(['a1']), 1)
271+
self.assertObjects(D.contains('a1'), 1)
272+
self.assertObjects(D.contains('k3'))
270273

271274
# Contains any.
272275
self.assertObjects(D.contains_any('a1', 'k1'), 0, 1, 2, 5)

0 commit comments

Comments
 (0)