1import operator
2from functools import cached_property
3
4from plain.models.backends.base.features import BaseDatabaseFeatures
5
6
7class DatabaseFeatures(BaseDatabaseFeatures):
8 empty_fetchmany_value = ()
9 allows_group_by_selected_pks = True
10 related_fields_match_type = True
11 has_select_for_update = True
12 supports_comments = True
13 supports_comments_inline = True
14 supports_temporal_subtraction = True
15 supports_slicing_ordering_in_compound = True
16 supports_update_conflicts = True
17
18 # Neither MySQL nor MariaDB support partial indexes.
19 supports_partial_indexes = False
20 # COLLATE must be wrapped in parentheses because MySQL treats COLLATE as an
21 # indexed expression.
22 collate_as_index_expression = True
23
24 supports_order_by_nulls_modifier = False
25 order_by_nulls_first = True
26 supports_logical_xor = True
27
28 @cached_property
29 def minimum_database_version(self):
30 if self.connection.mysql_is_mariadb:
31 return (10, 4)
32 else:
33 return (8,)
34
35 @cached_property
36 def _mysql_storage_engine(self):
37 "Internal method used in Plain tests. Don't rely on this from your code"
38 return self.connection.mysql_server_data["default_storage_engine"]
39
40 @cached_property
41 def allows_auto_pk_0(self):
42 """
43 Autoincrement primary key can be set to 0 if it doesn't generate new
44 autoincrement values.
45 """
46 return "NO_AUTO_VALUE_ON_ZERO" in self.connection.sql_mode
47
48 @cached_property
49 def update_can_self_select(self):
50 return self.connection.mysql_is_mariadb and self.connection.mysql_version >= (
51 10,
52 3,
53 2,
54 )
55
56 @cached_property
57 def can_return_columns_from_insert(self):
58 return self.connection.mysql_is_mariadb and self.connection.mysql_version >= (
59 10,
60 5,
61 0,
62 )
63
64 can_return_rows_from_bulk_insert = property(
65 operator.attrgetter("can_return_columns_from_insert")
66 )
67
68 @cached_property
69 def has_zoneinfo_database(self):
70 return self.connection.mysql_server_data["has_zoneinfo_database"]
71
72 @cached_property
73 def is_sql_auto_is_null_enabled(self):
74 return self.connection.mysql_server_data["sql_auto_is_null"]
75
76 @cached_property
77 def supports_over_clause(self):
78 if self.connection.mysql_is_mariadb:
79 return True
80 return self.connection.mysql_version >= (8, 0, 2)
81
82 @cached_property
83 def supports_column_check_constraints(self):
84 if self.connection.mysql_is_mariadb:
85 return True
86 return self.connection.mysql_version >= (8, 0, 16)
87
88 supports_table_check_constraints = property(
89 operator.attrgetter("supports_column_check_constraints")
90 )
91
92 @cached_property
93 def can_introspect_check_constraints(self):
94 if self.connection.mysql_is_mariadb:
95 return True
96 return self.connection.mysql_version >= (8, 0, 16)
97
98 @cached_property
99 def has_select_for_update_skip_locked(self):
100 if self.connection.mysql_is_mariadb:
101 return self.connection.mysql_version >= (10, 6)
102 return self.connection.mysql_version >= (8, 0, 1)
103
104 @cached_property
105 def has_select_for_update_nowait(self):
106 if self.connection.mysql_is_mariadb:
107 return True
108 return self.connection.mysql_version >= (8, 0, 1)
109
110 @cached_property
111 def has_select_for_update_of(self):
112 return (
113 not self.connection.mysql_is_mariadb
114 and self.connection.mysql_version >= (8, 0, 1)
115 )
116
117 @cached_property
118 def supports_explain_analyze(self):
119 return self.connection.mysql_is_mariadb or self.connection.mysql_version >= (
120 8,
121 0,
122 18,
123 )
124
125 @cached_property
126 def supported_explain_formats(self):
127 # Alias MySQL's TRADITIONAL to TEXT for consistency with other
128 # backends.
129 formats = {"JSON", "TEXT", "TRADITIONAL"}
130 if not self.connection.mysql_is_mariadb and self.connection.mysql_version >= (
131 8,
132 0,
133 16,
134 ):
135 formats.add("TREE")
136 return formats
137
138 @cached_property
139 def supports_transactions(self):
140 """
141 All storage engines except MyISAM support transactions.
142 """
143 return self._mysql_storage_engine != "MyISAM"
144
145 uses_savepoints = property(operator.attrgetter("supports_transactions"))
146
147 @cached_property
148 def ignores_table_name_case(self):
149 return self.connection.mysql_server_data["lower_case_table_names"]
150
151 @cached_property
152 def can_introspect_json_field(self):
153 if self.connection.mysql_is_mariadb:
154 return self.can_introspect_check_constraints
155 return True
156
157 @cached_property
158 def supports_index_column_ordering(self):
159 if self._mysql_storage_engine != "InnoDB":
160 return False
161 if self.connection.mysql_is_mariadb:
162 return self.connection.mysql_version >= (10, 8)
163 return self.connection.mysql_version >= (8, 0, 1)
164
165 @cached_property
166 def supports_expression_indexes(self):
167 return (
168 not self.connection.mysql_is_mariadb
169 and self._mysql_storage_engine != "MyISAM"
170 and self.connection.mysql_version >= (8, 0, 13)
171 )
172
173 @cached_property
174 def supports_select_intersection(self):
175 is_mariadb = self.connection.mysql_is_mariadb
176 return is_mariadb or self.connection.mysql_version >= (8, 0, 31)
177
178 supports_select_difference = property(
179 operator.attrgetter("supports_select_intersection")
180 )
181
182 @cached_property
183 def can_rename_index(self):
184 if self.connection.mysql_is_mariadb:
185 return self.connection.mysql_version >= (10, 5, 2)
186 return True