Plain is headed towards 1.0! Subscribe for development updates →

  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