Skip to content

Commit

Permalink
Fixed django#17760 -- Implemented callable database features as cache…
Browse files Browse the repository at this point in the history
…d 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.
  • Loading branch information
claudep committed Jun 9, 2012
1 parent 484fcd3 commit aa42357
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 48 deletions.
3 changes: 0 additions & 3 deletions django/contrib/gis/db/backends/spatialite/creation.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ def create_test_db(self, verbosity=1, autoclobber=False):
self.connection.close() self.connection.close()
self.connection.settings_dict["NAME"] = test_database_name self.connection.settings_dict["NAME"] = test_database_name


# Confirm the feature set of the test database
self.connection.features.confirm()

# Need to load the SpatiaLite initialization SQL before running `syncdb`. # Need to load the SpatiaLite initialization SQL before running `syncdb`.
self.load_spatialite_sql() self.load_spatialite_sql()


Expand Down
31 changes: 9 additions & 22 deletions django/db/backends/__init__.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from django.db import DEFAULT_DB_ALIAS from django.db import DEFAULT_DB_ALIAS
from django.db.backends import util from django.db.backends import util
from django.db.transaction import TransactionManagementError from django.db.transaction import TransactionManagementError
from django.utils.functional import cached_property
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils.timezone import is_aware from django.utils.timezone import is_aware


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


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


# Support for the DISTINCT ON clause # Support for the DISTINCT ON clause
can_distinct_on_fields = False can_distinct_on_fields = False


def __init__(self, connection): def __init__(self, connection):
self.connection = connection self.connection = connection


def confirm(self): @cached_property
"Perform manual checks of any database features that might vary between installs" def supports_transactions(self):
if not self._confirmed:
self._confirmed = True
self.supports_transactions = self._supports_transactions()
self.supports_stddev = self._supports_stddev()
self.can_introspect_foreign_keys = self._can_introspect_foreign_keys()

def _supports_transactions(self):
"Confirm support for transactions" "Confirm support for transactions"
cursor = self.connection.cursor() cursor = self.connection.cursor()
cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)') cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')
Expand All @@ -436,7 +428,8 @@ def _supports_transactions(self):
self.connection._commit() self.connection._commit()
return count == 0 return count == 0


def _supports_stddev(self): @cached_property
def supports_stddev(self):
"Confirm support for STDDEV and related stats functions" "Confirm support for STDDEV and related stats functions"
class StdDevPop(object): class StdDevPop(object):
sql_function = 'STDDEV_POP' sql_function = 'STDDEV_POP'
Expand All @@ -447,12 +440,6 @@ class StdDevPop(object):
except NotImplementedError: except NotImplementedError:
return False return False


def _can_introspect_foreign_keys(self):
"Confirm support for introspected foreign keys"
# Every database can do this reliably, except MySQL,
# which can't do it for MyISAM tables
return True



class BaseDatabaseOperations(object): class BaseDatabaseOperations(object):
""" """
Expand Down
3 changes: 0 additions & 3 deletions django/db/backends/creation.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -264,9 +264,6 @@ def create_test_db(self, verbosity=1, autoclobber=False):
self.connection.close() self.connection.close()
self.connection.settings_dict["NAME"] = test_database_name self.connection.settings_dict["NAME"] = test_database_name


# Confirm the feature set of the test database
self.connection.features.confirm()

# Report syncdb messages at one level lower than that requested. # Report syncdb messages at one level lower than that requested.
# This ensures we don't get flooded with messages during testing # This ensures we don't get flooded with messages during testing
# (unless you really ask to be flooded) # (unless you really ask to be flooded)
Expand Down
32 changes: 16 additions & 16 deletions django/db/backends/mysql/base.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from django.db.backends.mysql.creation import DatabaseCreation from django.db.backends.mysql.creation import DatabaseCreation
from django.db.backends.mysql.introspection import DatabaseIntrospection from django.db.backends.mysql.introspection import DatabaseIntrospection
from django.db.backends.mysql.validation import DatabaseValidation from django.db.backends.mysql.validation import DatabaseValidation
from django.utils.functional import cached_property
from django.utils.safestring import SafeString, SafeUnicode from django.utils.safestring import SafeString, SafeUnicode
from django.utils import timezone from django.utils import timezone


