Skip to content

Commit aa42357

Browse files
committed
Fixed django#17760 -- Implemented callable database features as cached properties
This does remove the requirement to call features.confirm() method before checking the properties. Thanks cdestiger and Ramiro Morales for their work on the patch.
1 parent 484fcd3 commit aa42357

File tree

7 files changed

+30
-48
lines changed

7 files changed

+30
-48
lines changed

django/contrib/gis/db/backends/spatialite/creation.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ def create_test_db(self, verbosity=1, autoclobber=False):
3131
self.connection.close()
3232
self.connection.settings_dict["NAME"] = test_database_name
3333

34-
# Confirm the feature set of the test database
35-
self.connection.features.confirm()
36-
3734
# Need to load the SpatiaLite initialization SQL before running `syncdb`.
3835
self.load_spatialite_sql()
3936

django/db/backends/__init__.py

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from django.db import DEFAULT_DB_ALIAS
1111
from django.db.backends import util
1212
from django.db.transaction import TransactionManagementError
13+
from django.utils.functional import cached_property
1314
from django.utils.importlib import import_module
1415
from django.utils.timezone import is_aware
1516

@@ -402,28 +403,19 @@ class BaseDatabaseFeatures(object):
402403
# Does the backend reset sequences between tests?
403404
supports_sequence_reset = True
404405

405-
# Features that need to be confirmed at runtime
406-
# Cache whether the confirmation has been performed.
407-
_confirmed = False
408-
supports_transactions = None
409-
supports_stddev = None
410-
can_introspect_foreign_keys = None
406+
# Confirm support for introspected foreign keys
407+
# Every database can do this reliably, except MySQL,
408+
# which can't do it for MyISAM tables
409+
can_introspect_foreign_keys = True
411410

412411
# Support for the DISTINCT ON clause
413412
can_distinct_on_fields = False
414413

415414
def __init__(self, connection):
416415
self.connection = connection
417416

418-
def confirm(self):
419-
"Perform manual checks of any database features that might vary between installs"
420-
if not self._confirmed:
421-
self._confirmed = True
422-
self.supports_transactions = self._supports_transactions()
423-
self.supports_stddev = self._supports_stddev()
424-
self.can_introspect_foreign_keys = self._can_introspect_foreign_keys()
425-
426-
def _supports_transactions(self):
417+
@cached_property
418+
def supports_transactions(self):
427419
"Confirm support for transactions"
428420
cursor = self.connection.cursor()
429421
cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')
@@ -436,7 +428,8 @@ def _supports_transactions(self):
436428
self.connection._commit()
437429
return count == 0
438430

439-
def _supports_stddev(self):
431+
@cached_property
432+
def supports_stddev(self):
440433
"Confirm support for STDDEV and related stats functions"
441434
class StdDevPop(object):
442435
sql_function = 'STDDEV_POP'
@@ -447,12 +440,6 @@ class StdDevPop(object):
447440
except NotImplementedError:
448441
return False
449442

450-
def _can_introspect_foreign_keys(self):
451-
"Confirm support for introspected foreign keys"
452-
# Every database can do this reliably, except MySQL,
453-
# which can't do it for MyISAM tables
454-
return True
455-
456443

457444
class BaseDatabaseOperations(object):
458445
"""

django/db/backends/creation.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,6 @@ def create_test_db(self, verbosity=1, autoclobber=False):
264264
self.connection.close()
265265
self.connection.settings_dict["NAME"] = test_database_name
266266

267-
# Confirm the feature set of the test database
268-
self.connection.features.confirm()
269-
270267
# Report syncdb messages at one level lower than that requested.
271268
# This ensures we don't get flooded with messages during testing
272269
# (unless you really ask to be flooded)

django/db/backends/mysql/base.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from django.db.backends.mysql.creation import DatabaseCreation
3838
from django.db.backends.mysql.introspection import DatabaseIntrospection
3939
from django.db.backends.mysql.validation import DatabaseValidation
40+
from django.utils.functional import cached_property
4041
from django.utils.safestring import SafeString, SafeUnicode
4142
from django.utils import timezone
4243

@@ -170,26 +171,25 @@ class DatabaseFeatures(BaseDatabaseFeatures):
170171

171172
def __init__(self, connection):
172173
super(DatabaseFeatures, self).__init__(connection)
173-
self._storage_engine = None
174174

175+
@cached_property
175176
def _mysql_storage_engine(self):
176177
"Internal method used in Django tests. Don't rely on this from your code"
177-
if self._storage_engine is None:
178-
cursor = self.connection.cursor()
179-
cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)')
180-
# This command is MySQL specific; the second column
181-
# will tell you the default table type of the created
182-
# table. Since all Django's test tables will have the same
183-
# table type, that's enough to evaluate the feature.
184-
cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'")
185-
result = cursor.fetchone()
186-
cursor.execute('DROP TABLE INTROSPECT_TEST')
187-
self._storage_engine = result[1]
188-
return self._storage_engine
189-
190-
def _can_introspect_foreign_keys(self):
178+
cursor = self.connection.cursor()
179+
cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)')
180+
# This command is MySQL specific; the second column
181+
# will tell you the default table type of the created
182+
# table. Since all Django's test tables will have the same
183+
# table type, that's enough to evaluate the feature.
184+
cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'")
185+
result = cursor.fetchone()
186+
cursor.execute('DROP TABLE INTROSPECT_TEST')
187+
return result[1]
188+
189+
@cached_property
190+
def can_introspect_foreign_keys(self):
191191
"Confirm support for introspected foreign keys"
192-
return self._mysql_storage_engine() != 'MyISAM'
192+
return self._mysql_storage_engine != 'MyISAM'
193193

194194
class DatabaseOperations(BaseDatabaseOperations):
195195
compiler_module = "django.db.backends.mysql.compiler"

django/db/backends/sqlite3/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from django.db.backends.sqlite3.creation import DatabaseCreation
2020
from django.db.backends.sqlite3.introspection import DatabaseIntrospection
2121
from django.utils.dateparse import parse_date, parse_datetime, parse_time
22+
from django.utils.functional import cached_property
2223
from django.utils.safestring import SafeString
2324
from django.utils import timezone
2425

@@ -86,7 +87,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
8687
has_bulk_insert = True
8788
can_combine_inserts_with_and_without_auto_increment_pk = True
8889

89-
def _supports_stddev(self):
90+
@cached_property
91+
def supports_stddev(self):
9092
"""Confirm support for STDDEV and related stats functions
9193
9294
SQLite supports STDDEV as an extension package; so

tests/regressiontests/backends/tests.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,7 @@ def test_database_operations_helper_class(self):
403403
self.assertTrue(hasattr(connection.ops, 'connection'))
404404
self.assertEqual(connection, connection.ops.connection)
405405

406-
def test_supports_needed_confirm(self):
407-
connection.features.confirm()
406+
def test_cached_db_features(self):
408407
self.assertIn(connection.features.supports_transactions, (True, False))
409408
self.assertIn(connection.features.supports_stddev, (True, False))
410409
self.assertIn(connection.features.can_introspect_foreign_keys, (True, False))

tests/regressiontests/transactions_regress/tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def work():
208208
work()
209209

210210
@skipIf(connection.vendor == 'mysql' and \
211-
connection.features._mysql_storage_engine() == 'MyISAM',
211+
connection.features._mysql_storage_engine == 'MyISAM',
212212
"MyISAM MySQL storage engine doesn't support savepoints")
213213
@skipUnlessDBFeature('uses_savepoints')
214214
def test_savepoint_rollback(self):

0 commit comments

Comments
 (0)