Plain is headed towards 1.0! Subscribe for development updates →

  1from __future__ import annotations
  2
  3from functools import cached_property
  4from typing import TYPE_CHECKING, Any
  5
  6if TYPE_CHECKING:
  7    from plain.models.backends.base.base import BaseDatabaseWrapper
  8
  9
 10class BaseDatabaseFeatures:
 11    # An optional tuple indicating the minimum supported database version.
 12    minimum_database_version = None
 13    allows_group_by_selected_pks = False
 14    allows_group_by_select_index = True
 15    empty_fetchmany_value = []
 16    update_can_self_select = True
 17
 18    # Does the backend support initially deferrable unique constraints?
 19    supports_deferrable_unique_constraints = False
 20
 21    can_use_chunked_reads = True
 22    can_return_columns_from_insert = False
 23    can_return_rows_from_bulk_insert = False
 24    has_bulk_insert = True
 25    uses_savepoints = True
 26
 27    # If True, don't use integer foreign keys referring to, e.g., positive
 28    # integer primary keys.
 29    related_fields_match_type = False
 30    has_select_for_update = False
 31    has_select_for_update_nowait = False
 32    has_select_for_update_skip_locked = False
 33    has_select_for_update_of = False
 34    has_select_for_no_key_update = False
 35    # Does the database's SELECT FOR UPDATE OF syntax require a column rather
 36    # than a table?
 37    select_for_update_of_column = False
 38
 39    # Does the backend truncate names properly when they are too long?
 40    truncates_names = False
 41
 42    # Does the backend ignore unnecessary ORDER BY clauses in subqueries?
 43    ignores_unnecessary_order_by_in_subqueries = True
 44
 45    # Is there a true datatype for uuid?
 46    has_native_uuid_field = False
 47
 48    # Is there a true datatype for timedeltas?
 49    has_native_duration_field = False
 50
 51    # Does the database driver supports same type temporal data subtraction
 52    # by returning the type used to store duration field?
 53    supports_temporal_subtraction = False
 54
 55    # Does the database have a copy of the zoneinfo database?
 56    has_zoneinfo_database = True
 57
 58    # Does the backend support NULLS FIRST and NULLS LAST in ORDER BY?
 59    supports_order_by_nulls_modifier = True
 60
 61    # Does the backend orders NULLS FIRST by default?
 62    order_by_nulls_first = False
 63
 64    # The database's limit on the number of query parameters.
 65    max_query_params = None
 66
 67    # Can an object have an autoincrement primary key of 0?
 68    allows_auto_pk_0 = True
 69
 70    # Do we need to NULL a ForeignKey out, or can the constraint check be
 71    # deferred
 72    can_defer_constraint_checks = False
 73
 74    # Can the backend introspect the column order (ASC/DESC) for indexes?
 75    supports_index_column_ordering = True
 76
 77    # Can we roll back DDL in a transaction?
 78    can_rollback_ddl = False
 79
 80    # Does it support operations requiring references rename in a transaction?
 81    supports_atomic_references_rename = True
 82
 83    # Can we issue more than one ALTER COLUMN clause in an ALTER TABLE?
 84    supports_combined_alters = False
 85
 86    # Does it support foreign keys?
 87    supports_foreign_keys = True
 88
 89    # Can an index be renamed?
 90    can_rename_index = False
 91
 92    # Does it support CHECK constraints?
 93    supports_column_check_constraints = True
 94    supports_table_check_constraints = True
 95    # Does the backend support introspection of CHECK constraints?
 96    can_introspect_check_constraints = True
 97
 98    # Does the backend require literal defaults, rather than parameterized ones?
 99    requires_literal_defaults = False
