diff --git a/django/core/management/sql.py b/django/core/management/sql.py index d9736262a1..90599b9a4d 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -213,7 +213,7 @@ def sql_model_create(model, style, known_models=set()): Returns the SQL required to create a single model, as a tuple of: (list_of_sql, pending_references_dict) """ - from django.db import backend, models + from django.db import backend, connection, models opts = model._meta final_output = [] @@ -267,9 +267,9 @@ def sql_model_create(model, style, known_models=set()): full_statement.append(';') final_output.append('\n'.join(full_statement)) - if opts.has_auto_field and hasattr(backend, 'get_autoinc_sql'): - # Add any extra SQL needed to support auto-incrementing primary keys - autoinc_sql = backend.get_autoinc_sql(opts.db_table) + if opts.has_auto_field: + # Add any extra SQL needed to support auto-incrementing primary keys. + autoinc_sql = connection.ops.autoinc_sql(opts.db_table) if autoinc_sql: for stmt in autoinc_sql: final_output.append(stmt) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 91ad9a3d39..76788bb84b 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -6,6 +6,10 @@ except ImportError: from django.utils._threading_local import local class BaseDatabaseWrapper(local): + """ + Represents a database connection. + """ + ops = None def __init__(self, **kwargs): self.connection = None self.queries = [] @@ -34,3 +38,18 @@ class BaseDatabaseWrapper(local): def make_debug_cursor(self, cursor): from django.db.backends import util return util.CursorDebugWrapper(cursor, self) + +class BaseDatabaseOperations(object): + """ + This class encapsulates all backend-specific differences, such as the way + a backend performs ordering or calculates the ID of a recently-inserted + row. + """ + def autoinc_sql(self, table): + """ + Returns any SQL needed to support auto-incrementing primary keys, or + None if no SQL is necessary. + + This SQL is executed when a table is created. + """ + return None diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py index 27b796ce56..b9d1bf1ed6 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, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util try: import adodbapi as Database except ImportError, e: @@ -48,7 +48,12 @@ def variantToPython(variant, adType): return res Database.convertVariantToPython = variantToPython +class DatabaseOperations(BaseDatabaseOperations): + pass + class DatabaseWrapper(BaseDatabaseWrapper): + ops = DatabaseOperations() + def _cursor(self, settings): if self.connection is None: if settings.DATABASE_NAME == '' or settings.DATABASE_USER == '': @@ -130,9 +135,6 @@ def get_start_transaction_sql(): def get_tablespace_sql(tablespace, inline=False): return "ON %s" % quote_name(tablespace) -def get_autoinc_sql(table): - return None - def get_sql_flush(style, tables, sequences): """Return a list of SQL statements required to remove all data from all tables in the database (without actually removing the tables diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py index f47cbdf3d4..4f4a3caa43 100644 --- a/django/db/backends/dummy/base.py +++ b/django/db/backends/dummy/base.py @@ -21,7 +21,12 @@ class DatabaseError(Exception): class IntegrityError(DatabaseError): pass -class DatabaseWrapper: +class DatabaseOperations(object): + def __getattr__(self, *args, **kwargs): + complain() + +class DatabaseWrapper(object): + ops = DatabaseOperations() cursor = complain _commit = complain _rollback = ignore @@ -50,7 +55,6 @@ get_drop_foreignkey_sql = complain get_pk_default_value = complain get_max_name_length = ignore get_start_transaction_sql = complain -get_autoinc_sql = complain get_sql_flush = complain get_sql_sequence_reset = complain diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 22c819e3b7..0051fe2072 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, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util try: import MySQLdb as Database except ImportError, e: @@ -53,7 +53,12 @@ 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 DatabaseOperations(BaseDatabaseOperations): + pass + class DatabaseWrapper(BaseDatabaseWrapper): + ops = DatabaseOperations() + def __init__(self, **kwargs): super(DatabaseWrapper, self).__init__(**kwargs) self.server_version = None @@ -181,9 +186,6 @@ def get_max_name_length(): def get_start_transaction_sql(): return "BEGIN;" -def get_autoinc_sql(table): - return None - def get_sql_flush(style, tables, sequences): """Return a list of SQL statements required to remove all data from all tables in the database (without actually removing the tables diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py index 9abc1bf129..cc19782e7d 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, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util from django.utils.encoding import force_unicode try: import MySQLdb as Database @@ -63,7 +63,12 @@ class MysqlDebugWrapper: else: return getattr(self.cursor, attr) +class DatabaseOperations(BaseDatabaseOperations): + pass + class DatabaseWrapper(BaseDatabaseWrapper): + ops = DatabaseOperations() + def __init__(self, **kwargs): super(DatabaseWrapper, self).__init__(**kwargs) self.server_version = None @@ -200,9 +205,6 @@ def get_max_name_length(): def get_start_transaction_sql(): return "BEGIN;" -def get_autoinc_sql(table): - return None - def get_sql_flush(style, tables, sequences): """Return a list of SQL statements required to remove all data from all tables in the database (without actually removing the tables diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index fbbe11eb2d..90efc4578a 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, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util from django.utils.datastructures import SortedDict from django.utils.encoding import smart_str, force_unicode import datetime @@ -21,7 +21,26 @@ except ImportError, e: DatabaseError = Database.Error IntegrityError = Database.IntegrityError +class DatabaseOperations(BaseDatabaseOperations): + def autoinc_sql(self, table): + # To simulate auto-incrementing primary keys in Oracle, we have to + # create a sequence and a trigger. + sq_name = get_sequence_name(table) + tr_name = get_trigger_name(table) + sequence_sql = 'CREATE SEQUENCE %s;' % sq_name + trigger_sql = """ + CREATE OR REPLACE TRIGGER %s + BEFORE INSERT ON %s + FOR EACH ROW + WHEN (new.id IS NULL) + BEGIN + SELECT %s.nextval INTO :new.id FROM dual; + END;/""" % (tr_name, quote_name(table), sq_name) + return sequence_sql, trigger_sql + class DatabaseWrapper(BaseDatabaseWrapper): + ops = DatabaseOperations() + def _valid_connection(self): return self.connection is not None @@ -187,22 +206,6 @@ def get_start_transaction_sql(): def get_tablespace_sql(tablespace, inline=False): return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), quote_name(tablespace)) -def get_autoinc_sql(table): - # To simulate auto-incrementing primary keys in Oracle, we have to - # create a sequence and a trigger. - sq_name = get_sequence_name(table) - tr_name = get_trigger_name(table) - sequence_sql = 'CREATE SEQUENCE %s;' % sq_name - trigger_sql = """CREATE OR REPLACE TRIGGER %s - BEFORE INSERT ON %s - FOR EACH ROW - WHEN (new.id IS NULL) - BEGIN - SELECT %s.nextval INTO :new.id FROM dual; - END; - /""" % (tr_name, quote_name(table), sq_name) - return sequence_sql, trigger_sql - def get_drop_sequence(table): return "DROP SEQUENCE %s;" % quote_name(get_sequence_name(table)) diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 0238ce8c57..1c6f5ec978 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, BaseDatabaseOperations, util try: import psycopg as Database except ImportError, e: @@ -57,7 +57,12 @@ class UnicodeCursorWrapper(object): postgres_version = None +class DatabaseOperations(BaseDatabaseOperations): + pass + class DatabaseWrapper(BaseDatabaseWrapper): + ops = DatabaseOperations() + def _cursor(self, settings): set_tz = False if self.connection is None: @@ -157,9 +162,6 @@ def get_max_name_length(): def get_start_transaction_sql(): return "BEGIN;" -def get_autoinc_sql(table): - return None - def get_sql_flush(style, tables, sequences): """Return a list of SQL statements required to remove all data from all tables in the database (without actually removing the tables diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index dcf9f01759..cf1d33bd35 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, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util try: import psycopg2 as Database import psycopg2.extensions @@ -19,7 +19,12 @@ psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) postgres_version = None +class DatabaseOperations(BaseDatabaseOperations): + pass + class DatabaseWrapper(BaseDatabaseWrapper): + ops = DatabaseOperations() + def _cursor(self, settings): set_tz = False if self.connection is None: @@ -111,9 +116,6 @@ def get_max_name_length(): def get_start_transaction_sql(): return "BEGIN;" -def get_autoinc_sql(table): - return None - def get_sql_flush(style, tables, sequences): """Return a list of SQL statements required to remove all data from all tables in the database (without actually removing the tables diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 9f4df08f07..5579d9d9ee 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, util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseOperations, util try: try: from sqlite3 import dbapi2 as Database @@ -34,7 +34,12 @@ Database.register_converter("TIMESTAMP", util.typecast_timestamp) Database.register_converter("decimal", util.typecast_decimal) Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal) +class DatabaseOperations(BaseDatabaseOperations): + pass + class DatabaseWrapper(BaseDatabaseWrapper): + ops = DatabaseOperations() + def _cursor(self, settings): if self.connection is None: kwargs = { @@ -143,9 +148,6 @@ def get_max_name_length(): def get_start_transaction_sql(): return "BEGIN;" -def get_autoinc_sql(table): - return None - def get_sql_flush(style, tables, sequences): """ Return a list of SQL statements required to remove all data from