summaryrefslogtreecommitdiff
path: root/django/db/backends
diff options
context:
space:
mode:
authorChristopher Long <indirecthit@gmail.com>2007-06-17 22:18:54 +0000
committerChristopher Long <indirecthit@gmail.com>2007-06-17 22:18:54 +0000
commitae22b6d403dcf25098c77f0dfcf59ae58b186461 (patch)
treec37fc631e99a7e4d909d6b6d236f495003731ea7 /django/db/backends
parent0cf7bc439129c66df8d64601e885f83b256b4f25 (diff)
per-object-permissions: Merged to trunk [5486] NOTE: Not fully tested, will be working on this over the next few weeks.
git-svn-id: http://code.djangoproject.com/svn/django/branches/per-object-permissions@5488 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/db/backends')
-rw-r--r--django/db/backends/ado_mssql/base.py27
-rw-r--r--django/db/backends/ado_mssql/creation.py4
-rw-r--r--django/db/backends/dummy/base.py12
-rw-r--r--django/db/backends/mysql/base.py118
-rw-r--r--django/db/backends/mysql/client.py29
-rw-r--r--django/db/backends/mysql/creation.py4
-rw-r--r--django/db/backends/mysql/introspection.py4
-rw-r--r--django/db/backends/mysql_old/__init__.py0
-rw-r--r--django/db/backends/mysql_old/base.py240
-rw-r--r--django/db/backends/mysql_old/client.py14
-rw-r--r--django/db/backends/mysql_old/creation.py30
-rw-r--r--django/db/backends/mysql_old/introspection.py95
-rw-r--r--django/db/backends/oracle/base.py27
-rw-r--r--django/db/backends/oracle/creation.py4
-rw-r--r--django/db/backends/oracle/introspection.py2
-rw-r--r--django/db/backends/postgresql/base.py139
-rw-r--r--django/db/backends/postgresql/creation.py4
-rw-r--r--django/db/backends/postgresql/introspection.py3
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py102
-rw-r--r--django/db/backends/postgresql_psycopg2/introspection.py3
-rw-r--r--django/db/backends/sqlite3/base.py39
-rw-r--r--django/db/backends/sqlite3/creation.py4
-rw-r--r--django/db/backends/util.py17
23 files changed, 846 insertions, 75 deletions
diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py
index 72d2fe083e..52363ed705 100644
--- a/django/db/backends/ado_mssql/base.py
+++ b/django/db/backends/ado_mssql/base.py
@@ -17,6 +17,7 @@ except ImportError:
mx = None
DatabaseError = Database.DatabaseError
+IntegrityError = Database.IntegrityError
# We need to use a special Cursor class because adodbapi expects question-mark
# param style, but Django expects "%s". This cursor converts question marks to
@@ -76,10 +77,11 @@ class DatabaseWrapper(local):
return cursor
def _commit(self):
- return self.connection.commit()
+ if self.connection is not None:
+ return self.connection.commit()
def _rollback(self):
- if self.connection:
+ if self.connection is not None:
return self.connection.rollback()
def close(self):
@@ -125,6 +127,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql():
return "RAND()"
+def get_deferrable_sql():
+ return " DEFERRABLE INITIALLY DEFERRED"
+
def get_fulltext_search_sql(field_name):
raise NotImplementedError
@@ -134,6 +139,24 @@ def get_drop_foreignkey_sql():
def get_pk_default_value():
return "DEFAULT"
+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
+ themselves) and put the database in an empty 'initial' state
+ """
+ # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
+ # TODO - SQL not actually tested against ADO MSSQL yet!
+ # TODO - autoincrement indices reset required? See other get_sql_flush() implementations
+ sql_list = ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables]
+
+def get_sql_sequence_reset(style, model_list):
+ "Returns a list of the SQL statements to reset sequences for the given models."
+ # No sequence reset required
+ return []
+
OPERATOR_MAPPING = {
'exact': '= %s',
'iexact': 'LIKE %s',
diff --git a/django/db/backends/ado_mssql/creation.py b/django/db/backends/ado_mssql/creation.py
index 4d85d27ea5..a1098ea43e 100644
--- a/django/db/backends/ado_mssql/creation.py
+++ b/django/db/backends/ado_mssql/creation.py
@@ -5,9 +5,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'smalldatetime',
'DateTimeField': 'smalldatetime',
+ 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar(100)',
'FilePathField': 'varchar(100)',
- 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'FloatField': 'double precision',
'ImageField': 'varchar(100)',
'IntegerField': 'int',
'IPAddressField': 'char(15)',
@@ -21,6 +22,5 @@ DATA_TYPES = {
'SmallIntegerField': 'smallint',
'TextField': 'text',
'TimeField': 'time',
- 'URLField': 'varchar(200)',
'USStateField': 'varchar(2)',
}
diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py
index f98afc48bb..d0ec897407 100644
--- a/django/db/backends/dummy/base.py
+++ b/django/db/backends/dummy/base.py
@@ -12,13 +12,19 @@ from django.core.exceptions import ImproperlyConfigured
def complain(*args, **kwargs):
raise ImproperlyConfigured, "You haven't set the DATABASE_ENGINE setting yet."
+def ignore(*args, **kwargs):
+ pass
+
class DatabaseError(Exception):
pass
+class IntegrityError(DatabaseError):
+ pass
+
class DatabaseWrapper:
cursor = complain
_commit = complain
- _rollback = complain
+ _rollback = ignore
def __init__(self, **kwargs):
pass
@@ -36,6 +42,10 @@ get_date_extract_sql = complain
get_date_trunc_sql = complain
get_limit_offset_sql = complain
get_random_function_sql = complain
+get_deferrable_sql = complain
get_fulltext_search_sql = complain
get_drop_foreignkey_sql = complain
+get_sql_flush = complain
+get_sql_sequence_reset = complain
+
OPERATOR_MAPPING = {}
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index e7e060e6c2..d4cb1fa964 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -10,19 +10,34 @@ try:
except ImportError, e:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "Error loading MySQLdb module: %s" % e
+
+# We want version (1, 2, 1, 'final', 2) or later. We can't just use
+# lexicographic ordering in this check because then (1, 2, 1, 'gamma')
+# inadvertently passes the version test.
+version = Database.version_info
+if (version < (1,2,1) or (version[:3] == (1, 2, 1) and
+ (len(version) < 5 or version[3] != 'final' or version[4] < 2))):
+ raise ImportError, "MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__
+
from MySQLdb.converters import conversions
from MySQLdb.constants import FIELD_TYPE
import types
import re
DatabaseError = Database.DatabaseError
+IntegrityError = Database.IntegrityError
+# MySQLdb-1.2.1 supports the Python boolean type, and only uses datetime
+# module for time-related columns; older versions could have used mx.DateTime
+# or strings if there were no datetime module. However, MySQLdb still returns
+# TIME columns as timedelta -- they are more like timedelta in terms of actual
+# behavior as they are signed and include days -- and Django expects time, so
+# we still need to override that.
django_conversions = conversions.copy()
django_conversions.update({
- types.BooleanType: util.rev_typecast_boolean,
- FIELD_TYPE.DATETIME: util.typecast_timestamp,
- FIELD_TYPE.DATE: util.typecast_date,
FIELD_TYPE.TIME: util.typecast_time,
+ FIELD_TYPE.DECIMAL: util.typecast_decimal,
+ FIELD_TYPE.NEWDECIMAL: util.typecast_decimal,
})
# This should match the numerical portion of the version numbers (we can treat
@@ -31,31 +46,12 @@ django_conversions.update({
# http://dev.mysql.com/doc/refman/5.0/en/news.html .
server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
-# This is an extra debug layer over MySQL queries, to display warnings.
-# It's only used when DEBUG=True.
-class MysqlDebugWrapper:
- def __init__(self, cursor):
- self.cursor = cursor
-
- def execute(self, sql, params=()):
- try:
- return self.cursor.execute(sql, params)
- except Database.Warning, w:
- self.cursor.execute("SHOW WARNINGS")
- raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
-
- def executemany(self, sql, param_list):
- try:
- return self.cursor.executemany(sql, param_list)
- except Database.Warning, w:
- self.cursor.execute("SHOW WARNINGS")
- raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
-
- def __getattr__(self, attr):
- if self.__dict__.has_key(attr):
- return self.__dict__[attr]
- else:
- return getattr(self.cursor, attr)
+# MySQLdb-1.2.1 and newer automatically makes use of SHOW WARNINGS on
+# MySQL-4.1 and newer, so the MysqlDebugWrapper is unnecessary. Since the
+# point is to raise Warnings as exceptions, this can be done with the Python
+# warning module, and this is setup when the connection is created, and the
+# standard util.CursorDebugWrapper can be used. Also, using sql_mode
+# TRADITIONAL will automatically cause most warnings to be treated as errors.
try:
# Only exists in Python 2.4+
@@ -83,33 +79,41 @@ class DatabaseWrapper(local):
def cursor(self):
from django.conf import settings
+ from warnings import filterwarnings
if not self._valid_connection():
kwargs = {
- 'user': settings.DATABASE_USER,
- 'db': settings.DATABASE_NAME,
- 'passwd': settings.DATABASE_PASSWORD,
'conv': django_conversions,
+ 'charset': 'utf8',
+ 'use_unicode': False,
}
+ if settings.DATABASE_USER:
+ kwargs['user'] = settings.DATABASE_USER
+ if settings.DATABASE_NAME:
+ kwargs['db'] = settings.DATABASE_NAME
+ if settings.DATABASE_PASSWORD:
+ kwargs['passwd'] = settings.DATABASE_PASSWORD
if settings.DATABASE_HOST.startswith('/'):
kwargs['unix_socket'] = settings.DATABASE_HOST
- else:
+ elif settings.DATABASE_HOST:
kwargs['host'] = settings.DATABASE_HOST
if settings.DATABASE_PORT:
kwargs['port'] = int(settings.DATABASE_PORT)
kwargs.update(self.options)
self.connection = Database.connect(**kwargs)
- cursor = self.connection.cursor()
- if self.connection.get_server_info() >= '4.1':
- cursor.execute("SET NAMES 'utf8'")
+ cursor = self.connection.cursor()
+ else:
+ cursor = self.connection.cursor()
if settings.DEBUG:
- return util.CursorDebugWrapper(MysqlDebugWrapper(cursor), self)
+ filterwarnings("error", category=Database.Warning)
+ return util.CursorDebugWrapper(cursor, self)
return cursor
def _commit(self):
- self.connection.commit()
+ if self.connection is not None:
+ self.connection.commit()
def _rollback(self):
- if self.connection:
+ if self.connection is not None:
try:
self.connection.rollback()
except Database.NotSupportedError:
@@ -172,6 +176,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql():
return "RAND()"
+def get_deferrable_sql():
+ return ""
+
def get_fulltext_search_sql(field_name):
return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
@@ -181,6 +188,41 @@ def get_drop_foreignkey_sql():
def get_pk_default_value():
return "DEFAULT"
+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
+ themselves) and put the database in an empty 'initial' state
+
+ """
+ # NB: The generated SQL below is specific to MySQL
+ # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
+ # to clear all tables of all data
+ if tables:
+ sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + \
+ ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables] + \
+ ['SET FOREIGN_KEY_CHECKS = 1;']
+
+ # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
+ # to reset sequence indices
+ sql.extend(["%s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('TABLE'),
+ style.SQL_TABLE(quote_name(sequence['table'])),
+ style.SQL_KEYWORD('AUTO_INCREMENT'),
+ style.SQL_FIELD('= 1'),
+ ) for sequence in sequences])
+ return sql
+ else:
+ return []
+
+def get_sql_sequence_reset(style, model_list):
+ "Returns a list of the SQL statements to reset sequences for the given models."
+ # No sequence reset required
+ return []
+
OPERATOR_MAPPING = {
'exact': '= %s',
'iexact': 'LIKE %s',
diff --git a/django/db/backends/mysql/client.py b/django/db/backends/mysql/client.py
index f9d6297b8e..116074a9ce 100644
--- a/django/db/backends/mysql/client.py
+++ b/django/db/backends/mysql/client.py
@@ -3,12 +3,25 @@ import os
def runshell():
args = ['']
- args += ["--user=%s" % settings.DATABASE_USER]
- if settings.DATABASE_PASSWORD:
- args += ["--password=%s" % settings.DATABASE_PASSWORD]
- if settings.DATABASE_HOST:
- args += ["--host=%s" % settings.DATABASE_HOST]
- if settings.DATABASE_PORT:
- args += ["--port=%s" % settings.DATABASE_PORT]
- args += [settings.DATABASE_NAME]
+ db = settings.DATABASE_OPTIONS.get('db', settings.DATABASE_NAME)
+ user = settings.DATABASE_OPTIONS.get('user', settings.DATABASE_USER)
+ passwd = settings.DATABASE_OPTIONS.get('passwd', settings.DATABASE_PASSWORD)
+ host = settings.DATABASE_OPTIONS.get('host', settings.DATABASE_HOST)
+ port = settings.DATABASE_OPTIONS.get('port', settings.DATABASE_PORT)
+ defaults_file = settings.DATABASE_OPTIONS.get('read_default_file')
+ # Seems to be no good way to set sql_mode with CLI
+
+ if defaults_file:
+ args += ["--defaults-file=%s" % defaults_file]
+ if user:
+ args += ["--user=%s" % user]
+ if passwd:
+ args += ["--password=%s" % passwd]
+ if host:
+ args += ["--host=%s" % host]
+ if port:
+ args += ["--port=%s" % port]
+ if db:
+ args += [db]
+
os.execvp('mysql', args)
diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py
index 116b490124..1b23fbff6e 100644
--- a/django/db/backends/mysql/creation.py
+++ b/django/db/backends/mysql/creation.py
@@ -9,9 +9,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
+ 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar(100)',
'FilePathField': 'varchar(100)',
- 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'FloatField': 'double precision',
'ImageField': 'varchar(100)',
'IntegerField': 'integer',
'IPAddressField': 'char(15)',
@@ -25,6 +26,5 @@ DATA_TYPES = {
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
- 'URLField': 'varchar(200)',
'USStateField': 'varchar(2)',
}
diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
index 7829457fa9..39733311c5 100644
--- a/django/db/backends/mysql/introspection.py
+++ b/django/db/backends/mysql/introspection.py
@@ -76,7 +76,7 @@ def get_indexes(cursor, table_name):
DATA_TYPES_REVERSE = {
FIELD_TYPE.BLOB: 'TextField',
FIELD_TYPE.CHAR: 'CharField',
- FIELD_TYPE.DECIMAL: 'FloatField',
+ FIELD_TYPE.DECIMAL: 'DecimalField',
FIELD_TYPE.DATE: 'DateField',
FIELD_TYPE.DATETIME: 'DateTimeField',
FIELD_TYPE.DOUBLE: 'FloatField',
@@ -85,7 +85,7 @@ DATA_TYPES_REVERSE = {
FIELD_TYPE.LONG: 'IntegerField',
FIELD_TYPE.LONGLONG: 'IntegerField',
FIELD_TYPE.SHORT: 'IntegerField',
- FIELD_TYPE.STRING: 'TextField',
+ FIELD_TYPE.STRING: 'CharField',
FIELD_TYPE.TIMESTAMP: 'DateTimeField',
FIELD_TYPE.TINY: 'IntegerField',
FIELD_TYPE.TINY_BLOB: 'TextField',
diff --git a/django/db/backends/mysql_old/__init__.py b/django/db/backends/mysql_old/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/db/backends/mysql_old/__init__.py
diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py
new file mode 100644
index 0000000000..ac3b75efde
--- /dev/null
+++ b/django/db/backends/mysql_old/base.py
@@ -0,0 +1,240 @@
+"""
+MySQL database backend for Django.
+
+Requires MySQLdb: http://sourceforge.net/projects/mysql-python
+"""
+
+from django.db.backends import util
+try:
+ import MySQLdb as Database
+except ImportError, e:
+ from django.core.exceptions import ImproperlyConfigured
+ raise ImproperlyConfigured, "Error loading MySQLdb module: %s" % e
+from MySQLdb.converters import conversions
+from MySQLdb.constants import FIELD_TYPE
+import types
+import re
+
+DatabaseError = Database.DatabaseError
+IntegrityError = Database.IntegrityError
+
+django_conversions = conversions.copy()
+django_conversions.update({
+ types.BooleanType: util.rev_typecast_boolean,
+ FIELD_TYPE.DATETIME: util.typecast_timestamp,
+ FIELD_TYPE.DATE: util.typecast_date,
+ FIELD_TYPE.TIME: util.typecast_time,
+ FIELD_TYPE.DECIMAL: util.typecast_decimal,
+})
+
+# This should match the numerical portion of the version numbers (we can treat
+# versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
+# at http://dev.mysql.com/doc/refman/4.1/en/news.html and
+# http://dev.mysql.com/doc/refman/5.0/en/news.html .
+server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
+
+# This is an extra debug layer over MySQL queries, to display warnings.
+# It's only used when DEBUG=True.
+class MysqlDebugWrapper:
+ def __init__(self, cursor):
+ self.cursor = cursor
+
+ def execute(self, sql, params=()):
+ try:
+ return self.cursor.execute(sql, params)
+ except Database.Warning, w:
+ self.cursor.execute("SHOW WARNINGS")
+ raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
+
+ def executemany(self, sql, param_list):
+ try:
+ return self.cursor.executemany(sql, param_list)
+ except Database.Warning, w:
+ self.cursor.execute("SHOW WARNINGS")
+ raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
+
+ def __getattr__(self, attr):
+ if attr in self.__dict__:
+ return self.__dict__[attr]
+ else:
+ return getattr(self.cursor, attr)
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+class DatabaseWrapper(local):
+ def __init__(self, **kwargs):
+ self.connection = None
+ self.queries = []
+ self.server_version = None
+ self.options = kwargs
+
+ def _valid_connection(self):
+ if self.connection is not None:
+ try:
+ self.connection.ping()
+ return True
+ except DatabaseError:
+ self.connection.close()
+ self.connection = None
+ return False
+
+ def cursor(self):
+ from django.conf import settings
+ if not self._valid_connection():
+ kwargs = {
+ 'user': settings.DATABASE_USER,
+ 'db': settings.DATABASE_NAME,
+ 'passwd': settings.DATABASE_PASSWORD,
+ 'conv': django_conversions,
+ }
+ if settings.DATABASE_HOST.startswith('/'):
+ kwargs['unix_socket'] = settings.DATABASE_HOST
+ else:
+ kwargs['host'] = settings.DATABASE_HOST
+ if settings.DATABASE_PORT:
+ kwargs['port'] = int(settings.DATABASE_PORT)
+ kwargs.update(self.options)
+ self.connection = Database.connect(**kwargs)
+ cursor = self.connection.cursor()
+ if self.connection.get_server_info() >= '4.1':
+ cursor.execute("SET NAMES 'utf8'")
+ else:
+ cursor = self.connection.cursor()
+ if settings.DEBUG:
+ return util.CursorDebugWrapper(MysqlDebugWrapper(cursor), self)
+ return cursor
+
+ def _commit(self):
+ if self.connection is not None:
+ self.connection.commit()
+
+ def _rollback(self):
+ if self.connection is not None:
+ try:
+ self.connection.rollback()
+ except Database.NotSupportedError:
+ pass
+
+ def close(self):
+ if self.connection is not None:
+ self.connection.close()
+ self.connection = None
+
+ def get_server_version(self):
+ if not self.server_version:
+ if not self._valid_connection():
+ self.cursor()
+ m = server_version_re.match(self.connection.get_server_info())
+ if not m:
+ raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
+ self.server_version = tuple([int(x) for x in m.groups()])
+ return self.server_version
+
+supports_constraints = True
+
+def quote_name(name):
+ if name.startswith("`") and name.endswith("`"):
+ return name # Quoting once is enough.
+ return "`%s`" % name
+
+dictfetchone = util.dictfetchone
+dictfetchmany = util.dictfetchmany
+dictfetchall = util.dictfetchall
+
+def get_last_insert_id(cursor, table_name, pk_name):
+ return cursor.lastrowid
+
+def get_date_extract_sql(lookup_type, table_name):
+ # lookup_type is 'year', 'month', 'day'
+ # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
+ return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), table_name)
+
+def get_date_trunc_sql(lookup_type, field_name):
+ # lookup_type is 'year', 'month', 'day'
+ fields = ['year', 'month', 'day', 'hour', 'minute', 'second']
+ format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape.
+ format_def = ('0000-', '01', '-01', ' 00:', '00', ':00')
+ try:
+ i = fields.index(lookup_type) + 1
+ except ValueError:
+ sql = field_name
+ else:
+ format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]])
+ sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str)
+ return sql
+
+def get_limit_offset_sql(limit, offset=None):
+ sql = "LIMIT "
+ if offset and offset != 0:
+ sql += "%s," % offset
+ return sql + str(limit)
+
+def get_random_function_sql():
+ return "RAND()"
+
+def get_deferrable_sql():
+ return ""
+
+def get_fulltext_search_sql(field_name):
+ return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
+
+def get_drop_foreignkey_sql():
+ return "DROP FOREIGN KEY"
+
+def get_pk_default_value():
+ return "DEFAULT"
+
+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
+ themselves) and put the database in an empty 'initial' state
+
+ """
+ # NB: The generated SQL below is specific to MySQL
+ # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
+ # to clear all tables of all data
+ if tables:
+ sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + \
+ ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables] + \
+ ['SET FOREIGN_KEY_CHECKS = 1;']
+
+ # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
+ # to reset sequence indices
+ sql.extend(["%s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('TABLE'),
+ style.SQL_TABLE(quote_name(sequence['table'])),
+ style.SQL_KEYWORD('AUTO_INCREMENT'),
+ style.SQL_FIELD('= 1'),
+ ) for sequence in sequences])
+ return sql
+ else:
+ return []
+
+def get_sql_sequence_reset(style, model_list):
+ "Returns a list of the SQL statements to reset sequences for the given models."
+ # No sequence reset required
+ return []
+
+OPERATOR_MAPPING = {
+ 'exact': '= %s',
+ 'iexact': 'LIKE %s',
+ 'contains': 'LIKE BINARY %s',
+ 'icontains': 'LIKE %s',
+ 'gt': '> %s',
+ 'gte': '>= %s',
+ 'lt': '< %s',
+ 'lte': '<= %s',
+ 'startswith': 'LIKE BINARY %s',
+ 'endswith': 'LIKE BINARY %s',
+ 'istartswith': 'LIKE %s',
+ 'iendswith': 'LIKE %s',
+}
diff --git a/django/db/backends/mysql_old/client.py b/django/db/backends/mysql_old/client.py
new file mode 100644
index 0000000000..f9d6297b8e
--- /dev/null
+++ b/django/db/backends/mysql_old/client.py
@@ -0,0 +1,14 @@
+from django.conf import settings
+import os
+
+def runshell():
+ args = ['']
+ args += ["--user=%s" % settings.DATABASE_USER]
+ if settings.DATABASE_PASSWORD:
+ args += ["--password=%s" % settings.DATABASE_PASSWORD]
+ if settings.DATABASE_HOST:
+ args += ["--host=%s" % settings.DATABASE_HOST]
+ if settings.DATABASE_PORT:
+ args += ["--port=%s" % settings.DATABASE_PORT]
+ args += [settings.DATABASE_NAME]
+ os.execvp('mysql', args)
diff --git a/django/db/backends/mysql_old/creation.py b/django/db/backends/mysql_old/creation.py
new file mode 100644
index 0000000000..1b23fbff6e
--- /dev/null
+++ b/django/db/backends/mysql_old/creation.py
@@ -0,0 +1,30 @@
+# This dictionary maps Field objects to their associated MySQL column
+# types, as strings. Column-type strings can contain format strings; they'll
+# be interpolated against the values of Field.__dict__ before being output.
+# If a column type is set to None, it won't be included in the output.
+DATA_TYPES = {
+ 'AutoField': 'integer AUTO_INCREMENT',
+ 'BooleanField': 'bool',
+ 'CharField': 'varchar(%(maxlength)s)',
+ 'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
+ 'DateField': 'date',
+ 'DateTimeField': 'datetime',
+ 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'FileField': 'varchar(100)',
+ 'FilePathField': 'varchar(100)',
+ 'FloatField': 'double precision',
+ 'ImageField': 'varchar(100)',
+ 'IntegerField': 'integer',
+ 'IPAddressField': 'char(15)',
+ 'ManyToManyField': None,
+ 'NullBooleanField': 'bool',
+ 'OneToOneField': 'integer',
+ 'PhoneNumberField': 'varchar(20)',
+ 'PositiveIntegerField': 'integer UNSIGNED',
+ 'PositiveSmallIntegerField': 'smallint UNSIGNED',
+ 'SlugField': 'varchar(%(maxlength)s)',
+ 'SmallIntegerField': 'smallint',
+ 'TextField': 'longtext',
+ 'TimeField': 'time',
+ 'USStateField': 'varchar(2)',
+}
diff --git a/django/db/backends/mysql_old/introspection.py b/django/db/backends/mysql_old/introspection.py
new file mode 100644
index 0000000000..cb5b8320d9
--- /dev/null
+++ b/django/db/backends/mysql_old/introspection.py
@@ -0,0 +1,95 @@
+from django.db.backends.mysql_old.base import quote_name
+from MySQLdb import ProgrammingError, OperationalError
+from MySQLdb.constants import FIELD_TYPE
+import re
+
+foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
+
+def get_table_list(cursor):
+ "Returns a list of table names in the current database."
+ cursor.execute("SHOW TABLES")
+ return [row[0] for row in cursor.fetchall()]
+
+def get_table_description(cursor, table_name):
+ "Returns a description of the table, with the DB-API cursor.description interface."
+ cursor.execute("SELECT * FROM %s LIMIT 1" % quote_name(table_name))
+ return cursor.description
+
+def _name_to_index(cursor, table_name):
+ """
+ Returns a dictionary of {field_name: field_index} for the given table.
+ Indexes are 0-based.
+ """
+ return dict([(d[0], i) for i, d in enumerate(get_table_description(cursor, table_name))])
+
+def get_relations(cursor, table_name):
+ """
+ Returns a dictionary of {field_index: (field_index_other_table, other_table)}
+ representing all relationships to the given table. Indexes are 0-based.
+ """
+ my_field_dict = _name_to_index(cursor, table_name)
+ constraints = []
+ relations = {}
+ try:
+ # This should work for MySQL 5.0.
+ cursor.execute("""
+ SELECT column_name, referenced_table_name, referenced_column_name
+ FROM information_schema.key_column_usage
+ WHERE table_name = %s
+ AND table_schema = DATABASE()
+ AND referenced_table_name IS NOT NULL
+ AND referenced_column_name IS NOT NULL""", [table_name])
+ constraints.extend(cursor.fetchall())
+ except (ProgrammingError, OperationalError):
+ # Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
+ # Go through all constraints and save the equal matches.
+ cursor.execute("SHOW CREATE TABLE %s" % quote_name(table_name))
+ for row in cursor.fetchall():
+ pos = 0
+ while True:
+ match = foreign_key_re.search(row[1], pos)
+ if match == None:
+ break
+ pos = match.end()
+ constraints.append(match.groups())
+
+ for my_fieldname, other_table, other_field in constraints:
+ other_field_index = _name_to_index(cursor, other_table)[other_field]
+ my_field_index = my_field_dict[my_fieldname]
+ relations[my_field_index] = (other_field_index, other_table)
+
+ return relations
+
+def get_indexes(cursor, table_name):
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ cursor.execute("SHOW INDEX FROM %s" % quote_name(table_name))
+ indexes = {}
+ for row in cursor.fetchall():
+ indexes[row[4]] = {'primary_key': (row[2] == 'PRIMARY'), 'unique': not bool(row[1])}
+ return indexes
+
+DATA_TYPES_REVERSE = {
+ FIELD_TYPE.BLOB: 'TextField',
+ FIELD_TYPE.CHAR: 'CharField',
+ FIELD_TYPE.DECIMAL: 'DecimalField',
+ FIELD_TYPE.DATE: 'DateField',
+ FIELD_TYPE.DATETIME: 'DateTimeField',
+ FIELD_TYPE.DOUBLE: 'FloatField',
+ FIELD_TYPE.FLOAT: 'FloatField',
+ FIELD_TYPE.INT24: 'IntegerField',
+ FIELD_TYPE.LONG: 'IntegerField',
+ FIELD_TYPE.LONGLONG: 'IntegerField',
+ FIELD_TYPE.SHORT: 'IntegerField',
+ FIELD_TYPE.STRING: 'TextField',
+ FIELD_TYPE.TIMESTAMP: 'DateTimeField',
+ FIELD_TYPE.TINY: 'IntegerField',
+ FIELD_TYPE.TINY_BLOB: 'TextField',
+ FIELD_TYPE.MEDIUM_BLOB: 'TextField',
+ FIELD_TYPE.LONG_BLOB: 'TextField',
+ FIELD_TYPE.VAR_STRING: 'CharField',
+}
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 3a13f39546..2bc88bb7b9 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -12,6 +12,7 @@ except ImportError, e:
raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e
DatabaseError = Database.Error
+IntegrityError = Database.IntegrityError
try:
# Only exists in Python 2.4+
@@ -43,10 +44,11 @@ class DatabaseWrapper(local):
return FormatStylePlaceholderCursor(self.connection)
def _commit(self):
- self.connection.commit()
+ if self.connection is not None:
+ self.connection.commit()
def _rollback(self):
- if self.connection:
+ if self.connection is not None:
try:
self.connection.rollback()
except Database.NotSupportedError:
@@ -108,6 +110,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql():
return "DBMS_RANDOM.RANDOM"
+def get_deferrable_sql():
+ return " DEFERRABLE INITIALLY DEFERRED"
+
def get_fulltext_search_sql(field_name):
raise NotImplementedError
@@ -117,6 +122,24 @@ def get_drop_foreignkey_sql():
def get_pk_default_value():
return "DEFAULT"
+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
+ themselves) and put the database in an empty 'initial' state
+ """
+ # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements
+ # TODO - SQL not actually tested against Oracle yet!
+ # TODO - autoincrement indices reset required? See other get_sql_flush() implementations
+ sql = ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables]
+
+def get_sql_sequence_reset(style, model_list):
+ "Returns a list of the SQL statements to reset sequences for the given models."
+ # No sequence reset required
+ return []
+
OPERATOR_MAPPING = {
'exact': '= %s',
'iexact': 'LIKE %s',
diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
index d45ceb64f5..14a864ac28 100644
--- a/django/db/backends/oracle/creation.py
+++ b/django/db/backends/oracle/creation.py
@@ -5,9 +5,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar2(%(maxlength)s)',
'DateField': 'date',
'DateTimeField': 'date',
+ 'DecimalField': 'number(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar2(100)',
'FilePathField': 'varchar2(100)',
- 'FloatField': 'number(%(max_digits)s, %(decimal_places)s)',
+ 'FloatField': 'double precision',
'ImageField': 'varchar2(100)',
'IntegerField': 'integer',
'IPAddressField': 'char(15)',
@@ -21,6 +22,5 @@ DATA_TYPES = {
'SmallIntegerField': 'smallint',
'TextField': 'long',
'TimeField': 'timestamp',
- 'URLField': 'varchar(200)',
'USStateField': 'varchar(2)',
}
diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py
index ecc8f372a8..7634206178 100644
--- a/django/db/backends/oracle/introspection.py
+++ b/django/db/backends/oracle/introspection.py
@@ -46,5 +46,5 @@ DATA_TYPES_REVERSE = {
1114: 'DateTimeField',
1184: 'DateTimeField',
1266: 'TimeField',
- 1700: 'FloatField',
+ 1700: 'DecimalField',
}
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index e44bc0b560..fedbb6b7f1 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -12,6 +12,7 @@ except ImportError, e:
raise ImproperlyConfigured, "Error loading psycopg module: %s" % e
DatabaseError = Database.DatabaseError
+IntegrityError = Database.IntegrityError
try:
# Only exists in Python 2.4+
@@ -20,6 +21,40 @@ except ImportError:
# Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local
+def smart_basestring(s, charset):
+ if isinstance(s, unicode):
+ return s.encode(charset)
+ return s
+
+class UnicodeCursorWrapper(object):
+ """
+ A thin wrapper around psycopg cursors that allows them to accept Unicode
+ strings as params.
+
+ This is necessary because psycopg doesn't apply any DB quoting to
+ parameters that are Unicode strings. If a param is Unicode, this will
+ convert it to a bytestring using DEFAULT_CHARSET before passing it to
+ psycopg.
+ """
+ def __init__(self, cursor, charset):
+ self.cursor = cursor
+ self.charset = charset
+
+ def execute(self, sql, params=()):
+ return self.cursor.execute(sql, [smart_basestring(p, self.charset) for p in params])
+
+ def executemany(self, sql, param_list):
+ new_param_list = [tuple([smart_basestring(p, self.charset) for p in params]) for params in param_list]
+ return self.cursor.executemany(sql, new_param_list)
+
+ def __getattr__(self, attr):
+ if attr in self.__dict__:
+ return self.__dict__[attr]
+ else:
+ return getattr(self.cursor, attr)
+
+postgres_version = None
+
class DatabaseWrapper(local):
def __init__(self, **kwargs):
self.connection = None
@@ -28,7 +63,9 @@ class DatabaseWrapper(local):
def cursor(self):
from django.conf import settings
+ set_tz = False
if self.connection is None:
+ set_tz = True
if settings.DATABASE_NAME == '':
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
@@ -44,16 +81,23 @@ class DatabaseWrapper(local):
self.connection = Database.connect(conn_string, **self.options)
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
cursor = self.connection.cursor()
- cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
+ if set_tz:
+ cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
+ cursor = UnicodeCursorWrapper(cursor, settings.DEFAULT_CHARSET)
+ global postgres_version
+ if not postgres_version:
+ cursor.execute("SELECT version()")
+ postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
if settings.DEBUG:
return util.CursorDebugWrapper(cursor, self)
return cursor
def _commit(self):
- return self.connection.commit()
+ if self.connection is not None:
+ return self.connection.commit()
def _rollback(self):
- if self.connection:
+ if self.connection is not None:
return self.connection.rollback()
def close(self):
@@ -103,6 +147,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql():
return "RANDOM()"
+def get_deferrable_sql():
+ return " DEFERRABLE INITIALLY DEFERRED"
+
def get_fulltext_search_sql(field_name):
raise NotImplementedError
@@ -112,6 +159,91 @@ def get_drop_foreignkey_sql():
def get_pk_default_value():
return "DEFAULT"
+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
+ themselves) and put the database in an empty 'initial' state
+
+ """
+ if tables:
+ if postgres_version[0] >= 8 and postgres_version[1] >= 1:
+ # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to
+ # truncate tables referenced by a foreign key in any other table. The result is a
+ # single SQL TRUNCATE statement.
+ sql = ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(', '.join([quote_name(table) for table in tables]))
+ )]
+ else:
+ # Older versions of Postgres can't do TRUNCATE in a single call, so they must use
+ # a simple delete.
+ sql = ['%s %s %s;' % \
+ (style.SQL_KEYWORD('DELETE'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables]
+
+ # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
+ # to reset sequence indices
+ for sequence_info in sequences:
+ table_name = sequence_info['table']
+ column_name = sequence_info['column']
+ if column_name and len(column_name)>0:
+ # sequence name in this case will be <table>_<column>_seq
+ sql.append("%s %s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('SEQUENCE'),
+ style.SQL_FIELD(quote_name('%s_%s_seq' % (table_name, column_name))),
+ style.SQL_KEYWORD('RESTART'),
+ style.SQL_KEYWORD('WITH'),
+ style.SQL_FIELD('1')
+ )
+ )
+ else:
+ # sequence name in this case will be <table>_id_seq
+ sql.append("%s %s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('SEQUENCE'),
+ style.SQL_FIELD(quote_name('%s_id_seq' % table_name)),
+ style.SQL_KEYWORD('RESTART'),
+ style.SQL_KEYWORD('WITH'),
+ style.SQL_FIELD('1')
+ )
+ )
+ return sql
+ else:
+ return []
+
+def get_sql_sequence_reset(style, model_list):
+ "Returns a list of the SQL statements to reset sequences for the given models."
+ from django.db import models
+ output = []
+ for model in model_list:
+ # Use `coalesce` to set the sequence for each model to the max pk value if there are records,
+ # or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true
+ # if there are records (as the max pk value is already in use), otherwise set it to false.
+ for f in model._meta.fields:
+ if isinstance(f, models.AutoField):
+ output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
+ (style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))),
+ style.SQL_FIELD(quote_name(f.column)),
+ style.SQL_FIELD(quote_name(f.column)),
+ style.SQL_KEYWORD('IS NOT'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_TABLE(quote_name(model._meta.db_table))))
+ break # Only one AutoField is allowed per model, so don't bother continuing.
+ for f in model._meta.many_to_many:
+ output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
+ (style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())),
+ style.SQL_FIELD(quote_name('id')),
+ style.SQL_FIELD(quote_name('id')),
+ style.SQL_KEYWORD('IS NOT'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_TABLE(f.m2m_db_table())))
+ return output
+
# Register these custom typecasts, because Django expects dates/times to be
# in Python's native (standard-library) datetime/time format, whereas psycopg
# use mx.DateTime by default.
@@ -122,6 +254,7 @@ except AttributeError:
Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time))
Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp))
Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean))
+Database.register_type(Database.new_type((1700,), "NUMERIC", util.typecast_decimal))
OPERATOR_MAPPING = {
'exact': '= %s',
diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py
index 65a804ec40..4646b68ab8 100644
--- a/django/db/backends/postgresql/creation.py
+++ b/django/db/backends/postgresql/creation.py
@@ -9,9 +9,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'date',
'DateTimeField': 'timestamp with time zone',
+ 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'FileField': 'varchar(100)',
'FilePathField': 'varchar(100)',
- 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'FloatField': 'double precision',
'ImageField': 'varchar(100)',
'IntegerField': 'integer',
'IPAddressField': 'inet',
@@ -25,6 +26,5 @@ DATA_TYPES = {
'SmallIntegerField': 'smallint',
'TextField': 'text',
'TimeField': 'time',
- 'URLField': 'varchar(200)',
'USStateField': 'varchar(2)',
}
diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
index 6e1d60c4ff..2605490afd 100644
--- a/django/db/backends/postgresql/introspection.py
+++ b/django/db/backends/postgresql/introspection.py
@@ -72,6 +72,7 @@ DATA_TYPES_REVERSE = {
21: 'SmallIntegerField',
23: 'IntegerField',
25: 'TextField',
+ 701: 'FloatField',
869: 'IPAddressField',
1043: 'CharField',
1082: 'DateField',
@@ -79,5 +80,5 @@ DATA_TYPES_REVERSE = {
1114: 'DateTimeField',
1184: 'DateTimeField',
1266: 'TimeField',
- 1700: 'FloatField',
+ 1700: 'DecimalField',
}
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index 04322332dc..d9ad363ac1 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -12,6 +12,7 @@ except ImportError, e:
raise ImproperlyConfigured, "Error loading psycopg2 module: %s" % e
DatabaseError = Database.DatabaseError
+IntegrityError = Database.IntegrityError
try:
# Only exists in Python 2.4+
@@ -20,6 +21,8 @@ except ImportError:
# Import copy of _thread_local.py from Python 2.4
from django.utils._threading_local import local
+postgres_version = None
+
class DatabaseWrapper(local):
def __init__(self, **kwargs):
self.connection = None
@@ -28,7 +31,9 @@ class DatabaseWrapper(local):
def cursor(self):
from django.conf import settings
+ set_tz = False
if self.connection is None:
+ set_tz = True
if settings.DATABASE_NAME == '':
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file."
@@ -45,16 +50,22 @@ class DatabaseWrapper(local):
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
cursor = self.connection.cursor()
cursor.tzinfo_factory = None
- cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
+ if set_tz:
+ cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
+ global postgres_version
+ if not postgres_version:
+ cursor.execute("SELECT version()")
+ postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')]
if settings.DEBUG:
return util.CursorDebugWrapper(cursor, self)
return cursor
def _commit(self):
- return self.connection.commit()
+ if self.connection is not None:
+ return self.connection.commit()
def _rollback(self):
- if self.connection:
+ if self.connection is not None:
return self.connection.rollback()
def close(self):
@@ -96,6 +107,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql():
return "RANDOM()"
+def get_deferrable_sql():
+ return " DEFERRABLE INITIALLY DEFERRED"
+
def get_fulltext_search_sql(field_name):
raise NotImplementedError
@@ -105,6 +119,88 @@ def get_drop_foreignkey_sql():
def get_pk_default_value():
return "DEFAULT"
+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
+ themselves) and put the database in an empty 'initial' state
+ """
+ if tables:
+ if postgres_version[0] >= 8 and postgres_version[1] >= 1:
+ # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to
+ # truncate tables referenced by a foreign key in any other table. The result is a
+ # single SQL TRUNCATE statement
+ sql = ['%s %s;' % \
+ (style.SQL_KEYWORD('TRUNCATE'),
+ style.SQL_FIELD(', '.join([quote_name(table) for table in tables]))
+ )]
+ else:
+ sql = ['%s %s %s;' % \
+ (style.SQL_KEYWORD('DELETE'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables]
+
+ # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
+ # to reset sequence indices
+ for sequence in sequences:
+ table_name = sequence['table']
+ column_name = sequence['column']
+ if column_name and len(column_name) > 0:
+ # sequence name in this case will be <table>_<column>_seq
+ sql.append("%s %s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('SEQUENCE'),
+ style.SQL_FIELD(quote_name('%s_%s_seq' % (table_name, column_name))),
+ style.SQL_KEYWORD('RESTART'),
+ style.SQL_KEYWORD('WITH'),
+ style.SQL_FIELD('1')
+ )
+ )
+ else:
+ # sequence name in this case will be <table>_id_seq
+ sql.append("%s %s %s %s %s %s;" % \
+ (style.SQL_KEYWORD('ALTER'),
+ style.SQL_KEYWORD('SEQUENCE'),
+ style.SQL_FIELD(quote_name('%s_id_seq' % table_name)),
+ style.SQL_KEYWORD('RESTART'),
+ style.SQL_KEYWORD('WITH'),
+ style.SQL_FIELD('1')
+ )
+ )
+ return sql
+ else:
+ return []
+
+def get_sql_sequence_reset(style, model_list):
+ "Returns a list of the SQL statements to reset sequences for the given models."
+ from django.db import models
+ output = []
+ for model in model_list:
+ # Use `coalesce` to set the sequence for each model to the max pk value if there are records,
+ # or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true
+ # if there are records (as the max pk value is already in use), otherwise set it to false.
+ for f in model._meta.fields:
+ if isinstance(f, models.AutoField):
+ output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
+ (style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))),
+ style.SQL_FIELD(quote_name(f.column)),
+ style.SQL_FIELD(quote_name(f.column)),
+ style.SQL_KEYWORD('IS NOT'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_TABLE(quote_name(model._meta.db_table))))
+ break # Only one AutoField is allowed per model, so don't bother continuing.
+ for f in model._meta.many_to_many:
+ output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
+ (style.SQL_KEYWORD('SELECT'),
+ style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())),
+ style.SQL_FIELD(quote_name('id')),
+ style.SQL_FIELD(quote_name('id')),
+ style.SQL_KEYWORD('IS NOT'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_TABLE(f.m2m_db_table())))
+ return output
+
OPERATOR_MAPPING = {
'exact': '= %s',
'iexact': 'ILIKE %s',
diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py
index a546da8c45..aa45fe7db7 100644
--- a/django/db/backends/postgresql_psycopg2/introspection.py
+++ b/django/db/backends/postgresql_psycopg2/introspection.py
@@ -72,6 +72,7 @@ DATA_TYPES_REVERSE = {
21: 'SmallIntegerField',
23: 'IntegerField',
25: 'TextField',
+ 701: 'FloatField',
869: 'IPAddressField',
1043: 'CharField',
1082: 'DateField',
@@ -79,5 +80,5 @@ DATA_TYPES_REVERSE = {
1114: 'DateTimeField',
1184: 'DateTimeField',
1266: 'TimeField',
- 1700: 'FloatField',
+ 1700: 'DecimalField',
}
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index 891320160f..5cd67a32f5 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -17,7 +17,13 @@ except ImportError, e:
module = 'sqlite3'
raise ImproperlyConfigured, "Error loading %s module: %s" % (module, e)
+try:
+ import decimal
+except ImportError:
+ from django.utils import _decimal as decimal # for Python 2.3
+
DatabaseError = Database.DatabaseError
+IntegrityError = Database.IntegrityError
Database.register_converter("bool", lambda s: str(s) == '1')
Database.register_converter("time", util.typecast_time)
@@ -25,6 +31,8 @@ Database.register_converter("date", util.typecast_date)
Database.register_converter("datetime", util.typecast_timestamp)
Database.register_converter("timestamp", util.typecast_timestamp)
Database.register_converter("TIMESTAMP", util.typecast_timestamp)
+Database.register_converter("decimal", util.typecast_decimal)
+Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
def utf8rowFactory(cursor, row):
def utf8(s):
@@ -67,10 +75,11 @@ class DatabaseWrapper(local):
return cursor
def _commit(self):
- self.connection.commit()
+ if self.connection is not None:
+ self.connection.commit()
def _rollback(self):
- if self.connection:
+ if self.connection is not None:
self.connection.rollback()
def close(self):
@@ -139,6 +148,9 @@ def get_limit_offset_sql(limit, offset=None):
def get_random_function_sql():
return "RANDOM()"
+def get_deferrable_sql():
+ return ""
+
def get_fulltext_search_sql(field_name):
raise NotImplementedError
@@ -148,6 +160,29 @@ def get_drop_foreignkey_sql():
def get_pk_default_value():
return "NULL"
+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
+ themselves) and put the database in an empty 'initial' state
+
+ """
+ # NB: The generated SQL below is specific to SQLite
+ # Note: The DELETE FROM... SQL generated below works for SQLite databases
+ # because constraints don't exist
+ sql = ['%s %s %s;' % \
+ (style.SQL_KEYWORD('DELETE'),
+ style.SQL_KEYWORD('FROM'),
+ style.SQL_FIELD(quote_name(table))
+ ) for table in tables]
+ # Note: No requirement for reset of auto-incremented indices (cf. other
+ # get_sql_flush() implementations). Just return SQL at this point
+ return sql
+
+def get_sql_sequence_reset(style, model_list):
+ "Returns a list of the SQL statements to reset sequences for the given models."
+ # No sequence reset required
+ return []
+
def _sqlite_date_trunc(lookup_type, dt):
try:
dt = util.typecast_timestamp(dt)
diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py
index e845179e64..e63046ab7d 100644
--- a/django/db/backends/sqlite3/creation.py
+++ b/django/db/backends/sqlite3/creation.py
@@ -8,9 +8,10 @@ DATA_TYPES = {
'CommaSeparatedIntegerField': 'varchar(%(maxlength)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
+ 'DecimalField': 'decimal',
'FileField': 'varchar(100)',
'FilePathField': 'varchar(100)',
- 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)',
+ 'FloatField': 'real',
'ImageField': 'varchar(100)',
'IntegerField': 'integer',
'IPAddressField': 'char(15)',
@@ -24,6 +25,5 @@ DATA_TYPES = {
'SmallIntegerField': 'smallint',
'TextField': 'text',
'TimeField': 'time',
- 'URLField': 'varchar(200)',
'USStateField': 'varchar(2)',
}
diff --git a/django/db/backends/util.py b/django/db/backends/util.py
index d8f86fef4f..81c752e664 100644
--- a/django/db/backends/util.py
+++ b/django/db/backends/util.py
@@ -1,6 +1,11 @@
import datetime
from time import time
+try:
+ import decimal
+except ImportError:
+ from django.utils import _decimal as decimal # for Python 2.3
+
class CursorDebugWrapper(object):
def __init__(self, cursor, db):
self.cursor = cursor
@@ -33,7 +38,7 @@ class CursorDebugWrapper(object):
})
def __getattr__(self, attr):
- if self.__dict__.has_key(attr):
+ if attr in self.__dict__:
return self.__dict__[attr]
else:
return getattr(self.cursor, attr)
@@ -85,6 +90,11 @@ def typecast_boolean(s):
if not s: return False
return str(s)[0].lower() == 't'
+def typecast_decimal(s):
+ if s is None or s == '':
+ return None
+ return decimal.Decimal(s)
+
###############################################
# Converters from Python to database (string) #
###############################################
@@ -92,6 +102,11 @@ def typecast_boolean(s):
def rev_typecast_boolean(obj, d):
return obj and '1' or '0'
+def rev_typecast_decimal(d):
+ if d is None:
+ return None
+ return str(d)
+
##################################################################################
# Helper functions for dictfetch* for databases that don't natively support them #
##################################################################################