diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index c0a82460cf3..48f448b58f7 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -975,15 +975,6 @@ class BaseDatabaseOperations(object): """ raise NotImplementedError('subclasses of BaseDatabaseOperations may require a quote_name() method') - def quote_parameter(self, value): - """ - Returns a quoted version of the value so it's safe to use in an SQL - string. This should NOT be used to prepare SQL statements to send to - the database; it is meant for outputting SQL statements to a file - or the console for later execution by a developer/DBA. - """ - raise NotImplementedError() - def random_function_sql(self): """ Returns an SQL expression that returns a random value. diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index d35d7a38889..e79f980e215 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -311,11 +311,6 @@ class DatabaseOperations(BaseDatabaseOperations): return name # Quoting once is enough. return "`%s`" % name - def quote_parameter(self, value): - # Inner import to allow module to fail to load gracefully - import MySQLdb.converters - return MySQLdb.escape(value, MySQLdb.converters.conversions) - def random_function_sql(self): return 'RAND()' diff --git a/django/db/backends/mysql/schema.py b/django/db/backends/mysql/schema.py index dc74b2db2ad..3d544f14cc3 100644 --- a/django/db/backends/mysql/schema.py +++ b/django/db/backends/mysql/schema.py @@ -24,3 +24,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_create_pk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s PRIMARY KEY (%(columns)s)" sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY" + + def quote_value(self, value): + # Inner import to allow module to fail to load gracefully + import MySQLdb.converters + return MySQLdb.escape(value, MySQLdb.converters.conversions) diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index b86cc034461..f5496634975 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -326,16 +326,6 @@ WHEN (new.%(col_name)s IS NULL) name = name.replace('%', '%%') return name.upper() - def quote_parameter(self, value): - if isinstance(value, (datetime.date, datetime.time, datetime.datetime)): - return "'%s'" % value - elif isinstance(value, six.string_types): - return repr(value) - elif isinstance(value, bool): - return "1" if value else "0" - else: - return str(value) - def random_function_sql(self): return "DBMS_RANDOM.RANDOM" diff --git a/django/db/backends/oracle/schema.py b/django/db/backends/oracle/schema.py index 1733fc06905..447c0a231fc 100644 --- a/django/db/backends/oracle/schema.py +++ b/django/db/backends/oracle/schema.py @@ -1,5 +1,7 @@ import copy +import datetime +from django.utils import six from django.db.backends.schema import BaseDatabaseSchemaEditor from django.db.utils import DatabaseError @@ -15,6 +17,16 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_delete_column = "ALTER TABLE %(table)s DROP COLUMN %(column)s" sql_delete_table = "DROP TABLE %(table)s CASCADE CONSTRAINTS" + def quote_value(self, value): + if isinstance(value, (datetime.date, datetime.time, datetime.datetime)): + return "'%s'" % value + elif isinstance(value, six.string_types): + return repr(value) + elif isinstance(value, bool): + return "1" if value else "0" + else: + return str(value) + def delete_model(self, model): # Run superclass action super(DatabaseSchemaEditor, self).delete_model(model) @@ -92,4 +104,4 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): return self.normalize_name(for_name + "_" + suffix) def prepare_default(self, value): - return self.connection.ops.quote_parameter(value) + return self.quote_value(value) diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py index b9496868b0d..9285e6eeca1 100644 --- a/django/db/backends/postgresql_psycopg2/operations.py +++ b/django/db/backends/postgresql_psycopg2/operations.py @@ -98,11 +98,6 @@ class DatabaseOperations(BaseDatabaseOperations): return name # Quoting once is enough. return '"%s"' % name - def quote_parameter(self, value): - # Inner import so backend fails nicely if it's not present - import psycopg2 - return psycopg2.extensions.adapt(value) - def set_time_zone_sql(self): return "SET TIME ZONE %s" diff --git a/django/db/backends/postgresql_psycopg2/schema.py b/django/db/backends/postgresql_psycopg2/schema.py index 946f39d5867..ceb446c13a8 100644 --- a/django/db/backends/postgresql_psycopg2/schema.py +++ b/django/db/backends/postgresql_psycopg2/schema.py @@ -7,6 +7,11 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_delete_sequence = "DROP SEQUENCE IF EXISTS %(sequence)s CASCADE" sql_set_sequence_max = "SELECT setval('%(sequence)s', MAX(%(column)s)) FROM %(table)s" + def quote_value(self, value): + # Inner import so backend fails nicely if it's not present + import psycopg2 + return psycopg2.extensions.adapt(value) + def _alter_column_type_sql(self, table, column, type): """ Makes ALTER TYPE with SERIAL make sense. diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py index 4a3d7fe46c0..638ae08436d 100644 --- a/django/db/backends/schema.py +++ b/django/db/backends/schema.py @@ -89,7 +89,7 @@ class BaseDatabaseSchemaEditor(object): # Log the command we're running, then run it logger.debug("%s; (params %r)" % (sql, params)) if self.collect_sql: - self.collected_sql.append((sql % tuple(map(self.connection.ops.quote_parameter, params))) + ";") + self.collected_sql.append((sql % tuple(map(self.quote_value, params))) + ";") else: with self.connection.cursor() as cursor: cursor.execute(sql, params) @@ -166,6 +166,16 @@ class BaseDatabaseSchemaEditor(object): default = default() return default + def quote_value(self, value): + """ + Returns a quoted version of the value so it's safe to use in an SQL + string. This is not safe against injection from user code; it is + intended only for use in making SQL scripts or preparing default values + for particularly tricky backends (defaults are not user-defined, though, + so this is safe). + """ + raise NotImplementedError() + # Actions def create_model(self, model): diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 2adfbacaa95..3bb85e042dd 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -215,25 +215,6 @@ class DatabaseOperations(BaseDatabaseOperations): return name # Quoting once is enough. return '"%s"' % name - def quote_parameter(self, value): - # Inner import to allow nice failure for backend if not present - import _sqlite3 - try: - value = _sqlite3.adapt(value) - except _sqlite3.ProgrammingError: - pass - # Manual emulation of SQLite parameter quoting - if isinstance(value, type(True)): - return str(int(value)) - elif isinstance(value, six.integer_types): - return str(value) - elif isinstance(value, six.string_types): - return '"%s"' % six.text_type(value) - elif value is None: - return "NULL" - else: - raise ValueError("Cannot quote parameter value %r" % value) - def no_limit_value(self): return -1 diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py index b6680e730d4..279ac4b2038 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -1,3 +1,4 @@ +from django.utils import six from django.apps.registry import Apps from django.db.backends.schema import BaseDatabaseSchemaEditor from django.db.models.fields.related import ManyToManyField @@ -8,6 +9,25 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_delete_table = "DROP TABLE %(table)s" sql_create_inline_fk = "REFERENCES %(to_table)s (%(to_column)s)" + def quote_value(self, value): + # Inner import to allow nice failure for backend if not present + import _sqlite3 + try: + value = _sqlite3.adapt(value) + except _sqlite3.ProgrammingError: + pass + # Manual emulation of SQLite parameter quoting + if isinstance(value, type(True)): + return str(int(value)) + elif isinstance(value, six.integer_types): + return str(value) + elif isinstance(value, six.string_types): + return '"%s"' % six.text_type(value) + elif value is None: + return "NULL" + else: + raise ValueError("Cannot quote parameter value %r" % value) + def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=[], rename_fields=[], override_uniques=None): """ Shortcut to transform a model from old_model into new_model @@ -31,7 +51,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): body[field.name] = field # If there's a default, insert it into the copy map if field.has_default(): - mapping[field.column] = self.connection.ops.quote_parameter( + mapping[field.column] = self.quote_value( field.get_default() ) # Add in any altered fields