From 1a8f9b2b97d2549fe28c2d9090fece3f29f029fa Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 20 Aug 2007 02:20:33 +0000 Subject: [PATCH] Implemented BaseDatabaseFeatures and changed all code to access it -- connection.features.foo instead of backend.foo git-svn-id: http://code.djangoproject.com/svn/django/trunk@5974 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/commands/syncdb.py | 2 +- django/core/management/sql.py | 24 +++++++++---------- django/db/backends/__init__.py | 10 ++++++++ django/db/backends/ado_mssql/base.py | 15 ++++-------- django/db/backends/dummy/base.py | 8 +++---- django/db/backends/mysql/base.py | 15 ++++-------- django/db/backends/mysql_old/base.py | 15 ++++-------- django/db/backends/oracle/base.py | 20 ++++++++-------- django/db/backends/postgresql/base.py | 15 ++++-------- .../db/backends/postgresql_psycopg2/base.py | 15 ++++-------- django/db/backends/sqlite3/base.py | 15 ++++-------- django/db/models/query.py | 6 ++--- 12 files changed, 69 insertions(+), 91 deletions(-) diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index dfa2323310c..0265cd22676 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -34,7 +34,7 @@ class Command(NoArgsCommand): # Get a list of all existing database tables, # so we know what needs to be added. table_list = table_list() - if backend.uses_case_insensitive_names: + if connection.features.uses_case_insensitive_names: table_name_converter = str.upper else: table_name_converter = lambda x: x diff --git a/django/core/management/sql.py b/django/core/management/sql.py index add4632920e..0857c4a9e7d 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -15,12 +15,12 @@ def table_list(): def installed_models(table_list): "Returns a set of all models that are installed, given a list of existing table names." - from django.db import backend, models + from django.db import connection, models all_models = [] for app in models.get_apps(): for model in models.get_models(app): all_models.append(model) - if backend.uses_case_insensitive_names: + if connection.features.uses_case_insensitive_names: converter = lambda x: x.upper() else: converter = lambda x: x @@ -110,7 +110,7 @@ def sql_delete(app, style): table_names = introspection.get_table_list(cursor) else: table_names = [] - if backend.uses_case_insensitive_names: + if connection.features.uses_case_insensitive_names: table_name_converter = str.upper else: table_name_converter = lambda x: x @@ -138,7 +138,7 @@ def sql_delete(app, style): # Drop the table now output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), style.SQL_TABLE(qn(model._meta.db_table)))) - if backend.supports_constraints and model in references_to_delete: + if connection.features.supports_constraints and model in references_to_delete: for rel_class, f in references_to_delete[model]: table = rel_class._meta.db_table col = f.column @@ -232,11 +232,11 @@ def sql_model_create(model, style, known_models=set()): field_output = [style.SQL_FIELD(qn(f.column)), style.SQL_COLTYPE(col_type)] field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or ''))) - if f.unique and (not f.primary_key or backend.allows_unique_and_pk): + if f.unique and (not f.primary_key or connection.features.allows_unique_and_pk): field_output.append(style.SQL_KEYWORD('UNIQUE')) if f.primary_key: field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) - if tablespace and backend.supports_tablespaces and (f.unique or f.primary_key) and backend.autoindexes_primary_keys: + if tablespace and connection.features.supports_tablespaces and (f.unique or f.primary_key) and connection.features.autoindexes_primary_keys: # We must specify the index tablespace inline, because we # won't be generating a CREATE INDEX statement for this field. field_output.append(connection.ops.tablespace_sql(tablespace, inline=True)) @@ -264,7 +264,7 @@ def sql_model_create(model, style, known_models=set()): for i, line in enumerate(table_output): # Combine and add commas. full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) full_statement.append(')') - if opts.db_tablespace and backend.supports_tablespaces: + if opts.db_tablespace and connection.features.supports_tablespaces: full_statement.append(connection.ops.tablespace_sql(opts.db_tablespace)) full_statement.append(';') final_output.append('\n'.join(full_statement)) @@ -287,7 +287,7 @@ def sql_for_pending_references(model, style, pending_references): qn = connection.ops.quote_name final_output = [] - if backend.supports_constraints: + if connection.features.supports_constraints: opts = model._meta if model in pending_references: for rel_class, f in pending_references[model]: @@ -316,7 +316,7 @@ def many_to_many_sql_for_model(model, style): for f in opts.many_to_many: if not isinstance(f.rel, generic.GenericRel): tablespace = f.db_tablespace or opts.db_tablespace - if tablespace and backend.supports_tablespaces and backend.autoindexes_primary_keys: + if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys: tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True) else: tablespace_sql = '' @@ -347,7 +347,7 @@ def many_to_many_sql_for_model(model, style): style.SQL_FIELD(qn(f.m2m_reverse_name())), tablespace_sql)) table_output.append(')') - if opts.db_tablespace and backend.supports_tablespaces: + if opts.db_tablespace and connection.features.supports_tablespaces: # f.db_tablespace is only for indices, so ignore its value here. table_output.append(connection.ops.tablespace_sql(opts.db_tablespace)) table_output.append(';') @@ -395,10 +395,10 @@ def sql_indexes_for_model(model, style): qn = connection.ops.quote_name for f in model._meta.fields: - if f.db_index and not ((f.primary_key or f.unique) and backend.autoindexes_primary_keys): + if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys): unique = f.unique and 'UNIQUE ' or '' tablespace = f.db_tablespace or model._meta.db_tablespace - if tablespace and backend.supports_tablespaces: + if tablespace and connection.features.supports_tablespaces: tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace) else: tablespace_sql = '' diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 632dd36639a..2415d453ce6 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -39,6 +39,16 @@ class BaseDatabaseWrapper(local): from django.db.backends import util return util.CursorDebugWrapper(cursor, self) +class BaseDatabaseFeatures(object): + allows_group_by_ordinal = True + allows_unique_and_pk = True + autoindexes_primary_keys = True + needs_datetime_string_cast = True + needs_upper_for_iops = False + supports_constraints = True + supports_tablespaces = False + uses_case_insensitive_names = False + class BaseDatabaseOperations(object): """ This class encapsulates all backend-specific differences, such as the way diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py index fe7daaf765c..60109b72263 100644 --- a/django/db/backends/ado_mssql/base.py +++ b/django/db/backends/ado_mssql/base.py @@ -4,7 +4,7 @@ ADO MSSQL database backend for Django. Requires adodbapi 2.0.1: http://adodbapi.sourceforge.net/ """ -from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util try: import adodbapi as Database except ImportError, e: @@ -48,6 +48,9 @@ def variantToPython(variant, adType): return res Database.convertVariantToPython = variantToPython +class DatabaseFeatures(BaseDatabaseFeatures): + supports_tablespaces = True + class DatabaseOperations(BaseDatabaseOperations): def date_extract_sql(self, lookup_type, field_name): return "DATEPART(%s, %s)" % (lookup_type, field_name) @@ -79,6 +82,7 @@ class DatabaseOperations(BaseDatabaseOperations): return "ON %s" % self.quote_name(tablespace) class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() ops = DatabaseOperations() def _cursor(self, settings): @@ -93,15 +97,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.connection = Database.connect(conn_string) return self.connection.cursor() -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = True -needs_datetime_string_cast = True -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = True -uses_case_insensitive_names = False - OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py index 94fa3e1c232..671c9d72912 100644 --- a/django/db/backends/dummy/base.py +++ b/django/db/backends/dummy/base.py @@ -21,12 +21,13 @@ class DatabaseError(Exception): class IntegrityError(DatabaseError): pass -class DatabaseOperations(object): +class ComplainOnGetattr(object): def __getattr__(self, *args, **kwargs): complain() class DatabaseWrapper(object): - ops = DatabaseOperations() + ops = ComplainOnGetattr() + features = ComplainOnGetattr() cursor = complain _commit = complain _rollback = ignore @@ -37,7 +38,4 @@ class DatabaseWrapper(object): def close(self): pass # close() -supports_constraints = False -supports_tablespaces = False - OPERATOR_MAPPING = {} diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 1501508b4c0..079bf36eb09 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -4,7 +4,7 @@ MySQL database backend for Django. Requires MySQLdb: http://sourceforge.net/projects/mysql-python """ -from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util try: import MySQLdb as Database except ImportError, e: @@ -53,6 +53,9 @@ server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})') # standard util.CursorDebugWrapper can be used. Also, using sql_mode # TRADITIONAL will automatically cause most warnings to be treated as errors. +class DatabaseFeatures(BaseDatabaseFeatures): + autoindexes_primary_keys = False + class DatabaseOperations(BaseDatabaseOperations): def date_extract_sql(self, lookup_type, field_name): # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html @@ -116,6 +119,7 @@ class DatabaseOperations(BaseDatabaseOperations): return [] class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() ops = DatabaseOperations() def __init__(self, **kwargs): @@ -175,15 +179,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.server_version = tuple([int(x) for x in m.groups()]) return self.server_version -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = False -needs_datetime_string_cast = True # MySQLdb requires a typecast for dates -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = False -uses_case_insensitive_names = False - OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py index 296a576e269..e4ff1c64b26 100644 --- a/django/db/backends/mysql_old/base.py +++ b/django/db/backends/mysql_old/base.py @@ -4,7 +4,7 @@ MySQL database backend for Django. Requires MySQLdb: http://sourceforge.net/projects/mysql-python """ -from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util from django.utils.encoding import force_unicode try: import MySQLdb as Database @@ -63,6 +63,9 @@ class MysqlDebugWrapper: else: return getattr(self.cursor, attr) +class DatabaseFeatures(BaseDatabaseFeatures): + autoindexes_primary_keys = False + class DatabaseOperations(BaseDatabaseOperations): def date_extract_sql(self, lookup_type, field_name): # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html @@ -126,6 +129,7 @@ class DatabaseOperations(BaseDatabaseOperations): return [] class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() ops = DatabaseOperations() def __init__(self, **kwargs): @@ -194,15 +198,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.server_version = tuple([int(x) for x in m.groups()]) return self.server_version -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = False -needs_datetime_string_cast = True # MySQLdb requires a typecast for dates -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = False -uses_case_insensitive_names = False - OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 842efde35c8..f4f602ff0d8 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -4,7 +4,7 @@ Oracle database backend for Django. Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/ """ -from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util from django.utils.datastructures import SortedDict from django.utils.encoding import smart_str, force_unicode import datetime @@ -21,6 +21,14 @@ except ImportError, e: DatabaseError = Database.Error IntegrityError = Database.IntegrityError +class DatabaseFeatures(BaseDatabaseFeatures): + allows_group_by_ordinal = False + allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259) + needs_datetime_string_cast = False + needs_upper_for_iops = True + supports_tablespaces = True + uses_case_insensitive_names = True + class DatabaseOperations(BaseDatabaseOperations): def autoinc_sql(self, table): # To simulate auto-incrementing primary keys in Oracle, we have to @@ -128,6 +136,7 @@ class DatabaseOperations(BaseDatabaseOperations): return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), self.quote_name(tablespace)) class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() ops = DatabaseOperations() def _valid_connection(self): @@ -151,15 +160,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'") return cursor -allows_group_by_ordinal = False -allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259) -autoindexes_primary_keys = True -needs_datetime_string_cast = False -needs_upper_for_iops = True -supports_constraints = True -supports_tablespaces = True -uses_case_insensitive_names = True - class FormatStylePlaceholderCursor(Database.Cursor): """ Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 094670cbc6e..755a34231ce 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -5,7 +5,7 @@ Requires psycopg 1: http://initd.org/projects/psycopg1 """ from django.utils.encoding import smart_str, smart_unicode -from django.db.backends import BaseDatabaseWrapper, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, util from django.db.backends.postgresql.operations import DatabaseOperations try: import psycopg as Database @@ -56,7 +56,11 @@ class UnicodeCursorWrapper(object): else: return getattr(self.cursor, attr) +class DatabaseFeatures(BaseDatabaseFeatures): + pass # This backend uses all the defaults. + class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() ops = DatabaseOperations() def _cursor(self, settings): @@ -87,15 +91,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.ops.postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] return cursor -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = True -needs_datetime_string_cast = True -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = False -uses_case_insensitive_names = False - def typecast_string(s): """ Cast all returned strings to unicode strings. diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 87b41877a6c..0bcfd46ac4d 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -4,7 +4,7 @@ PostgreSQL database backend for Django. Requires psycopg 2: http://initd.org/projects/psycopg2 """ -from django.db.backends import BaseDatabaseWrapper +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures from django.db.backends.postgresql.operations import DatabaseOperations try: import psycopg2 as Database @@ -18,7 +18,11 @@ IntegrityError = Database.IntegrityError psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) +class DatabaseFeatures(BaseDatabaseFeatures): + needs_datetime_string_cast = False + class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() ops = DatabaseOperations() def _cursor(self, settings): @@ -49,15 +53,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.ops.postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] return cursor -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = True -needs_datetime_string_cast = False -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = False -uses_case_insensitive_names = False - OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'ILIKE %s', diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 96f88dfa172..2ffdefa73ec 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -2,7 +2,7 @@ SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/). """ -from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util try: try: from sqlite3 import dbapi2 as Database @@ -34,6 +34,9 @@ Database.register_converter("TIMESTAMP", util.typecast_timestamp) Database.register_converter("decimal", util.typecast_decimal) Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal) +class DatabaseFeatures(BaseDatabaseFeatures): + supports_constraints = False + class DatabaseOperations(BaseDatabaseOperations): def date_extract_sql(self, lookup_type, field_name): # sqlite doesn't support extract, so we fake it with the user-defined @@ -70,6 +73,7 @@ class DatabaseOperations(BaseDatabaseOperations): return sql class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() ops = DatabaseOperations() def _cursor(self, settings): @@ -111,15 +115,6 @@ class SQLiteCursorWrapper(Database.Cursor): def convert_query(self, query, num_params): return query % tuple("?" * num_params) -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = True -needs_datetime_string_cast = True -needs_upper_for_iops = False -supports_constraints = False -supports_tablespaces = False -uses_case_insensitive_names = False - def _sqlite_extract(lookup_type, dt): try: dt = util.typecast_timestamp(dt) diff --git a/django/db/models/query.py b/django/db/models/query.py index 8cf45106229..767942c6a57 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -651,7 +651,7 @@ class DateQuerySet(QuerySet): table_name = qn(self.model._meta.db_table) field_name = qn(self._field.column) - if backend.allows_group_by_ordinal: + if connection.features.allows_group_by_ordinal: group_by = '1' else: group_by = connection.ops.date_trunc_sql(self._kind, '%s.%s' % (table_name, field_name)) @@ -663,7 +663,7 @@ class DateQuerySet(QuerySet): cursor.execute(sql, params) has_resolve_columns = hasattr(self, 'resolve_columns') - needs_datetime_string_cast = backend.needs_datetime_string_cast + needs_datetime_string_cast = connection.features.needs_datetime_string_cast dates = [] # It would be better to use self._field here instead of DateTimeField(), # but in Oracle that will result in a list of datetime.date instead of @@ -796,7 +796,7 @@ def get_where_clause(lookup_type, table_prefix, field_name, value, db_type): else: field_cast_sql = '%s%s' field_sql = field_cast_sql % (table_prefix, field_name) - if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith') and backend.needs_upper_for_iops: + if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith') and connection.features.needs_upper_for_iops: format = 'UPPER(%s) %s' else: format = '%s %s'