features.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. from django.db import DatabaseError, InterfaceError
  2. from django.db.backends.base.features import BaseDatabaseFeatures
  3. from django.db.backends.oracle.oracledb_any import is_oracledb
  4. from django.utils.functional import cached_property
  5. class DatabaseFeatures(BaseDatabaseFeatures):
  6. minimum_database_version = (19,)
  7. # Oracle crashes with "ORA-00932: inconsistent datatypes: expected - got
  8. # BLOB" when grouping by LOBs (#24096).
  9. allows_group_by_lob = False
  10. # Although GROUP BY select index is supported by Oracle 23c+, it requires
  11. # GROUP_BY_POSITION_ENABLED to be enabled to avoid backward compatibility
  12. # issues. Introspection of this settings is not straightforward.
  13. allows_group_by_select_index = False
  14. interprets_empty_strings_as_nulls = True
  15. has_select_for_update = True
  16. has_select_for_update_nowait = True
  17. has_select_for_update_skip_locked = True
  18. has_select_for_update_of = True
  19. select_for_update_of_column = True
  20. can_return_columns_from_insert = True
  21. supports_subqueries_in_group_by = False
  22. ignores_unnecessary_order_by_in_subqueries = False
  23. supports_transactions = True
  24. supports_timezones = False
  25. has_native_duration_field = True
  26. can_defer_constraint_checks = True
  27. supports_partially_nullable_unique_constraints = False
  28. supports_deferrable_unique_constraints = True
  29. truncates_names = True
  30. supports_comments = True
  31. supports_tablespaces = True
  32. supports_sequence_reset = False
  33. can_introspect_materialized_views = True
  34. atomic_transactions = False
  35. nulls_order_largest = True
  36. requires_literal_defaults = True
  37. supports_default_keyword_in_bulk_insert = False
  38. closed_cursor_error_class = InterfaceError
  39. # Select for update with limit can be achieved on Oracle, but not with the
  40. # current backend.
  41. supports_select_for_update_with_limit = False
  42. supports_temporal_subtraction = True
  43. # Oracle doesn't ignore quoted identifiers case but the current backend
  44. # does by uppercasing all identifiers.
  45. ignores_table_name_case = True
  46. supports_index_on_text_field = False
  47. create_test_procedure_without_params_sql = """
  48. CREATE PROCEDURE "TEST_PROCEDURE" AS
  49. V_I INTEGER;
  50. BEGIN
  51. V_I := 1;
  52. END;
  53. """
  54. create_test_procedure_with_int_param_sql = """
  55. CREATE PROCEDURE "TEST_PROCEDURE" (P_I INTEGER) AS
  56. V_I INTEGER;
  57. BEGIN
  58. V_I := P_I;
  59. END;
  60. """
  61. create_test_table_with_composite_primary_key = """
  62. CREATE TABLE test_table_composite_pk (
  63. column_1 NUMBER(11) NOT NULL,
  64. column_2 NUMBER(11) NOT NULL,
  65. PRIMARY KEY (column_1, column_2)
  66. )
  67. """
  68. supports_callproc_kwargs = True
  69. supports_over_clause = True
  70. supports_frame_range_fixed_distance = True
  71. supports_ignore_conflicts = False
  72. max_query_params = 2**16 - 1
  73. supports_partial_indexes = False
  74. supports_stored_generated_columns = False
  75. supports_virtual_generated_columns = True
  76. can_rename_index = True
  77. supports_slicing_ordering_in_compound = True
  78. requires_compound_order_by_subquery = True
  79. allows_multiple_constraints_on_same_fields = False
  80. supports_json_field_contains = False
  81. supports_collation_on_textfield = False
  82. test_now_utc_template = "CURRENT_TIMESTAMP AT TIME ZONE 'UTC'"
  83. django_test_expected_failures = {
  84. # A bug in Django/oracledb with respect to string handling (#23843).
  85. "annotations.tests.NonAggregateAnnotationTestCase.test_custom_functions",
  86. "annotations.tests.NonAggregateAnnotationTestCase."
  87. "test_custom_functions_can_ref_other_functions",
  88. }
  89. insert_test_table_with_defaults = (
  90. "INSERT INTO {} VALUES (DEFAULT, DEFAULT, DEFAULT)"
  91. )
  92. @cached_property
  93. def django_test_skips(self):
  94. skips = {
  95. "Oracle doesn't support SHA224.": {
  96. "db_functions.text.test_sha224.SHA224Tests.test_basic",
  97. "db_functions.text.test_sha224.SHA224Tests.test_transform",
  98. },
  99. "Oracle doesn't correctly calculate ISO 8601 week numbering before "
  100. "1583 (the Gregorian calendar was introduced in 1582).": {
  101. "db_functions.datetime.test_extract_trunc.DateFunctionTests."
  102. "test_trunc_week_before_1000",
  103. "db_functions.datetime.test_extract_trunc."
  104. "DateFunctionWithTimeZoneTests.test_trunc_week_before_1000",
  105. },
  106. "Oracle doesn't support bitwise XOR.": {
  107. "expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor",
  108. "expressions.tests.ExpressionOperatorTests."
  109. "test_lefthand_bitwise_xor_null",
  110. "expressions.tests.ExpressionOperatorTests."
  111. "test_lefthand_bitwise_xor_right_null",
  112. },
  113. "Oracle requires ORDER BY in row_number, ANSI:SQL doesn't.": {
  114. "expressions_window.tests.WindowFunctionTests."
  115. "test_row_number_no_ordering",
  116. "prefetch_related.tests.PrefetchLimitTests.test_empty_order",
  117. },
  118. "Oracle doesn't support changing collations on indexed columns (#33671).": {
  119. "migrations.test_operations.OperationTests."
  120. "test_alter_field_pk_fk_db_collation",
  121. },
  122. "Oracle doesn't support comparing NCLOB to NUMBER.": {
  123. "generic_relations_regress.tests.GenericRelationTests."
  124. "test_textlink_filter",
  125. },
  126. "Oracle doesn't support casting filters to NUMBER.": {
  127. "lookup.tests.LookupQueryingTests.test_aggregate_combined_lookup",
  128. },
  129. }
  130. if self.connection.oracle_version < (23,):
  131. skips.update(
  132. {
  133. "Raises ORA-00600 on Oracle < 23c: internal error code.": {
  134. "model_fields.test_jsonfield.TestQuerying."
  135. "test_usage_in_subquery",
  136. },
  137. }
  138. )
  139. if is_oracledb and self.connection.oracledb_version >= (2, 1, 2):
  140. skips.update(
  141. {
  142. "python-oracledb 2.1.2+ no longer hides 'ORA-1403: no data found' "
  143. "exceptions raised in database triggers.": {
  144. "backends.oracle.tests.TransactionalTests."
  145. "test_hidden_no_data_found_exception"
  146. },
  147. },
  148. )
  149. return skips
  150. @cached_property
  151. def introspected_field_types(self):
  152. return {
  153. **super().introspected_field_types,
  154. "GenericIPAddressField": "CharField",
  155. "PositiveBigIntegerField": "BigIntegerField",
  156. "PositiveIntegerField": "IntegerField",
  157. "PositiveSmallIntegerField": "IntegerField",
  158. "SmallIntegerField": "IntegerField",
  159. "TimeField": "DateTimeField",
  160. }
  161. @cached_property
  162. def test_collations(self):
  163. return {
  164. "ci": "BINARY_CI",
  165. "cs": "BINARY",
  166. "non_default": "SWEDISH_CI",
  167. "swedish_ci": "SWEDISH_CI",
  168. "virtual": "SWEDISH_CI" if self.supports_collation_on_charfield else None,
  169. }
  170. @cached_property
  171. def supports_collation_on_charfield(self):
  172. sql = "SELECT CAST('a' AS VARCHAR2(4001))" + self.bare_select_suffix
  173. with self.connection.cursor() as cursor:
  174. try:
  175. cursor.execute(sql)
  176. except DatabaseError as e:
  177. if e.args[0].code == 910:
  178. return False
  179. raise
  180. return True
  181. @cached_property
  182. def supports_primitives_in_json_field(self):
  183. return self.connection.oracle_version >= (21,)
  184. @cached_property
  185. def supports_frame_exclusion(self):
  186. return self.connection.oracle_version >= (21,)
  187. @cached_property
  188. def supports_boolean_expr_in_select_clause(self):
  189. return self.connection.oracle_version >= (23,)
  190. @cached_property
  191. def supports_comparing_boolean_expr(self):
  192. return self.connection.oracle_version >= (23,)
  193. @cached_property
  194. def supports_aggregation_over_interval_types(self):
  195. return self.connection.oracle_version >= (23,)
  196. @cached_property
  197. def bare_select_suffix(self):
  198. return "" if self.connection.oracle_version >= (23,) else " FROM DUAL"