diff --git a/asyncpg/serverversion.py b/asyncpg/serverversion.py index 6b2d28c7..31568a2e 100644 --- a/asyncpg/serverversion.py +++ b/asyncpg/serverversion.py @@ -5,53 +5,56 @@ # the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 -from . import types +import re + +from .types import ServerVersion + +version_regex = re.compile( + r"(Postgre[^\s]*)?\s*" + r"(?P[0-9]+)\.?" + r"((?P[0-9]+)\.?)?" + r"(?P[0-9]+)?" + r"(?P[a-z]+)?" + r"(?P[0-9]+)?" +) def split_server_version_string(version_string): - version_string = version_string.strip() - if version_string.startswith('PostgreSQL '): - version_string = version_string[len('PostgreSQL '):] - if version_string.startswith('Postgres-XL'): - version_string = version_string[len('Postgres-XL '):] - - # Some distros (e.g Debian) like may inject their branding - # into the numeric version string, so make sure to only look - # at stuff before the first space. - version_string = version_string.split(' ')[0] - parts = version_string.strip().split('.') - if not parts[-1].isdigit(): - # release level specified - lastitem = parts[-1] - levelpart = lastitem.rstrip('0123456789').lower() - if levelpart != lastitem: - serial = int(lastitem[len(levelpart):]) - else: - serial = 0 - - level = levelpart.lstrip('0123456789') - if level != levelpart: - parts[-1] = levelpart[:-len(level)] - else: - parts[-1] = 0 - else: - level = 'final' - serial = 0 - - if int(parts[0]) >= 10: - # Since PostgreSQL 10 the versioning scheme has changed. - # 10.x really means 10.0.x. While parsing 10.1 - # as (10, 1) may seem less confusing, in practice most - # version checks are written as version[:2], and we - # want to keep that behaviour consistent, i.e not fail - # a major version check due to a bugfix release. - parts.insert(1, 0) - - versions = [int(p) for p in parts][:3] - if len(versions) < 3: - versions += [0] * (3 - len(versions)) - - versions.append(level) - versions.append(serial) - - return types.ServerVersion(*versions) + version_match = version_regex.search(version_string) + + if version_match is None: + raise ValueError( + "Unable to parse Postgres " + f'version from "{version_string}"' + ) + + version = version_match.groupdict() + for ver_key, ver_value in version.items(): + # Cast all possible versions parts to int + try: + version[ver_key] = int(ver_value) + except (TypeError, ValueError): + pass + + if version.get("major") < 10: + return ServerVersion( + version.get("major"), + version.get("minor") or 0, + version.get("micro") or 0, + version.get("releaselevel") or "final", + version.get("serial") or 0, + ) + + # Since PostgreSQL 10 the versioning scheme has changed. + # 10.x really means 10.0.x. While parsing 10.1 + # as (10, 1) may seem less confusing, in practice most + # version checks are written as version[:2], and we + # want to keep that behaviour consistent, i.e not fail + # a major version check due to a bugfix release. + return ServerVersion( + version.get("major"), + 0, + version.get("minor") or 0, + version.get("releaselevel") or "final", + version.get("serial") or 0, + ) diff --git a/tests/test_connect.py b/tests/test_connect.py index ff884af8..84eac202 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -64,6 +64,9 @@ def test_server_version_02(self): ("10.1", (10, 0, 1, 'final', 0),), ("11.1.2", (11, 0, 1, 'final', 0),), ("PostgreSQL 10.1 (Debian 10.1-3)", (10, 0, 1, 'final', 0),), + ("PostgreSQL 11.2-YB-2.7.1.1-b0 on x86_64-pc-linux-gnu, " + "compiled by gcc (Homebrew gcc 5.5.0_4) 5.5.0, 64-bit", + (11, 0, 2, "final", 0),), ] for version, expected in versions: result = split_server_version_string(version)