Skip to content

Commit

Permalink
Make Server Version Extraction More Flexible (#778)
Browse files Browse the repository at this point in the history
#250
#261
#771

Just tried to use this library through Ormar->Databases->AsyncPG against
a Yugabyte cluster and hit issue 771. Looks like this has been a problem
for a while now so going for a complete overhaul of the server version
extraction method. Using a groupdict regex against the version string
allows for much higher flexibility in extracting what we're looking for
and fixes #771 while not breaking any of the existing version patterns.
  • Loading branch information
Natrinicle authored Aug 2, 2021
1 parent da58cd2 commit d076169
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 47 deletions.
97 changes: 50 additions & 47 deletions asyncpg/serverversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<major>[0-9]+)\.?"
r"((?P<minor>[0-9]+)\.?)?"
r"(?P<micro>[0-9]+)?"
r"(?P<releaselevel>[a-z]+)?"
r"(?P<serial>[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,
)
3 changes: 3 additions & 0 deletions tests/test_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit d076169

Please sign in to comment.