diff options
| author | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-12-12 05:39:11 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-12 05:39:11 +0100 |
| commit | fcf95e592774a6ededec35481a2061474d467a2b (patch) | |
| tree | a29be963a35061170cb18d18bcd68dc194fe5728 /django | |
| parent | b287af5dc954628d4b336aefc5027b2edceee64b (diff) | |
Fixed #35018 -- Fixed migrations crash on GeneratedField with BooleanField as output_field on Oracle < 23c.
Thanks Václav Řehák for the report.
Regression in f333e3513e8bdf5ffeb6eeb63021c230082e6f95.
Diffstat (limited to 'django')
| -rw-r--r-- | django/db/backends/oracle/base.py | 39 | ||||
| -rw-r--r-- | django/db/backends/oracle/features.py | 8 | ||||
| -rw-r--r-- | django/db/backends/oracle/utils.py | 1 | ||||
| -rw-r--r-- | django/db/models/expressions.py | 11 | ||||
| -rw-r--r-- | django/db/models/fields/generated.py | 8 |
5 files changed, 50 insertions, 17 deletions
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 70b280dd08..a5e7f97df0 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -300,7 +300,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): @async_unsafe def create_cursor(self, name=None): - return FormatStylePlaceholderCursor(self.connection) + return FormatStylePlaceholderCursor(self.connection, self) def _commit(self): if self.connection is not None: @@ -365,11 +365,15 @@ class OracleParam: param = Oracle_datetime.from_datetime(param) string_size = 0 - # Oracle doesn't recognize True and False correctly. - if param is True: - param = 1 - elif param is False: - param = 0 + has_boolean_data_type = ( + cursor.database.features.supports_boolean_expr_in_select_clause + ) + if not has_boolean_data_type: + # Oracle < 23c doesn't recognize True and False correctly. + if param is True: + param = 1 + elif param is False: + param = 0 if hasattr(param, "bind_parameter"): self.force_bytes = param.bind_parameter(cursor) elif isinstance(param, (Database.Binary, datetime.timedelta)): @@ -389,6 +393,8 @@ class OracleParam: self.input_size = Database.DB_TYPE_CLOB elif isinstance(param, datetime.datetime): self.input_size = Database.DB_TYPE_TIMESTAMP + elif has_boolean_data_type and isinstance(param, bool): + self.input_size = Database.DB_TYPE_BOOLEAN else: self.input_size = None @@ -426,9 +432,10 @@ class FormatStylePlaceholderCursor: charset = "utf-8" - def __init__(self, connection): + def __init__(self, connection, database): self.cursor = connection.cursor() self.cursor.outputtypehandler = self._output_type_handler + self.database = database @staticmethod def _output_number_converter(value): @@ -528,14 +535,24 @@ class FormatStylePlaceholderCursor: # values. It can be used only in single query execute() because # executemany() shares the formatted query with each of the params # list. e.g. for input params = [0.75, 2, 0.75, 'sth', 0.75] - # params_dict = {0.75: ':arg0', 2: ':arg1', 'sth': ':arg2'} + # params_dict = { + # (float, 0.75): ':arg0', + # (int, 2): ':arg1', + # (str, 'sth'): ':arg2', + # } # args = [':arg0', ':arg1', ':arg0', ':arg2', ':arg0'] # params = {':arg0': 0.75, ':arg1': 2, ':arg2': 'sth'} + # The type of parameters in param_types keys is necessary to avoid + # unifying 0/1 with False/True. + param_types = [(type(param), param) for param in params] params_dict = { - param: ":arg%d" % i for i, param in enumerate(dict.fromkeys(params)) + param_type: ":arg%d" % i + for i, param_type in enumerate(dict.fromkeys(param_types)) + } + args = [params_dict[param_type] for param_type in param_types] + params = { + placeholder: param for (_, param), placeholder in params_dict.items() } - args = [params_dict[param] for param in params] - params = {value: key for key, value in params_dict.items()} query %= tuple(args) else: # Handle params as sequence diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py index dfec605c1b..f7937f24e3 100644 --- a/django/db/backends/oracle/features.py +++ b/django/db/backends/oracle/features.py @@ -76,7 +76,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_slicing_ordering_in_compound = True requires_compound_order_by_subquery = True allows_multiple_constraints_on_same_fields = False - supports_boolean_expr_in_select_clause = False supports_comparing_boolean_expr = False supports_json_field_contains = False supports_collation_on_textfield = False @@ -119,6 +118,9 @@ class DatabaseFeatures(BaseDatabaseFeatures): "Oracle doesn't support comparing NCLOB to NUMBER.": { "generic_relations_regress.tests.GenericRelationTests.test_textlink_filter", }, + "Oracle doesn't support casting filters to NUMBER.": { + "lookup.tests.LookupQueryingTests.test_aggregate_combined_lookup", + }, } django_test_expected_failures = { # A bug in Django/oracledb with respect to string handling (#23843). @@ -170,3 +172,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): @cached_property def supports_frame_exclusion(self): return self.connection.oracle_version >= (21,) + + @cached_property + def supports_boolean_expr_in_select_clause(self): + return self.connection.oracle_version >= (23,) diff --git a/django/db/backends/oracle/utils.py b/django/db/backends/oracle/utils.py index a5ec2f3899..57d97b3f77 100644 --- a/django/db/backends/oracle/utils.py +++ b/django/db/backends/oracle/utils.py @@ -21,6 +21,7 @@ class InsertVar: "PositiveBigIntegerField": int, "PositiveSmallIntegerField": int, "PositiveIntegerField": int, + "BooleanField": int, "FloatField": Database.DB_TYPE_BINARY_DOUBLE, "DateTimeField": Database.DB_TYPE_TIMESTAMP, "DateField": Database.Date, diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 36c0bbd50a..41443dc6db 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -1705,10 +1705,13 @@ class OrderBy(Expression): return (template % placeholders).rstrip(), params def as_oracle(self, compiler, connection): - # Oracle doesn't allow ORDER BY EXISTS() or filters unless it's wrapped - # in a CASE WHEN. - if connection.ops.conditional_expression_supported_in_where_clause( - self.expression + # Oracle < 23c doesn't allow ORDER BY EXISTS() or filters unless it's + # wrapped in a CASE WHEN. + if ( + not connection.features.supports_boolean_expr_in_select_clause + and connection.ops.conditional_expression_supported_in_where_clause( + self.expression + ) ): copy = self.copy() copy.expression = Case( diff --git a/django/db/models/fields/generated.py b/django/db/models/fields/generated.py index 95d19582de..257feeeba2 100644 --- a/django/db/models/fields/generated.py +++ b/django/db/models/fields/generated.py @@ -58,7 +58,13 @@ class GeneratedField(Field): resolved_expression = self.expression.resolve_expression( self._query, allow_joins=False ) - return compiler.compile(resolved_expression) + sql, params = compiler.compile(resolved_expression) + if ( + getattr(self.expression, "conditional", False) + and not connection.features.supports_boolean_expr_in_select_clause + ): + sql = f"CASE WHEN {sql} THEN 1 ELSE 0 END" + return sql, params def check(self, **kwargs): databases = kwargs.get("databases") or [] |
