diff options
| author | Russell Keith-Magee <russell@keith-magee.com> | 2008-08-11 12:11:25 +0000 |
|---|---|---|
| committer | Russell Keith-Magee <russell@keith-magee.com> | 2008-08-11 12:11:25 +0000 |
| commit | 9dc4ba875f21d5690f6ad5995123a67a3c44bafe (patch) | |
| tree | 621f876758ac16dceee95faf51973d4b05f1c830 /django/db/backends/postgresql | |
| parent | cec69eb70d1e2f84dc5a7fb172da88a79b0f5063 (diff) | |
Fixed #5461 -- Refactored the database backend code to use classes for the creation and introspection modules. Introduces a new validation module for DB-specific validation. This is a backwards incompatible change; see the wiki for details.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@8296 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/db/backends/postgresql')
| -rw-r--r-- | django/db/backends/postgresql/base.py | 23 | ||||
| -rw-r--r-- | django/db/backends/postgresql/client.py | 26 | ||||
| -rw-r--r-- | django/db/backends/postgresql/creation.py | 66 | ||||
| -rw-r--r-- | django/db/backends/postgresql/introspection.py | 162 |
4 files changed, 149 insertions, 128 deletions
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 1dfe34aceb..4a8d6ebef0 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -4,9 +4,13 @@ PostgreSQL database backend for Django. Requires psycopg 1: http://initd.org/projects/psycopg1 """ -from django.utils.encoding import smart_str, smart_unicode -from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, util +from django.db.backends import * +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.utils.encoding import smart_str, smart_unicode + try: import psycopg as Database except ImportError, e: @@ -59,12 +63,7 @@ class UnicodeCursorWrapper(object): def __iter__(self): return iter(self.cursor) -class DatabaseFeatures(BaseDatabaseFeatures): - pass # This backend uses all the defaults. - class DatabaseWrapper(BaseDatabaseWrapper): - features = DatabaseFeatures() - ops = DatabaseOperations() operators = { 'exact': '= %s', 'iexact': 'ILIKE %s', @@ -82,6 +81,16 @@ class DatabaseWrapper(BaseDatabaseWrapper): 'iendswith': 'ILIKE %s', } + def __init__(self, *args, **kwargs): + super(DatabaseWrapper, self).__init__(*args, **kwargs) + + self.features = BaseDatabaseFeatures() + self.ops = DatabaseOperations() + self.client = DatabaseClient() + self.creation = DatabaseCreation(self) + self.introspection = DatabaseIntrospection(self) + self.validation = BaseDatabaseValidation() + def _cursor(self, settings): set_tz = False if self.connection is None: diff --git a/django/db/backends/postgresql/client.py b/django/db/backends/postgresql/client.py index 8123ec7848..28daed833a 100644 --- a/django/db/backends/postgresql/client.py +++ b/django/db/backends/postgresql/client.py @@ -1,15 +1,17 @@ +from django.db.backends import BaseDatabaseClient from django.conf import settings import os -def runshell(): - args = ['psql'] - if settings.DATABASE_USER: - args += ["-U", settings.DATABASE_USER] - if settings.DATABASE_PASSWORD: - args += ["-W"] - if settings.DATABASE_HOST: - args.extend(["-h", settings.DATABASE_HOST]) - if settings.DATABASE_PORT: - args.extend(["-p", str(settings.DATABASE_PORT)]) - args += [settings.DATABASE_NAME] - os.execvp('psql', args) +class DatabaseClient(BaseDatabaseClient): + def runshell(self): + args = ['psql'] + if settings.DATABASE_USER: + args += ["-U", settings.DATABASE_USER] + if settings.DATABASE_PASSWORD: + args += ["-W"] + if settings.DATABASE_HOST: + args.extend(["-h", settings.DATABASE_HOST]) + if settings.DATABASE_PORT: + args.extend(["-p", str(settings.DATABASE_PORT)]) + args += [settings.DATABASE_NAME] + os.execvp('psql', args) diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py index a8877a7d9b..3e537e345e 100644 --- a/django/db/backends/postgresql/creation.py +++ b/django/db/backends/postgresql/creation.py @@ -1,28 +1,38 @@ -# This dictionary maps Field objects to their associated PostgreSQL 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': 'serial', - 'BooleanField': 'boolean', - 'CharField': 'varchar(%(max_length)s)', - 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', - 'DateField': 'date', - 'DateTimeField': 'timestamp with time zone', - 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', - 'FileField': 'varchar(%(max_length)s)', - 'FilePathField': 'varchar(%(max_length)s)', - 'FloatField': 'double precision', - 'IntegerField': 'integer', - 'IPAddressField': 'inet', - 'NullBooleanField': 'boolean', - 'OneToOneField': 'integer', - 'PhoneNumberField': 'varchar(20)', - 'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)', - 'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)', - 'SlugField': 'varchar(%(max_length)s)', - 'SmallIntegerField': 'smallint', - 'TextField': 'text', - 'TimeField': 'time', - 'USStateField': 'varchar(2)', -} +from django.conf import settings +from django.db.backends.creation import BaseDatabaseCreation + +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 + # 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': 'serial', + 'BooleanField': 'boolean', + 'CharField': 'varchar(%(max_length)s)', + 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', + 'DateField': 'date', + 'DateTimeField': 'timestamp with time zone', + 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', + 'FloatField': 'double precision', + 'IntegerField': 'integer', + 'IPAddressField': 'inet', + 'NullBooleanField': 'boolean', + 'OneToOneField': 'integer', + 'PhoneNumberField': 'varchar(20)', + 'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)', + 'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)', + 'SlugField': 'varchar(%(max_length)s)', + 'SmallIntegerField': 'smallint', + 'TextField': 'text', + 'TimeField': 'time', + 'USStateField': 'varchar(2)', + } + + def sql_table_creation_suffix(self): + assert settings.TEST_DATABASE_COLLATION is None, "PostgreSQL does not support collation setting at database creation time." + if settings.TEST_DATABASE_CHARSET: + return "WITH ENCODING '%s'" % settings.TEST_DATABASE_CHARSET + return '' diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py index 982c004569..7b3ab3bb8a 100644 --- a/django/db/backends/postgresql/introspection.py +++ b/django/db/backends/postgresql/introspection.py @@ -1,86 +1,86 @@ -from django.db.backends.postgresql.base import DatabaseOperations +from django.db.backends import BaseDatabaseIntrospection -quote_name = DatabaseOperations().quote_name +class DatabaseIntrospection(BaseDatabaseIntrospection): + # Maps type codes to Django Field types. + data_types_reverse = { + 16: 'BooleanField', + 21: 'SmallIntegerField', + 23: 'IntegerField', + 25: 'TextField', + 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_list(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_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 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_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. - """ - 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 -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} - """ - # 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 - -# Maps type codes to Django Field types. -DATA_TYPES_REVERSE = { - 16: 'BooleanField', - 21: 'SmallIntegerField', - 23: 'IntegerField', - 25: 'TextField', - 701: 'FloatField', - 869: 'IPAddressField', - 1043: 'CharField', - 1082: 'DateField', - 1083: 'TimeField', - 1114: 'DateTimeField', - 1184: 'DateTimeField', - 1266: 'TimeField', - 1700: 'DecimalField', -} |
