features.py 5.83 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
import operator
import platform

from django.db import transaction
from django.db.backends.base.features import BaseDatabaseFeatures
from django.db.utils import OperationalError
from django.utils.functional import cached_property

from .base import Database


class DatabaseFeatures(BaseDatabaseFeatures):
    # SQLite can read from a cursor since SQLite 3.6.5, subject to the caveat
    # that statements within a connection aren't isolated from each other. See
    # https://sqlite.org/isolation.html.
    can_use_chunked_reads = True
    test_db_allows_multiple_connections = False
    supports_unspecified_pk = True
    supports_timezones = False
    max_query_params = 999
    supports_mixed_date_datetime_comparisons = False
    supports_transactions = True
    atomic_transactions = False
    can_rollback_ddl = True
    can_create_inline_fk = False
    supports_paramstyle_pyformat = False
    can_clone_databases = True
    supports_temporal_subtraction = True
    ignores_table_name_case = True
    supports_cast_with_precision = False
    time_cast_precision = 3
    can_release_savepoints = True
    # Is "ALTER TABLE ... RENAME COLUMN" supported?
    can_alter_table_rename_column = Database.sqlite_version_info >= (3, 25, 0)
    supports_parentheses_in_compound = False
    # Deferred constraint checks can be emulated on SQLite < 3.20 but not in a
    # reasonably performant way.
    supports_pragma_foreign_key_check = Database.sqlite_version_info >= (3, 20, 0)
    can_defer_constraint_checks = supports_pragma_foreign_key_check
    supports_functions_in_partial_indexes = Database.sqlite_version_info >= (3, 15, 0)
    supports_over_clause = Database.sqlite_version_info >= (3, 25, 0)
    supports_frame_range_fixed_distance = Database.sqlite_version_info >= (3, 28, 0)
    supports_aggregate_filter_clause = Database.sqlite_version_info >= (3, 30, 1)
    supports_order_by_nulls_modifier = Database.sqlite_version_info >= (3, 30, 0)
    order_by_nulls_first = True
    supports_json_field_contains = False
    test_collations = {
        "ci": "nocase",
        "cs": "binary",
        "non_default": "nocase",
    }

    @cached_property
    def django_test_skips(self):
        skips = {
            "SQLite stores values rounded to 15 significant digits.": {
                "model_fields.test_decimalfield.DecimalFieldTests."
                "test_fetch_from_db_without_float_rounding",
            },
            "SQLite naively remakes the table on field alteration.": {
                "schema.tests.SchemaTests.test_unique_no_unnecessary_fk_drops",
                "schema.tests.SchemaTests.test_unique_and_reverse_m2m",
                "schema.tests.SchemaTests."
                "test_alter_field_default_doesnt_perform_queries",
                "schema.tests.SchemaTests."
                "test_rename_column_renames_deferred_sql_references",
            },
            "SQLite doesn't have a constraint.": {
                "model_fields.test_integerfield.PositiveIntegerFieldTests."
                "test_negative_values",
            },
            "SQLite doesn't support negative precision for ROUND().": {
                "db_functions.math.test_round.RoundTests."
                "test_null_with_negative_precision",
                "db_functions.math.test_round.RoundTests."
                "test_decimal_with_negative_precision",
                "db_functions.math.test_round.RoundTests."
                "test_float_with_negative_precision",
                "db_functions.math.test_round.RoundTests."
                "test_integer_with_negative_precision",
            },
        }
        if Database.sqlite_version_info < (3, 27):
            skips.update(
                {
                    "Nondeterministic failure on SQLite < 3.27.": {
                        "expressions_window.tests.WindowFunctionTests."
                        "test_subquery_row_range_rank",
                    },
                }
            )
        if self.connection.is_in_memory_db():
            skips.update(
                {
                    "the sqlite backend's close() method is a no-op when using an "
                    "in-memory database": {
                        "servers.test_liveserverthread.LiveServerThreadTest."
                        "test_closes_connections",
                        "servers.tests.LiveServerTestCloseConnectionTest."
                        "test_closes_connections",
                    },
                }
            )
        return skips

    @cached_property
    def supports_atomic_references_rename(self):
        # SQLite 3.28.0 bundled with MacOS 10.15 does not support renaming
        # references atomically.
        if platform.mac_ver()[0].startswith(
            "10.15."
        ) and Database.sqlite_version_info == (3, 28, 0):
            return False
        return Database.sqlite_version_info >= (3, 26, 0)

    @cached_property
    def introspected_field_types(self):
        return {
            **super().introspected_field_types,
            "BigAutoField": "AutoField",
            "DurationField": "BigIntegerField",
            "GenericIPAddressField": "CharField",
            "SmallAutoField": "AutoField",
        }

    @cached_property
    def supports_json_field(self):
        with self.connection.cursor() as cursor:
            try:
                with transaction.atomic(self.connection.alias):
                    cursor.execute('SELECT JSON(\'{"a": "b"}\')')
            except OperationalError:
                return False
        return True

    can_introspect_json_field = property(operator.attrgetter("supports_json_field"))
    has_json_object_function = property(operator.attrgetter("supports_json_field"))

    @cached_property
    def can_return_columns_from_insert(self):
        return Database.sqlite_version_info >= (3, 35)

    can_return_rows_from_bulk_insert = property(
        operator.attrgetter("can_return_columns_from_insert")
    )