diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 4f6bcbf973..5a94c815bd 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -105,6 +105,13 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.creation = DatabaseCreation(self) self.introspection = DatabaseIntrospection(self) self.validation = BaseDatabaseValidation(self) + self._pg_version = None + + def _get_pg_version(self): + if self._pg_version is None: + self._pg_version = get_version(self.connection) + return self._pg_version + pg_version = property(_get_pg_version) def _cursor(self): new_connection = False @@ -139,8 +146,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): 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) + self._get_pg_version() if self.features.uses_autocommit: # FIXME: Eventually we'll enable this by default for # versions that support it, but, right now, that's hard to diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py index ecd0dde1c6..fe021b5fc6 100644 --- a/django/db/backends/postgresql_psycopg2/operations.py +++ b/django/db/backends/postgresql_psycopg2/operations.py @@ -6,15 +6,6 @@ from django.db.backends import BaseDatabaseOperations class DatabaseOperations(BaseDatabaseOperations): def __init__(self, connection): super(DatabaseOperations, self).__init__(connection) - self._postgres_version = None - - def _get_postgres_version(self): - if self._postgres_version is None: - from django.db.backends.postgresql_psycopg2.version import get_version - cursor = self.connection.cursor() - self._postgres_version = get_version(cursor) - return self._postgres_version - postgres_version = property(_get_postgres_version) def date_extract_sql(self, lookup_type, field_name): # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT @@ -166,9 +157,9 @@ class DatabaseOperations(BaseDatabaseOperations): NotImplementedError if this is the database in use. """ if aggregate.sql_function in ('STDDEV_POP', 'VAR_POP'): - 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) + pg_version = self.connection.pg_version + if pg_version >= 80200 and pg_version <= 80204: + 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) def max_name_length(self): """ diff --git a/django/db/backends/postgresql_psycopg2/version.py b/django/db/backends/postgresql_psycopg2/version.py index b9298296ea..8ef516704e 100644 --- a/django/db/backends/postgresql_psycopg2/version.py +++ b/django/db/backends/postgresql_psycopg2/version.py @@ -17,16 +17,27 @@ 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) + return int(major) * 10000 + int(major2) * 100 + int(minor) except (ValueError, TypeError): - return int(major), int(major2), None + return int(major) * 10000 + int(major2) * 100 -def get_version(cursor): +def get_version(connection): """ - 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'). + Returns an integer representing the major, minor and revision number of the + server. Format is the one used for the return value of libpq + PQServerVersion()/``server_version`` connection attribute (available in + newer psycopg2 versions.) + + For example, 80304 for 8.3.4. The last two digits will be 00 in the case of + releases (e.g., 80400 for 'PostgreSQL 8.4') or in the case of beta and + prereleases (e.g. 90100 for 'PostgreSQL 9.1beta2'). + + PQServerVersion()/``server_version`` doesn't execute a query so try that + first, then fallback to a ``SELECT version()`` query. """ - cursor.execute("SELECT version()") - return _parse_version(cursor.fetchone()[0]) + if hasattr(connection, 'server_version'): + return connection.server_version + else: + cursor = connection.cursor() + cursor.execute("SELECT version()") + return _parse_version(cursor.fetchone()[0]) diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 33c8763037..29db6a705a 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -196,12 +196,34 @@ class PostgresVersionTest(TestCase): self.assertEqual(pg_version._parse_version(version_string), version) def test_parsing(self): - self.assert_parses("PostgreSQL 8.3 beta4", (8, 3, None)) - self.assert_parses("PostgreSQL 8.3", (8, 3, None)) - self.assert_parses("EnterpriseDB 8.3", (8, 3, None)) - self.assert_parses("PostgreSQL 8.3.6", (8, 3, 6)) - self.assert_parses("PostgreSQL 8.4beta1", (8, 4, None)) - self.assert_parses("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)) + """Test PostgreSQL version parsing from `SELECT version()` output""" + self.assert_parses("PostgreSQL 8.3 beta4", 80300) + self.assert_parses("PostgreSQL 8.3", 80300) + self.assert_parses("EnterpriseDB 8.3", 80300) + self.assert_parses("PostgreSQL 8.3.6", 80306) + self.assert_parses("PostgreSQL 8.4beta1", 80400) + self.assert_parses("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)", 80301) + + def test_version_detection(self): + """Test PostgreSQL version detection""" + + # Helper mocks + class CursorMock(object): + "Very simple mock of DB-API cursor" + def execute(self, arg): + pass + + def fetchone(self): + return ["PostgreSQL 8.3"] + + class OlderConnectionMock(object): + "Mock of psycopg2 (< 2.0.12) connection" + def cursor(self): + return CursorMock() + + # psycopg2 < 2.0.12 code path + conn = OlderConnectionMock() + self.assertEqual(pg_version.get_version(conn), 80300) # Unfortunately with sqlite3 the in-memory test database cannot be # closed, and so it cannot be re-opened during testing, and so we