diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index afb7432c84..f0a117f570 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -121,7 +121,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']]) if not hasattr(self, '_version'): self.__class__._version = get_version(cursor) - if self._version < (8, 0): + 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'") diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index 35824609b2..7b3a6c4413 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -2,8 +2,6 @@ import re from django.db.backends import BaseDatabaseOperations -server_version_re = re.compile(r'PostgreSQL (\d{1,2})\.(\d{1,2})\.?(\d{1,2})?') - # This DatabaseOperations class lives in here instead of base.py because it's # used by both the 'postgresql' and 'postgresql_psycopg2' backends. @@ -14,13 +12,9 @@ class DatabaseOperations(BaseDatabaseOperations): def _get_postgres_version(self): if self._postgres_version is None: from django.db import connection + from django.db.backends.postgresql.version import get_version cursor = connection.cursor() - cursor.execute("SELECT version()") - version_string = cursor.fetchone()[0] - m = server_version_re.match(version_string) - if not m: - raise Exception('Unable to determine PostgreSQL version from version() function string: %r' % version_string) - self._postgres_version = [int(val) for val in m.groups() if val] + self._postgres_version = get_version(cursor) return self._postgres_version postgres_version = property(_get_postgres_version) @@ -72,7 +66,7 @@ class DatabaseOperations(BaseDatabaseOperations): def sql_flush(self, style, tables, sequences): if tables: - if self.postgres_version[0] >= 8 and self.postgres_version[1] >= 1: + if self.postgres_version[0:2] >= [8,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 @@ -157,5 +151,6 @@ class DatabaseOperations(BaseDatabaseOperations): NotImplementedError if this is the database in use. """ if aggregate.sql_function == 'STDDEV_POP' or aggregate.sql_function == 'VAR_POP': - if self.postgres_version[0] == 8 and self.postgres_version[1] == 2 and self.postgres_version[2] <= 4: - raise NotImplementedError('PostgreSQL 8.2 to 8.2.4 is known to have a faulty implementation of %s. Please upgrade your version of PostgreSQL.' % aggregate.sql_function) + if self.postgres_version[0:2] == [8,2]: + if self.postgres_version[2] is None or self.postgres_version[2] <= 4: + raise NotImplementedError('PostgreSQL 8.2 to 8.2.4 is known to have a faulty implementation of %s. Please upgrade your version of PostgreSQL.' % aggregate.sql_function) diff --git a/django/db/backends/postgresql/version.py b/django/db/backends/postgresql/version.py index ed2ad9e441..8fd773e59b 100644 --- a/django/db/backends/postgresql/version.py +++ b/django/db/backends/postgresql/version.py @@ -4,20 +4,28 @@ Extracts the version of the PostgreSQL server. import re -# This reg-exp is intentionally fairly flexible here. Require only the major -# and minor version numbers, but need to be able to handle standard stuff like: +# This reg-exp is intentionally fairly flexible here. +# Needs to be able to handle stuff like: # PostgreSQL 8.3.6 # EnterpriseDB 8.3 # PostgreSQL 8.3 beta4 -VERSION_RE = re.compile(r'\S+ (\d+)\.(\d+)') +# 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() + try: + return int(major), int(major2), int(minor) + except (ValueError, TypeError): + return int(major), int(major2), None def get_version(cursor): """ - Returns a tuple representing the major and minor version number of the - server. For example, (7, 4) or (8, 3). + Returns a tuple representing the major, minor and revision number of the + server. For example, (7, 4, 1) or (8, 3, 4). The revision number will be + None in the case of initial releases (e.g., 'PostgreSQL 8.3') or in the + case of beta and prereleases ('PostgreSQL 8.4beta1'). """ cursor.execute("SELECT version()") - version = cursor.fetchone()[0] - major, minor = VERSION_RE.search(version).groups() - return int(major), int(minor) - + return _parse_version(cursor.fetchone()[0]) diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index e1a5d830e2..600e864d23 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -105,11 +105,11 @@ class DatabaseWrapper(BaseDatabaseWrapper): cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']]) if not hasattr(self, '_version'): self.__class__._version = get_version(cursor) - if self._version < (8, 0): + if self._version[0:2] < [8, 0]: # No savepoint support for earlier version of PostgreSQL. self.features.uses_savepoints = False if self.features.uses_autocommit: - if self._version < (8, 2): + if self._version[0:2] < [8, 2]: # FIXME: Needs extra code to do reliable model insert # handling, so we forbid it for now. from django.core.exceptions import ImproperlyConfigured diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 15bfe1f579..81dcfde30d 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -21,7 +21,29 @@ class Callproc(unittest.TestCase): def connection_created_test(sender, **kwargs): print 'connection_created signal' -__test__ = {'API_TESTS': ''} +__test__ = {'API_TESTS': """ +# Check Postgres version parsing +>>> from django.db.backends.postgresql import version as pg_version + +>>> pg_version._parse_version("PostgreSQL 8.3.1 on i386-apple-darwin9.2.2, compiled by GCC i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5478)") +(8, 3, 1) + +>>> pg_version._parse_version("PostgreSQL 8.3.6") +(8, 3, 6) + +>>> pg_version._parse_version("PostgreSQL 8.3") +(8, 3, None) + +>>> pg_version._parse_version("EnterpriseDB 8.3") +(8, 3, None) + +>>> pg_version._parse_version("PostgreSQL 8.3 beta4") +(8, 3, None) + +>>> pg_version._parse_version("PostgreSQL 8.4beta1") +(8, 4, None) + +"""} # Unfortunately with sqlite3 the in-memory test database cannot be # closed, and so it cannot be re-opened during testing, and so we