Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #11065, #11285 -- Streamlined PostgreSQL version detection, fix…

…ing incompatibility with multi-db. Thanks findepi for the report.

Changed our internal representation of the PostgreSQL version from tuples to
integers as used by libpq and psycopg2. This simplifies version comparison
operations.

Also, using the associated libpq/psycopg2 API allows to remove the need for
manually issuing in-band ``SELECT version()`` SQL queries to obtain the server
version (or at least reduce its number if version of psycopg2 in use is older
than 2.0.12). Refs #10509.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16439 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit f2dca72afd89160580454abe5a90e0b4c6be196e 1 parent 656360c
Ramiro Morales authored
10  django/db/backends/postgresql_psycopg2/base.py
@@ -105,6 +105,13 @@ def __init__(self, *args, **kwargs):
105 105
         self.creation = DatabaseCreation(self)
106 106
         self.introspection = DatabaseIntrospection(self)
107 107
         self.validation = BaseDatabaseValidation(self)
  108
+        self._pg_version = None
  109
+
  110
+    def _get_pg_version(self):
  111
+        if self._pg_version is None:
  112
+            self._pg_version = get_version(self.connection)
  113
+        return self._pg_version
  114
+    pg_version = property(_get_pg_version)
108 115
 
109 116
     def _cursor(self):
110 117
         new_connection = False
@@ -139,8 +146,7 @@ def _cursor(self):
139 146
         if new_connection:
140 147
             if set_tz:
141 148
                 cursor.execute("SET TIME ZONE %s", [settings_dict['TIME_ZONE']])
142  
-            if not hasattr(self, '_version'):
143  
-                self.__class__._version = get_version(cursor)
  149
+            self._get_pg_version()
144 150
             if self.features.uses_autocommit:
145 151
                 # FIXME: Eventually we'll enable this by default for
146 152
                 # versions that support it, but, right now, that's hard to
15  django/db/backends/postgresql_psycopg2/operations.py
@@ -6,15 +6,6 @@
6 6
 class DatabaseOperations(BaseDatabaseOperations):
7 7
     def __init__(self, connection):
8 8
         super(DatabaseOperations, self).__init__(connection)
9  
-        self._postgres_version = None
10  
-
11  
-    def _get_postgres_version(self):
12  
-        if self._postgres_version is None:
13  
-            from django.db.backends.postgresql_psycopg2.version import get_version
14  
-            cursor = self.connection.cursor()
15  
-            self._postgres_version = get_version(cursor)
16  
-        return self._postgres_version
17  
-    postgres_version = property(_get_postgres_version)
18 9
 
19 10
     def date_extract_sql(self, lookup_type, field_name):
20 11
         # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
@@ -166,9 +157,9 @@ def check_aggregate_support(self, aggregate):
166 157
         NotImplementedError if this is the database in use.
167 158
         """
168 159
         if aggregate.sql_function in ('STDDEV_POP', 'VAR_POP'):
169  
-            if self.postgres_version[0:2] == (8,2):
170  
-                if self.postgres_version[2] is None or self.postgres_version[2] <= 4:
171  
-                    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)
  160
+            pg_version = self.connection.pg_version
  161
+            if pg_version >= 80200 and pg_version <= 80204:
  162
+                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)
172 163
 
173 164
     def max_name_length(self):
174 165
         """
29  django/db/backends/postgresql_psycopg2/version.py
@@ -17,16 +17,27 @@ def _parse_version(text):
17 17
     "Internal parsing method. Factored out for testing purposes."
18 18
     major, major2, minor = VERSION_RE.search(text).groups()
19 19
     try:
20  
-        return int(major), int(major2), int(minor)
  20
+        return int(major) * 10000 + int(major2) * 100 + int(minor)
21 21
     except (ValueError, TypeError):
22  
-        return int(major), int(major2), None
  22
+        return int(major) * 10000 +  int(major2) * 100
23 23
 
24  
-def get_version(cursor):
  24
+def get_version(connection):
25 25
     """
26  
-    Returns a tuple representing the major, minor and revision number of the
27  
-    server. For example, (7, 4, 1) or (8, 3, 4). The revision number will be
28  
-    None in the case of initial releases (e.g., 'PostgreSQL 8.3') or in the
29  
-    case of beta and prereleases ('PostgreSQL 8.4beta1').
  26
+    Returns an integer representing the major, minor and revision number of the
  27
+    server. Format is the one used for the return value of libpq
  28
+    PQServerVersion()/``server_version`` connection attribute (available in
  29
+    newer psycopg2 versions.)
  30
+
  31
+    For example, 80304 for 8.3.4. The last two digits will be 00 in the case of
  32
+    releases (e.g., 80400 for 'PostgreSQL 8.4') or in the case of beta and
  33
+    prereleases (e.g. 90100 for 'PostgreSQL 9.1beta2').
  34
+
  35
+    PQServerVersion()/``server_version`` doesn't execute a query so try that
  36
+    first, then fallback to a ``SELECT version()`` query.
30 37
     """
31  
-    cursor.execute("SELECT version()")
32  
-    return _parse_version(cursor.fetchone()[0])
  38
+    if hasattr(connection, 'server_version'):
  39
+        return connection.server_version
  40
+    else:
  41
+        cursor = connection.cursor()
  42
+        cursor.execute("SELECT version()")
  43
+        return _parse_version(cursor.fetchone()[0])
34  tests/regressiontests/backends/tests.py
@@ -196,12 +196,34 @@ def assert_parses(self, version_string, version):
196 196
         self.assertEqual(pg_version._parse_version(version_string), version)
197 197
 
198 198
     def test_parsing(self):
199  
-        self.assert_parses("PostgreSQL 8.3 beta4", (8, 3, None))
200  
-        self.assert_parses("PostgreSQL 8.3", (8, 3, None))
201  
-        self.assert_parses("EnterpriseDB 8.3", (8, 3, None))
202  
-        self.assert_parses("PostgreSQL 8.3.6", (8, 3, 6))
203  
-        self.assert_parses("PostgreSQL 8.4beta1", (8, 4, None))
204  
-        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))
  199
+        """Test PostgreSQL version parsing from `SELECT version()` output"""
  200
+        self.assert_parses("PostgreSQL 8.3 beta4", 80300)
  201
+        self.assert_parses("PostgreSQL 8.3", 80300)
  202
+        self.assert_parses("EnterpriseDB 8.3", 80300)
  203
+        self.assert_parses("PostgreSQL 8.3.6", 80306)
  204
+        self.assert_parses("PostgreSQL 8.4beta1", 80400)
  205
+        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)
  206
+
  207
+    def test_version_detection(self):
  208
+        """Test PostgreSQL version detection"""
  209
+
  210
+        # Helper mocks
  211
+        class CursorMock(object):
  212
+            "Very simple mock of DB-API cursor"
  213
+            def execute(self, arg):
  214
+                pass
  215
+
  216
+            def fetchone(self):
  217
+                return ["PostgreSQL 8.3"]
  218
+
  219
+        class OlderConnectionMock(object):
  220
+            "Mock of psycopg2 (< 2.0.12) connection"
  221
+            def cursor(self):
  222
+                return CursorMock()
  223
+
  224
+        # psycopg2 < 2.0.12 code path
  225
+        conn = OlderConnectionMock()
  226
+        self.assertEqual(pg_version.get_version(conn), 80300)
205 227
 
206 228
 # Unfortunately with sqlite3 the in-memory test database cannot be
207 229
 # closed, and so it cannot be re-opened during testing, and so we

0 notes on commit f2dca72

Please sign in to comment.
Something went wrong with that request. Please try again.