Relax version check (#8356)

Relaxes the Citus version compatibility check for asynchronous replica
upgrades.

Citus requires the loaded shared library and the installed extension to
have identical major and minor versions.
This change keeps the library vs. control-file version checks the same
(a restart is still needed after upgrading the library), but allows the
installed extension to differ by at most one minor version from the
loaded library.

With this relaxation in place, replicas can lag by a single minor
version during phased upgrades without triggering a version mismatch for
distributed queries.

Tested the change on release branch 13.2 as main already points to the
next major version. Versioned test capability from
https://github.com/citusdata/citus/pull/8361 is used in test.

See branch for test
https://github.com/citusdata/citus/tree/eag/release-13.2/relax-test ,
the so and sql versions are 13.2-1.

```
// Create extension fails
CITUSVERSION=13.0-1 citus_tests/run_test.py adaptive_executor
....
CREATE DATABASE
ERROR:  specified version incompatible with loaded Citus library
DETAIL:  Loaded library requires 13.2, but 13.0-1 was specified.
HINT:  If a newer library is present, restart the database and try the command again.
....

// Test proceeds as expected 
CITUSVERSION=13.1-1 citus_tests/run_test.py adaptive_executor
CREATE DATABASE
CREATE EXTENSION
CREATE FUNCTION

// Test proceeds as expected
CITUSVERSION=13.2-1 citus_tests/run_test.py adaptive_executor

CREATE DATABASE
CREATE EXTENSION
CREATE FUNCTION
```
pull/7823/merge
eaydingol 2025-12-17 16:57:36 +03:00 committed by GitHub
parent 84fc6801ba
commit c125b9bec5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 105 additions and 3 deletions

View File

@ -162,6 +162,8 @@ static bool CitusColumnarHasBeenLoadedInternal(void);
static bool CitusColumnarHasBeenLoaded(void);
static bool CheckCitusColumnarVersion(int elevel);
static bool MajorVersionsCompatibleColumnar(char *leftVersion, char *rightVersion);
static bool MinorVersionsCompatibleRelaxedColumnar(char *leftVersion, char *rightVersion);
static int ParseVersionComponent(const char *version, char **endPtr);
/* global variables for CheckCitusColumnarVersion */
static bool extensionLoadedColumnar = false;
@ -2869,7 +2871,7 @@ CheckInstalledVersionColumnar(int elevel)
char *installedVersion = InstalledExtensionVersionColumnar();
if (!MajorVersionsCompatibleColumnar(installedVersion, CITUS_EXTENSIONVERSION))
if (!MinorVersionsCompatibleRelaxedColumnar(installedVersion, CITUS_EXTENSIONVERSION))
{
ereport(elevel, (errmsg("loaded Citus library version differs from installed "
"extension version"),
@ -2928,6 +2930,55 @@ MajorVersionsCompatibleColumnar(char *leftVersion, char *rightVersion)
}
/*
* ParseVersionComponent parses the integer at the current position and
* advances endPtr past the parsed digits to the next character.
*/
static int
ParseVersionComponent(const char *version, char **endPtr)
{
errno = 0;
long int val = strtol(version, endPtr, 10);
if (errno == ERANGE || val > INT_MAX || val < INT_MIN)
{
ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("Invalid integer in version string")));
}
return (int) val;
}
/*
* MinorVersionsCompatibleRelaxedColumnar checks if two versions have the same major
* version and their minor versions differ by at most 1. The schema version
* (after '-') is ignored. Returns true if compatible, false otherwise.
*
* Version format expected: "major.minor-schema" (e.g., "13.1-2")
*/
bool
MinorVersionsCompatibleRelaxedColumnar(char *leftVersion, char *rightVersion)
{
char *leftSep;
char *rightSep;
int leftMajor = ParseVersionComponent(leftVersion, &leftSep);
int rightMajor = ParseVersionComponent(rightVersion, &rightSep);
if (leftMajor != rightMajor)
{
return false;
}
int leftMinor = (*leftSep == '.') ? ParseVersionComponent(leftSep + 1, &leftSep) : 0;
int rightMinor = (*rightSep == '.') ? ParseVersionComponent(rightSep + 1, &rightSep) :
0;
int diff = leftMinor - rightMinor;
return diff >= -1 && diff <= 1;
}
/*
* AvailableExtensionVersion returns the Citus version from citus.control file. It also
* saves the result, thus consecutive calls to CitusExtensionAvailableVersion will

View File

@ -80,7 +80,7 @@ ErrorIfUnstableCreateOrAlterExtensionStmt(Node *parseTree)
if (newExtensionVersion != NULL)
{
/* explicit version provided in CREATE or ALTER EXTENSION UPDATE; verify */
if (!MajorVersionsCompatible(newExtensionVersion, CITUS_EXTENSIONVERSION))
if (!MinorVersionsCompatibleRelaxed(newExtensionVersion, CITUS_EXTENSIONVERSION))
{
ereport(ERROR, (errmsg("specified version incompatible with loaded "
"Citus library"),

View File

@ -335,6 +335,7 @@ static Oid DistAuthinfoRelationId(void);
static Oid DistAuthinfoIndexId(void);
static Oid DistPoolinfoRelationId(void);
static Oid DistPoolinfoIndexId(void);
static int ParseVersionComponent(const char *version, char **endPtr);
/* exports for SQL callable functions */
PG_FUNCTION_INFO_V1(citus_dist_partition_cache_invalidate);
@ -2436,7 +2437,7 @@ CheckInstalledVersion(int elevel)
char *installedVersion = InstalledExtensionVersion();
if (!MajorVersionsCompatible(installedVersion, CITUS_EXTENSIONVERSION))
if (!MinorVersionsCompatibleRelaxed(installedVersion, CITUS_EXTENSIONVERSION))
{
ereport(elevel, (errmsg("loaded Citus library version differs from installed "
"extension version"),
@ -2514,6 +2515,55 @@ MajorVersionsCompatible(char *leftVersion, char *rightVersion)
}
/*
* ParseVersionComponent parses the integer at the current position and
* advances endPtr past the parsed digits to the next character.
*/
static int
ParseVersionComponent(const char *version, char **endPtr)
{
errno = 0;
long int val = strtol(version, endPtr, 10);
if (errno == ERANGE || val > INT_MAX || val < INT_MIN)
{
ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("Invalid integer in version string")));
}
return (int) val;
}
/*
* MinorVersionsCompatibleRelaxed checks if two versions have the same major
* version and their minor versions differ by at most 1. The schema version
* (after '-') is ignored. Returns true if compatible, false otherwise.
*
* Version format expected: "major.minor-schema" (e.g., "13.1-2")
*/
bool
MinorVersionsCompatibleRelaxed(char *leftVersion, char *rightVersion)
{
char *leftSep;
char *rightSep;
int leftMajor = ParseVersionComponent(leftVersion, &leftSep);
int rightMajor = ParseVersionComponent(rightVersion, &rightSep);
if (leftMajor != rightMajor)
{
return false;
}
int leftMinor = (*leftSep == '.') ? ParseVersionComponent(leftSep + 1, &leftSep) : 0;
int rightMinor = (*rightSep == '.') ? ParseVersionComponent(rightSep + 1, &rightSep) :
0;
int diff = leftMinor - rightMinor;
return diff >= -1 && diff <= 1;
}
/*
* AvailableExtensionVersion returns the Citus version from citus.control file. It also
* saves the result, thus consecutive calls to CitusExtensionAvailableVersion will

View File

@ -217,6 +217,7 @@ extern bool CheckCitusVersion(int elevel);
extern bool CheckAvailableVersion(int elevel);
extern bool InstalledAndAvailableVersionsSame(void);
extern bool MajorVersionsCompatible(char *leftVersion, char *rightVersion);
extern bool MinorVersionsCompatibleRelaxed(char *leftVersion, char *rightVersion);
extern void ErrorIfInconsistentShardIntervals(CitusTableCacheEntry *cacheEntry);
extern void EnsureModificationsCanRun(void);
extern void EnsureModificationsCanRunOnRelation(Oid relationId);