From 02447fb133b53ec7d0ff068cc08f06fdf8817ef7 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 28 Jul 2020 11:54:01 +0200 Subject: [PATCH] Fixed #31835 -- Dropped support for JSONField __contains lookup on Oracle. The current implementation works only for basic examples without supporting nested structures and doesn't follow "the general principle that the contained object must match the containing object as to structure and data contents, possibly after discarding some non-matching array elements or object key/value pairs from the containing object". --- django/db/models/fields/json.py | 19 +------------------ docs/topics/db/queries.txt | 4 ++++ tests/model_fields/test_jsonfield.py | 19 +++++++++++++++++-- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/django/db/models/fields/json.py b/django/db/models/fields/json.py index edc5441799..b82c6a82e2 100644 --- a/django/db/models/fields/json.py +++ b/django/db/models/fields/json.py @@ -146,24 +146,7 @@ class DataContains(PostgresOperatorLookup): return 'JSON_CONTAINS(%s, %s)' % (lhs, rhs), params def as_oracle(self, compiler, connection): - if isinstance(self.rhs, KeyTransform): - return HasKey(self.lhs, self.rhs).as_oracle(compiler, connection) - lhs, lhs_params = self.process_lhs(compiler, connection) - params = tuple(lhs_params) - sql = ( - "JSON_QUERY(%s, '$%s' WITH WRAPPER) = " - "JSON_QUERY('%s', '$.value' WITH WRAPPER)" - ) - rhs = json.loads(self.rhs) - if isinstance(rhs, dict): - if not rhs: - return "DBMS_LOB.SUBSTR(%s) LIKE '{%%%%}'" % lhs, params - return ' AND '.join([ - sql % ( - lhs, '.%s' % json.dumps(key), json.dumps({'value': value}), - ) for key, value in rhs.items() - ]), params - return sql % (lhs, '', json.dumps({'value': rhs})), params + raise NotSupportedError('contains lookup is not supported on Oracle.') class ContainedBy(PostgresOperatorLookup): diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index bf154b658d..793c0b48ad 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -960,6 +960,10 @@ contained in the top-level of the field. For example:: >>> Dog.objects.filter(data__contains={'breed': 'collie'}) ]> +.. admonition:: Oracle + + ``contains`` is not supported on Oracle. + .. fieldlookup:: jsonfield.contained_by ``contained_by`` diff --git a/tests/model_fields/test_jsonfield.py b/tests/model_fields/test_jsonfield.py index bc0a89fd7d..9a9e1a1286 100644 --- a/tests/model_fields/test_jsonfield.py +++ b/tests/model_fields/test_jsonfield.py @@ -441,6 +441,10 @@ class TestQuerying(TestCase): [self.objs[3], self.objs[4], self.objs[6]], ) + @skipIf( + connection.vendor == 'oracle', + "Oracle doesn't support contains lookup.", + ) def test_contains(self): tests = [ ({}, self.objs[2:5] + self.objs[6:8]), @@ -456,6 +460,17 @@ class TestQuerying(TestCase): qs = NullableJSONModel.objects.filter(value__contains=value) self.assertSequenceEqual(qs, expected) + @skipUnless( + connection.vendor == 'oracle', + "Oracle doesn't support contains lookup.", + ) + def test_contains_unsupported(self): + msg = 'contains lookup is not supported on Oracle.' + with self.assertRaisesMessage(NotSupportedError, msg): + NullableJSONModel.objects.filter( + value__contains={'baz': {'a': 'b', 'c': 'd'}}, + ).get() + @skipUnlessDBFeature('supports_primitives_in_json_field') def test_contains_primitives(self): for value in self.primitives: @@ -662,12 +677,12 @@ class TestQuerying(TestCase): ('value__baz__has_key', 'c'), ('value__baz__has_keys', ['a', 'c']), ('value__baz__has_any_keys', ['a', 'x']), - ('value__contains', KeyTransform('bax', 'value')), ('value__has_key', KeyTextTransform('foo', 'value')), ) - # contained_by lookup is not supported on Oracle. + # contained_by and contains lookups are not supported on Oracle. if connection.vendor != 'oracle': tests += ( + ('value__contains', KeyTransform('bax', 'value')), ('value__baz__contained_by', {'a': 'b', 'c': 'd', 'e': 'f'}), ( 'value__contained_by',