1from __future__ import annotations
  2
  3from functools import cached_property
  4from typing import TYPE_CHECKING
  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: tuple[int, ...] | None = 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
 36    # Does the backend truncate names properly when they are too long?
 37    truncates_names = False
 38
 39    # Does the backend ignore unnecessary ORDER BY clauses in subqueries?
 40    ignores_unnecessary_order_by_in_subqueries = True
 41
 42    # Is there a true datatype for uuid?
 43    has_native_uuid_field = False
 44
 45    # Is there a true datatype for timedeltas?
 46    has_native_duration_field = False
 47
 48    # Does the database driver supports same type temporal data subtraction
 49    # by returning the type used to store duration field?
 50    supports_temporal_subtraction = False
 51
 52    # Does the database have a copy of the zoneinfo database?
 53    has_zoneinfo_database = True
 54
 55    # Does the backend support NULLS FIRST and NULLS LAST in ORDER BY?
 56    supports_order_by_nulls_modifier = True
 57
 58    # Does the backend orders NULLS FIRST by default?
 59    order_by_nulls_first = False
 60
 61    # The database's limit on the number of query parameters.
 62    max_query_params = None
 63
 64    # Can an object have an autoincrement primary key of 0?
 65    allows_auto_pk_0 = True
 66
 67    # Do we need to NULL a ForeignKeyField out, or can the constraint check be
 68    # deferred
 69    can_defer_constraint_checks = False
 70
 71    # Can the backend introspect the column order (ASC/DESC) for indexes?
 72    supports_index_column_ordering = True
 73
 74    # Can we roll back DDL in a transaction?
 75    can_rollback_ddl = False
 76
 77    # Does it support operations requiring references rename in a transaction?
 78    supports_atomic_references_rename = True
 79
 80    # Can we issue more than one ALTER COLUMN clause in an ALTER TABLE?
 81    supports_combined_alters = False
 82
 83    # Does it support foreign keys?
 84    supports_foreign_keys = True
 85
 86    # Can an index be renamed?
 87    can_rename_index = False
 88
 89    # Does it support CHECK constraints?
 90    supports_column_check_constraints = True
 91    supports_table_check_constraints = True
 92    # Does the backend support introspection of CHECK constraints?
 93    can_introspect_check_constraints = True
 94
 95    # Does the backend require literal defaults, rather than parameterized ones?
 96    requires_literal_defaults = False
 97
 98    # Suffix for backends that don't support "SELECT xxx;" queries.
 99    bare_select_suffix = ""
100
101    # Does the backend support "select for update" queries with limit (and offset)?
102    supports_select_for_update_with_limit = True
103
104    # Does the backend consider table names with different casing to
105    # be equal?
106    ignores_table_name_case = False
107
108    # Does the database support SQL 2003 FILTER (WHERE ...) in aggregate
109    # expressions?
110    supports_aggregate_filter_clause = False
111
112    # Does the backend support window expressions (expression OVER (...))?
113    supports_over_clause = False
114    only_supports_unbounded_with_preceding_and_following = False
115
116    # Does the backend support keyword parameters for cursor.callproc()?
117    supports_callproc_kwargs = False
118
119    # What formats does the backend EXPLAIN syntax support?
120    supported_explain_formats = set()
121
122    # Does the backend support updating rows on constraint or uniqueness errors
123    # during INSERT?
124    supports_update_conflicts = False
125    supports_update_conflicts_with_target = False
126
127    # Does this backend require casting the results of CASE expressions used
128    # in UPDATE statements to ensure the expression has the correct type?
129    requires_casted_case_in_updates = False
130
131    # Does the backend support partial indexes (CREATE INDEX ... WHERE ...)?
132    supports_partial_indexes = True
133    # Does the backend support covering indexes (CREATE INDEX ... INCLUDE ...)?
134    supports_covering_indexes = False
135    # Does the backend support indexes on expressions?
136    supports_expression_indexes = True
137    # Does the backend treat COLLATE as an indexed expression?
138    collate_as_index_expression = False
139
140    # Does the backend support boolean expressions in SELECT and GROUP BY
141    # clauses?
142    supports_boolean_expr_in_select_clause = True
143    # Does the backend support comparing boolean expressions in WHERE clauses?
144    # Eg: WHERE (price > 0) IS NOT NULL
145    supports_comparing_boolean_expr = True
146
147    # Does the backend support JSONField?
148    supports_json_field = True
149    # Can the backend introspect a JSONField?
150    can_introspect_json_field = True
151    # Is there a true datatype for JSON?
152    has_native_json_field = False
153    # Does the backend support __contains and __contained_by lookups for
154    # a JSONField?
155    supports_json_field_contains = True
156    # Does the backend support JSONObject() database function?
157    has_json_object_function = True
158
159    # Does the backend support column collations?
160    supports_collation_on_charfield = True
161    supports_collation_on_textfield = True
162
163    # Does the backend support column and table comments?
164    supports_comments = False
165    # Does the backend support column comments in ADD COLUMN statements?
166    supports_comments_inline = False
167
168    # Does the backend support the logical XOR operator?
169    supports_logical_xor = False
170
171    # Does the backend support unlimited character columns?
172    supports_unlimited_charfield = False
173
174    def __init__(self, connection: BaseDatabaseWrapper):
175        self.connection = connection
176
177    @cached_property
178    def supports_transactions(self) -> bool:
179        """Confirm support for transactions."""
180        with self.connection.cursor() as cursor:
181            cursor.execute("CREATE TABLE ROLLBACK_TEST (X INT)")
182            self.connection.set_autocommit(False)
183            cursor.execute("INSERT INTO ROLLBACK_TEST (X) VALUES (8)")
184            self.connection.rollback()
185            self.connection.set_autocommit(True)
186            cursor.execute("SELECT COUNT(X) FROM ROLLBACK_TEST")
187            row = cursor.fetchone()
188            assert row is not None
189            (count,) = row
190            cursor.execute("DROP TABLE ROLLBACK_TEST")
191        return count == 0