diff --git a/django/db/backends/postgresql/__init__.py b/django/db/backends/postgresql/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py deleted file mode 100644 index f3ac451a15f..00000000000 --- a/django/db/backends/postgresql/base.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -PostgreSQL database backend for Django. - -Requires psycopg 1: http://initd.org/projects/psycopg1 -""" - -import sys - -from django.db import utils -from django.db.backends import * -from django.db.backends.signals import connection_created -from django.db.backends.postgresql.client import DatabaseClient -from django.db.backends.postgresql.creation import DatabaseCreation -from django.db.backends.postgresql.introspection import DatabaseIntrospection -from django.db.backends.postgresql.operations import DatabaseOperations -from django.db.backends.postgresql.version import get_version -from django.utils.encoding import smart_str, smart_unicode - -try: - import psycopg as Database -except ImportError, e: - from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured("Error loading psycopg module: %s" % e) - -DatabaseError = Database.DatabaseError -IntegrityError = Database.IntegrityError - -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 database client's encoding before passing - it to psycopg. - - All results retrieved from the database are converted into Unicode strings - before being returned to the caller. - """ - def __init__(self, cursor, charset): - self.cursor = cursor - self.charset = charset - - def format_params(self, params): - if isinstance(params, dict): - result = {} - charset = self.charset - for key, value in params.items(): - result[smart_str(key, charset)] = smart_str(value, charset) - return result - else: - return tuple([smart_str(p, self.charset, True) for p in params]) - - def execute(self, sql, params=()): - try: - return self.cursor.execute(smart_str(sql, self.charset), self.format_params(params)) - except Database.IntegrityError, e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - except Database.DatabaseError, e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] - - def executemany(self, sql, param_list): - try: - new_param_list = [self.format_params(params) for params in param_list] - return self.cursor.executemany(sql, new_param_list) - except Database.IntegrityError, e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - except Database.DatabaseError, e: - raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] - - def __getattr__(self, attr): - if attr in self.__dict__: - return self.__dict__[attr] - else: - return getattr(self.cursor, attr) - - def __iter__(self): - return iter(self.cursor.fetchall()) - -class DatabaseFeatures(BaseDatabaseFeatures): - uses_savepoints = True - requires_rollback_on_dirty_transaction = True - has_real_datatype = True - can_defer_constraint_checks = True - -class DatabaseWrapper(BaseDatabaseWrapper): - vendor = 'postgresql' - operators = { - 'exact': '= %s', - 'iexact': '= UPPER(%s)', - 'contains': 'LIKE %s', - 'icontains': 'LIKE UPPER(%s)', - 'regex': '~ %s', - 'iregex': '~* %s', - 'gt': '> %s', - 'gte': '>= %s', - 'lt': '< %s', - 'lte': '<= %s', - 'startswith': 'LIKE %s', - 'endswith': 'LIKE %s', - 'istartswith': 'LIKE UPPER(%s)', - 'iendswith': 'LIKE UPPER(%s)', - } - - def __init__(self, *args, **kwargs): - super(DatabaseWrapper, self).__init__(*args, **kwargs) - - import warnings - warnings.warn( - 'The "postgresql" backend has been deprecated. Use "postgresql_psycopg2" instead.', - DeprecationWarning - ) - - self.features = DatabaseFeatures(self) - self.ops = DatabaseOperations(self) - self.client = DatabaseClient(self) - self.creation = DatabaseCreation(self) - self.introspection = DatabaseIntrospection(self) - self.validation = BaseDatabaseValidation(self) - - def _cursor(self): - new_connection = False - set_tz = False - settings_dict = self.settings_dict - if self.connection is None: - new_connection = True - set_tz = settings_dict.get('TIME_ZONE') - if settings_dict['NAME'] == '': - from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured("You need to specify NAME in your Django settings file.") - conn_string = "dbname=%s" % settings_dict['NAME'] - if settings_dict['USER']: - conn_string = "user=%s %s" % (settings_dict['USER'], conn_string) - if settings_dict['PASSWORD']: - conn_string += " password='%s'" % settings_dict['PASSWORD'] - if settings_dict['HOST']: - conn_string += " host=%s" % settings_dict['HOST'] - if settings_dict['PORT']: - conn_string += " port=%s" % settings_dict['PORT'] - self.connection = Database.connect(conn_string, **settings_dict['OPTIONS']) - # make transactions transparent to all cursors - self.connection.set_isolation_level(1) - connection_created.send(sender=self.__class__, connection=self) - cursor = self.connection.cursor() - if new_connection: - if set_tz: - cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']]) - if not hasattr(self, '_version'): - self.__class__._version = get_version(cursor) - if self._version[0:2] < (8, 0): - # No savepoint support for earlier version of PostgreSQL. - self.features.uses_savepoints = False - cursor.execute("SET client_encoding to 'UNICODE'") - return UnicodeCursorWrapper(cursor, 'utf-8') - - def _commit(self): - if self.connection is not None: - try: - return self.connection.commit() - except Database.IntegrityError, e: - raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] - -def typecast_string(s): - """ - Cast all returned strings to unicode strings. - """ - if not s and not isinstance(s, str): - return s - return smart_unicode(s) - -# 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. -try: - Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date)) -except AttributeError: - raise Exception("You appear to be using psycopg version 2. Set your DATABASES.ENGINE to 'postgresql_psycopg2' instead of 'postgresql'.") -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)) -Database.register_type(Database.new_type(Database.types[1043].values, 'STRING', typecast_string)) diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py deleted file mode 100644 index 534bf41d461..00000000000 --- a/django/db/backends/postgresql/introspection.py +++ /dev/null @@ -1,88 +0,0 @@ -from django.db.backends import BaseDatabaseIntrospection - -class DatabaseIntrospection(BaseDatabaseIntrospection): - # Maps type codes to Django Field types. - data_types_reverse = { - 16: 'BooleanField', - 20: 'BigIntegerField', - 21: 'SmallIntegerField', - 23: 'IntegerField', - 25: 'TextField', - 700: 'FloatField', - 701: 'FloatField', - 869: 'IPAddressField', - 1043: 'CharField', - 1082: 'DateField', - 1083: 'TimeField', - 1114: 'DateTimeField', - 1184: 'DateTimeField', - 1266: 'TimeField', - 1700: 'DecimalField', - } - - def get_table_list(self, cursor): - "Returns a list of table names in the current database." - cursor.execute(""" - SELECT c.relname - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind IN ('r', 'v', '') - AND n.nspname NOT IN ('pg_catalog', 'pg_toast') - AND pg_catalog.pg_table_is_visible(c.oid)""") - return [row[0] for row in cursor.fetchall()] - - def get_table_description(self, cursor, table_name): - "Returns a description of the table, with the DB-API cursor.description interface." - cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name)) - return cursor.description - - def get_relations(self, 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. - """ - cursor.execute(""" - SELECT con.conkey, con.confkey, c2.relname - FROM pg_constraint con, pg_class c1, pg_class c2 - WHERE c1.oid = con.conrelid - AND c2.oid = con.confrelid - AND c1.relname = %s - AND con.contype = 'f'""", [table_name]) - relations = {} - for row in cursor.fetchall(): - try: - # row[0] and row[1] are like "{2}", so strip the curly braces. - relations[int(row[0][1:-1]) - 1] = (int(row[1][1:-1]) - 1, row[2]) - except ValueError: - continue - return relations - - def get_indexes(self, 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} - """ - # This query retrieves each index on the given table, including the - # first associated field name - cursor.execute(""" - SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary - FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, - pg_catalog.pg_index idx, pg_catalog.pg_attribute attr - WHERE c.oid = idx.indrelid - AND idx.indexrelid = c2.oid - AND attr.attrelid = c.oid - AND attr.attnum = idx.indkey[0] - AND c.relname = %s""", [table_name]) - indexes = {} - for row in cursor.fetchall(): - # row[1] (idx.indkey) is stored in the DB as an array. It comes out as - # a string of space-separated integers. This designates the field - # indexes (1-based) of the fields that have indexes on the table. - # Here, we skip any indexes across multiple fields. - if ' ' in row[1]: - continue - indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]} - return indexes - diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 15a588defae..e0990a1089a 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -9,10 +9,10 @@ import sys from django.db import utils from django.db.backends import * from django.db.backends.signals import connection_created -from django.db.backends.postgresql.operations import DatabaseOperations as PostgresqlDatabaseOperations -from django.db.backends.postgresql.client import DatabaseClient -from django.db.backends.postgresql.creation import DatabaseCreation -from django.db.backends.postgresql.version import get_version +from django.db.backends.postgresql_psycopg2.operations import DatabaseOperations +from django.db.backends.postgresql_psycopg2.client import DatabaseClient +from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation +from django.db.backends.postgresql_psycopg2.version import get_version from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection from django.utils.safestring import SafeUnicode, SafeString @@ -71,16 +71,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): has_real_datatype = True can_defer_constraint_checks = True -class DatabaseOperations(PostgresqlDatabaseOperations): - def last_executed_query(self, cursor, sql, params): - # With psycopg2, cursor objects have a "query" attribute that is the - # exact query sent to the database. See docs here: - # http://www.initd.org/tracker/psycopg/wiki/psycopg2_documentation#postgresql-status-message-and-executed-query - return cursor.query - - def return_insert_id(self): - return "RETURNING %s", () - class DatabaseWrapper(BaseDatabaseWrapper): vendor = 'postgresql' operators = { diff --git a/django/db/backends/postgresql/client.py b/django/db/backends/postgresql_psycopg2/client.py similarity index 100% rename from django/db/backends/postgresql/client.py rename to django/db/backends/postgresql_psycopg2/client.py diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql_psycopg2/creation.py similarity index 99% rename from django/db/backends/postgresql/creation.py rename to django/db/backends/postgresql_psycopg2/creation.py index 99847163892..5d4d50b5cf6 100644 --- a/django/db/backends/postgresql/creation.py +++ b/django/db/backends/postgresql_psycopg2/creation.py @@ -1,6 +1,7 @@ from django.db.backends.creation import BaseDatabaseCreation from django.db.backends.util import truncate_name + class DatabaseCreation(BaseDatabaseCreation): # This dictionary maps Field objects to their associated PostgreSQL column # types, as strings. Column-type strings can contain format strings; they'll diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py index 83bd9b4c44c..9f2c81dee5a 100644 --- a/django/db/backends/postgresql_psycopg2/introspection.py +++ b/django/db/backends/postgresql_psycopg2/introspection.py @@ -1,6 +1,41 @@ -from django.db.backends.postgresql.introspection import DatabaseIntrospection as PostgresDatabaseIntrospection +from django.db.backends import BaseDatabaseIntrospection -class DatabaseIntrospection(PostgresDatabaseIntrospection): + +class DatabaseIntrospection(BaseDatabaseIntrospection): + # Maps type codes to Django Field types. + data_types_reverse = { + 16: 'BooleanField', + 20: 'BigIntegerField', + 21: 'SmallIntegerField', + 23: 'IntegerField', + 25: 'TextField', + 700: 'FloatField', + 701: 'FloatField', + 869: 'IPAddressField', + 1043: 'CharField', + 1082: 'DateField', + 1083: 'TimeField', + 1114: 'DateTimeField', + 1184: 'DateTimeField', + 1266: 'TimeField', + 1700: 'DecimalField', + } + + def get_table_list(self, cursor): + "Returns a list of table names in the current database." + cursor.execute(""" + SELECT c.relname + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind IN ('r', 'v', '') + AND n.nspname NOT IN ('pg_catalog', 'pg_toast') + AND pg_catalog.pg_table_is_visible(c.oid)""") + return [row[0] for row in cursor.fetchall()] + + def get_table_description(self, cursor, table_name): + "Returns a description of the table, with the DB-API cursor.description interface." + cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name)) + return cursor.description def get_relations(self, cursor, table_name): """ @@ -19,3 +54,32 @@ class DatabaseIntrospection(PostgresDatabaseIntrospection): # row[0] and row[1] are single-item lists, so grab the single item. relations[row[0][0] - 1] = (row[1][0] - 1, row[2]) return relations + + def get_indexes(self, 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} + """ + # This query retrieves each index on the given table, including the + # first associated field name + cursor.execute(""" + SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary + FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, + pg_catalog.pg_index idx, pg_catalog.pg_attribute attr + WHERE c.oid = idx.indrelid + AND idx.indexrelid = c2.oid + AND attr.attrelid = c.oid + AND attr.attnum = idx.indkey[0] + AND c.relname = %s""", [table_name]) + indexes = {} + for row in cursor.fetchall(): + # row[1] (idx.indkey) is stored in the DB as an array. It comes out as + # a string of space-separated integers. This designates the field + # indexes (1-based) of the fields that have indexes on the table. + # Here, we skip any indexes across multiple fields. + if ' ' in row[1]: + continue + indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]} + return indexes diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql_psycopg2/operations.py similarity index 94% rename from django/db/backends/postgresql/operations.py rename to django/db/backends/postgresql_psycopg2/operations.py index 83fe7c21ec6..537fa459816 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql_psycopg2/operations.py @@ -2,8 +2,6 @@ import re from django.db.backends import BaseDatabaseOperations -# This DatabaseOperations class lives in here instead of base.py because it's -# used by both the 'postgresql' and 'postgresql_psycopg2' backends. class DatabaseOperations(BaseDatabaseOperations): def __init__(self, connection): @@ -13,7 +11,7 @@ class DatabaseOperations(BaseDatabaseOperations): def _get_postgres_version(self): if self._postgres_version is None: - from django.db.backends.postgresql.version import get_version + from django.db.backends.postgresql_psycopg2.version import get_version cursor = self.connection.cursor() self._postgres_version = get_version(cursor) return self._postgres_version @@ -203,3 +201,12 @@ class DatabaseOperations(BaseDatabaseOperations): """ return 63 + + def last_executed_query(self, cursor, sql, params): + # With psycopg2, cursor objects have a "query" attribute that is the + # exact query sent to the database. See docs here: + # http://www.initd.org/tracker/psycopg/wiki/psycopg2_documentation#postgresql-status-message-and-executed-query + return cursor.query + + def return_insert_id(self): + return "RETURNING %s", () diff --git a/django/db/backends/postgresql/version.py b/django/db/backends/postgresql_psycopg2/version.py similarity index 99% rename from django/db/backends/postgresql/version.py rename to django/db/backends/postgresql_psycopg2/version.py index 8fd773e59b0..b9298296eab 100644 --- a/django/db/backends/postgresql/version.py +++ b/django/db/backends/postgresql_psycopg2/version.py @@ -12,6 +12,7 @@ import re # PostgreSQL 8.4beta1 VERSION_RE = re.compile(r'\S+ (\d+)\.(\d+)\.?(\d+)?') + def _parse_version(text): "Internal parsing method. Factored out for testing purposes." major, major2, minor = VERSION_RE.search(text).groups()