Browse Source

Refs #36148 -- Relied on a feature switch to define tuple lookups support.

This should allow backends more easily opt-in or out of native support and rely
on the fallback if unavailable.
Simon Charette 1 tháng trước cách đây
mục cha
commit
a0a765ddeb

+ 3 - 0
django/db/backends/base/features.py

@@ -368,6 +368,9 @@ class BaseDatabaseFeatures:
     # Does the backend support unlimited character columns?
     supports_unlimited_charfield = False
 
+    # Does the backend support native tuple lookups (=, >, <, IN)?
+    supports_tuple_lookups = True
+
     # Collation names for use by the Django test suite.
     test_collations = {
         "ci": None,  # Case-insensitive.

+ 1 - 0
django/db/backends/oracle/features.py

@@ -80,6 +80,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     allows_multiple_constraints_on_same_fields = False
     supports_json_field_contains = False
     supports_collation_on_textfield = False
+    supports_tuple_lookups = False
     test_now_utc_template = "CURRENT_TIMESTAMP AT TIME ZONE 'UTC'"
     django_test_expected_failures = {
         # A bug in Django/oracledb with respect to string handling (#23843).

+ 1 - 0
django/db/backends/sqlite3/features.py

@@ -61,6 +61,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     insert_test_table_with_defaults = 'INSERT INTO {} ("null") VALUES (1)'
     supports_default_keyword_in_insert = False
     supports_unlimited_charfield = True
+    supports_tuple_lookups = False
 
     @cached_property
     def django_test_skips(self):

+ 29 - 20
django/db/models/fields/tuple_lookups.py

@@ -96,9 +96,20 @@ class TupleLookupMixin:
                 )
             return "(%s)" % sql, params
 
+    def get_fallback_sql(self, compiler, connection):
+        raise NotImplementedError(
+            f"{self.__class__.__name__}.get_fallback_sql() must be implemented "
+            f"for backends that don't have the supports_tuple_lookups feature enabled."
+        )
+
+    def as_sql(self, compiler, connection):
+        if not connection.features.supports_tuple_lookups:
+            return self.get_fallback_sql(compiler, connection)
+        return super().as_sql(compiler, connection)
+
 
 class TupleExact(TupleLookupMixin, Exact):
-    def as_oracle(self, compiler, connection):
+    def get_fallback_sql(self, compiler, connection):
         # Process right-hand-side to trigger sanitization.
         self.process_rhs(compiler, connection)
         # e.g.: (a, b, c) == (x, y, z) as SQL:
@@ -132,7 +143,7 @@ class TupleIsNull(TupleLookupMixin, IsNull):
 
 
 class TupleGreaterThan(TupleLookupMixin, GreaterThan):
-    def as_oracle(self, compiler, connection):
+    def get_fallback_sql(self, compiler, connection):
         # Process right-hand-side to trigger sanitization.
         self.process_rhs(compiler, connection)
         # e.g.: (a, b, c) > (x, y, z) as SQL:
@@ -160,7 +171,7 @@ class TupleGreaterThan(TupleLookupMixin, GreaterThan):
 
 
 class TupleGreaterThanOrEqual(TupleLookupMixin, GreaterThanOrEqual):
-    def as_oracle(self, compiler, connection):
+    def get_fallback_sql(self, compiler, connection):
         # Process right-hand-side to trigger sanitization.
         self.process_rhs(compiler, connection)
         # e.g.: (a, b, c) >= (x, y, z) as SQL:
@@ -188,7 +199,7 @@ class TupleGreaterThanOrEqual(TupleLookupMixin, GreaterThanOrEqual):
 
 
 class TupleLessThan(TupleLookupMixin, LessThan):
-    def as_oracle(self, compiler, connection):
+    def get_fallback_sql(self, compiler, connection):
         # Process right-hand-side to trigger sanitization.
         self.process_rhs(compiler, connection)
         # e.g.: (a, b, c) < (x, y, z) as SQL:
@@ -216,7 +227,7 @@ class TupleLessThan(TupleLookupMixin, LessThan):
 
 
 class TupleLessThanOrEqual(TupleLookupMixin, LessThanOrEqual):
-    def as_oracle(self, compiler, connection):
+    def get_fallback_sql(self, compiler, connection):
         # Process right-hand-side to trigger sanitization.
         self.process_rhs(compiler, connection)
         # e.g.: (a, b, c) <= (x, y, z) as SQL:
@@ -315,17 +326,19 @@ class TupleIn(TupleLookupMixin, In):
 
         return compiler.compile(Tuple(*result))
 
-    def as_sql(self, compiler, connection):
-        if not self.rhs_is_direct_value():
-            return self.as_subquery(compiler, connection)
-        return super().as_sql(compiler, connection)
+    def as_subquery_sql(self, compiler, connection):
+        lhs = self.lhs
+        rhs = self.rhs
+        if isinstance(lhs, ColPairs):
+            rhs = rhs.clone()
+            rhs.set_values([source.name for source in lhs.sources])
+            lhs = Tuple(lhs)
+        return compiler.compile(In(lhs, rhs))
 
-    def as_sqlite(self, compiler, connection):
+    def get_fallback_sql(self, compiler, connection):
         rhs = self.rhs
         if not rhs:
             raise EmptyResultSet
-        if not self.rhs_is_direct_value():
-            return self.as_subquery(compiler, connection)
 
         # e.g.: (a, b, c) in [(x1, y1, z1), (x2, y2, z2)] as SQL:
         # WHERE (a = x1 AND b = y1 AND c = z1) OR (a = x2 AND b = y2 AND c = z2)
@@ -338,14 +351,10 @@ class TupleIn(TupleLookupMixin, In):
 
         return root.as_sql(compiler, connection)
 
-    def as_subquery(self, compiler, connection):
-        lhs = self.lhs
-        rhs = self.rhs
-        if isinstance(lhs, ColPairs):
-            rhs = rhs.clone()
-            rhs.set_values([source.name for source in lhs.sources])
-            lhs = Tuple(lhs)
-        return compiler.compile(In(lhs, rhs))
+    def as_sql(self, compiler, connection):
+        if not self.rhs_is_direct_value():
+            return self.as_subquery_sql(compiler, connection)
+        return super().as_sql(compiler, connection)
 
 
 tuple_lookups = {