100
101    # Does the backend require a connection reset after each material schema change?
102    connection_persists_old_columns = False
103
104    # Suffix for backends that don't support "SELECT xxx;" queries.
105    bare_select_suffix = ""
106
107    # If NULL is implied on columns without needing to be explicitly specified
108    implied_column_null = False
109
110    # Does the backend support "select for update" queries with limit (and offset)?
111    supports_select_for_update_with_limit = True
112
113    # Does the backend consider table names with different casing to
114    # be equal?
115    ignores_table_name_case = False
116
117    # Place FOR UPDATE right after FROM clause. Used on MSSQL.
118    for_update_after_from = False
119
120    # Combinatorial flags
121    supports_select_union = True
122    supports_select_intersection = True
123    supports_select_difference = True
124    supports_slicing_ordering_in_compound = False
125    supports_parentheses_in_compound = True
126    requires_compound_order_by_subquery = False
127
128    # Does the database support SQL 2003 FILTER (WHERE ...) in aggregate
129    # expressions?
130    supports_aggregate_filter_clause = False
131
132    # Does the backend support window expressions (expression OVER (...))?
133    supports_over_clause = False
134    only_supports_unbounded_with_preceding_and_following = False
135
136    # Does the backend support keyword parameters for cursor.callproc()?
137    supports_callproc_kwargs = False
138
139    # What formats does the backend EXPLAIN syntax support?
140    supported_explain_formats = set()
141
142    # Does the backend support updating rows on constraint or uniqueness errors
143    # during INSERT?
144    supports_update_conflicts = False
145    supports_update_conflicts_with_target = False
146
147    # Does this backend require casting the results of CASE expressions used
148    # in UPDATE statements to ensure the expression has the correct type?
149    requires_casted_case_in_updates = False
150
151    # Does the backend support partial indexes (CREATE INDEX ... WHERE ...)?
152    supports_partial_indexes = True
153    # Does the backend support covering indexes (CREATE INDEX ... INCLUDE ...)?
154    supports_covering_indexes = False
155    # Does the backend support indexes on expressions?
156    supports_expression_indexes = True
157    # Does the backend treat COLLATE as an indexed expression?
158    collate_as_index_expression = False
159
160    # Does the backend support boolean expressions in SELECT and GROUP BY
161    # clauses?
162    supports_boolean_expr_in_select_clause = True
163    # Does the backend support comparing boolean expressions in WHERE clauses?
164    # Eg: WHERE (price > 0) IS NOT NULL
165    supports_comparing_boolean_expr = True
166
167    # Does the backend support JSONField?
168    supports_json_field = True
169    # Can the backend introspect a JSONField?
170    can_introspect_json_field = True
171    # Is there a true datatype for JSON?
172    has_native_json_field = False
173    # Does the backend support __contains and __contained_by lookups for
174    # a JSONField?
175    supports_json_field_contains = True
176    # Does the backend support JSONObject() database function?
177    has_json_object_function = True
178
179    # Does the backend support column collations?
180    supports_collation_on_charfield = True
181    supports_collation_on_textfield = True
182
183    # Does the backend support column and table comments?
184    supports_comments = False
185    # Does the backend support column comments in ADD COLUMN statements?
186    supports_comments_inline = False
187
188    # Does the backend support the logical XOR operator?
189    supports_logical_xor = False
190
191    # Does the backend support unlimited character columns?
192    supports_unlimited_charfield = False
193
194    def __init__(self, connection: BaseDatabaseWrapper):
195        self.connection = connection
196
197    @cached_property
198    def supports_explaining_query_execution(self) -> bool:
199        """Does this backend support explaining query execution?"""
200        return self.connection.ops.explain_prefix is not None
201
202    @cached_property
203    def supports_transactions(self) -> bool:
204        """Confirm support for transactions."""
205        with self.connection.cursor() as cursor:
206            cursor.execute("CREATE TABLE ROLLBACK_TEST (X INT)")
207            self.connection.set_autocommit(False)
208            cursor.execute("INSERT INTO ROLLBACK_TEST (X) VALUES (8)")
209            self.connection.rollback()
210            self.connection.set_autocommit(True)
211            cursor.execute("SELECT COUNT(X) FROM ROLLBACK_TEST")
212            (count,) = cursor.fetchone()
213            cursor.execute("DROP TABLE ROLLBACK_TEST")
214        return count == 0
215
216    def allows_group_by_selected_pks_on_model(self, model: Any) -> bool:
217        if not self.allows_group_by_selected_pks:
218            return False
219        return True