Expand Down Expand Up @@ -170,26 +171,25 @@ class DatabaseFeatures(BaseDatabaseFeatures):


def __init__(self, connection): def __init__(self, connection):
super(DatabaseFeatures, self).__init__(connection) super(DatabaseFeatures, self).__init__(connection)
self._storage_engine = None


@cached_property
def _mysql_storage_engine(self): def _mysql_storage_engine(self):
"Internal method used in Django tests. Don't rely on this from your code" "Internal method used in Django tests. Don't rely on this from your code"
if self._storage_engine is None: cursor = self.connection.cursor()
cursor = self.connection.cursor() cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)')
cursor.execute('CREATE TABLE INTROSPECT_TEST (X INT)') # This command is MySQL specific; the second column
# This command is MySQL specific; the second column # will tell you the default table type of the created
# will tell you the default table type of the created # table. Since all Django's test tables will have the same
# table. Since all Django's test tables will have the same # table type, that's enough to evaluate the feature.
# table type, that's enough to evaluate the feature. cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'")
cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'") result = cursor.fetchone()
result = cursor.fetchone() cursor.execute('DROP TABLE INTROSPECT_TEST')
cursor.execute('DROP TABLE INTROSPECT_TEST') return result[1]
self._storage_engine = result[1]
return self._storage_engine @cached_property

def can_introspect_foreign_keys(self):
def _can_introspect_foreign_keys(self):
"Confirm support for introspected foreign keys" "Confirm support for introspected foreign keys"
return self._mysql_storage_engine() != 'MyISAM' return self._mysql_storage_engine != 'MyISAM'


class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
compiler_module = "django.db.backends.mysql.compiler" compiler_module = "django.db.backends.mysql.compiler"
Expand Down
4 changes: 3 additions & 1 deletion django/db/backends/sqlite3/base.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from django.db.backends.sqlite3.creation import DatabaseCreation from django.db.backends.sqlite3.creation import DatabaseCreation
from django.db.backends.sqlite3.introspection import DatabaseIntrospection from django.db.backends.sqlite3.introspection import DatabaseIntrospection
from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import cached_property
from django.utils.safestring import SafeString from django.utils.safestring import SafeString
from django.utils import timezone from django.utils import timezone


Expand Down Expand Up @@ -86,7 +87,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_bulk_insert = True has_bulk_insert = True
can_combine_inserts_with_and_without_auto_increment_pk = True can_combine_inserts_with_and_without_auto_increment_pk = True


def _supports_stddev(self): @cached_property
def supports_stddev(self):
"""Confirm support for STDDEV and related stats functions """Confirm support for STDDEV and related stats functions
SQLite supports STDDEV as an extension package; so SQLite supports STDDEV as an extension package; so
Expand Down
3 changes: 1 addition & 2 deletions tests/regressiontests/backends/tests.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -403,8 +403,7 @@ def test_database_operations_helper_class(self):
self.assertTrue(hasattr(connection.ops, 'connection')) self.assertTrue(hasattr(connection.ops, 'connection'))
self.assertEqual(connection, connection.ops.connection) self.assertEqual(connection, connection.ops.connection)


def test_supports_needed_confirm(self): def test_cached_db_features(self):
connection.features.confirm()
self.assertIn(connection.features.supports_transactions, (True, False)) self.assertIn(connection.features.supports_transactions, (True, False))
self.assertIn(connection.features.supports_stddev, (True, False)) self.assertIn(connection.features.supports_stddev, (True, False))
self.assertIn(connection.features.can_introspect_foreign_keys, (True, False)) self.assertIn(connection.features.can_introspect_foreign_keys, (True, False))
Expand Down
2 changes: 1 addition & 1 deletion tests/regressiontests/transactions_regress/tests.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def work():
work() work()


@skipIf(connection.vendor == 'mysql' and \ @skipIf(connection.vendor == 'mysql' and \
connection.features._mysql_storage_engine() == 'MyISAM', connection.features._mysql_storage_engine == 'MyISAM',
"MyISAM MySQL storage engine doesn't support savepoints") "MyISAM MySQL storage engine doesn't support savepoints")
@skipUnlessDBFeature('uses_savepoints') @skipUnlessDBFeature('uses_savepoints')
def test_savepoint_rollback(self): def test_savepoint_rollback(self):
Expand Down

0 comments on commit aa42357

Please sign in to comment.