'Compare software version in postgres
Is there a way to compare software version (e.g. X.Y.Z > A.B.C) in postgres ? I'm searching for a function on string/varchar or a "version" type.
I found out that http://pgxn.org/dist/semver/doc/semver.html, but i'm looking for alternatives (not so easy to deploy..)
Thanks a lot.
Solution 1:[1]
Use the cheaper string_to_array(). There is no need for expensive regular expressions here:
SELECT string_to_array(v1, '.')::int[] AS v1
, string_to_array(v2, '.')::int[] AS v2
,(string_to_array(v1, '.')::int[] > string_to_array(v2, '.')::int[]) AS cmp
FROM versions;
Solution 2:[2]
As already suggested an easy way is to work with a numeric format of the version. The variable 'server_version_num' contains a numeric format of the version.
Eg.
- version 9.5.2 => 90502
- version 9.6.0 => 90600
version 10.5 => 100500
select current_setting('server_version_num')
return a number that can be easily compared with another version number.
Solution 3:[3]
An alternative approach is to use
SHOW server_version_num;
This returns a version number that's simpler to compare. e.g. 90610 for 9.6.10.
Update
Clarifying this answer based on the comments below. The version number produced here is machine readable. It is designed to be unique and avoid clashes.
e.g. Postgres version 12.1 will produce 120001 while 12.10 will produce 120010, etc
https://database.guide/how-to-check-your-postgresql-version/
Solution 4:[4]
Maybe you can add a pl function, in my case I have used python and distutils.version:
CREATE FUNCTION _is_major (a text, b text)
RETURNS boolean
AS $$
from distutils.version import LooseVersion
return LooseVersion(a) > LooseVersion(b)
$$ LANGUAGE PLPYTHONU;
You need the postgresql-plpython package.
Solution 5:[5]
Going a bit further Erwin's answer, we can create a function to compare a software version with a requirement (like in ruby). I've wrote a function that does just that, to use it:
SELECT semver_match('4.2.0', '>= 4.0'); -- TRUE
Here's the code and tests:
CREATE OR REPLACE FUNCTION semver_match(version text, req text) RETURNS boolean
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT
AS $$
SELECT CASE
WHEN req LIKE '~>%' THEN
string_to_array(version, '.')::int[] >= string_to_array(substring(req from 4), '.')::int[]
AND
string_to_array(version, '.')::int[] <
-- increment last item by one. (X.Y.Z => X.Y.(Z+1))
array_append(
(string_to_array(substring(req from 4), '.')::int[])[1:(array_length(string_to_array(req, '.'), 1) - 1)], -- X.Y
(string_to_array(substring(req from 4), '.')::int[])[array_length(string_to_array(req, '.'), 1)] + 1 -- Z + 1
)
WHEN req LIKE '>%' THEN string_to_array(version, '.')::int[] > string_to_array(substring(req from 3), '.')::int[]
WHEN req LIKE '<%' THEN string_to_array(version, '.')::int[] < string_to_array(substring(req from 3), '.')::int[]
WHEN req LIKE '>=%' THEN string_to_array(version, '.')::int[] >= string_to_array(substring(req from 4), '.')::int[]
WHEN req LIKE '<=%' THEN string_to_array(version, '.')::int[] <= string_to_array(substring(req from 4), '.')::int[]
WHEN req LIKE '=%' THEN
(string_to_array(version, '.')::int[])[1:array_length(string_to_array(substring(req from 3), '.'), 1)] =
string_to_array(substring(req from 3), '.')::int[]
ELSE NULL
END $$;
-- tests.
SELECT
ver,
req,
CASE WHEN semver_match(ver, req) = expected
THEN '?' ELSE '?' END AS test_passed
FROM (VALUES
('2.3.1', '> 2.3', TRUE),
('2.3.1', '< 2.3.2', TRUE),
('2.3.1', '~> 2.3.2', FALSE),
('2.4.3', '~> 2.3.2', FALSE),
('2.3.2', '~> 2.3.2', TRUE),
('2.3.2', '= 2.3.2', TRUE),
('2.3.2', '= 2.3', TRUE),
('2.3.2', '= 2.4', FALSE)
) AS _ (ver, req, expected)
See my blogpost on that topic for more implementation details.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Gianluca Rossini |
| Solution 3 | |
| Solution 4 | Nando |
| Solution 5 |
