This is an automated email from the ASF dual-hosted git repository.

maxyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudberry-go-libs.git


The following commit(s) were added to refs/heads/main by this push:
     new f7b28a3  feat(dbconn): Add support for Apache Cloudberry Database
f7b28a3 is described below

commit f7b28a35eb01670948e0ba167f97b518f12309a7
Author: Robert Mu <[email protected]>
AuthorDate: Sat Jul 12 21:13:36 2025 +0800

    feat(dbconn): Add support for Apache Cloudberry Database
    
    This commit refactors the database version parsing logic to support
    multiple database types, starting with Greenplum and Apache Cloudberry.
    The previous implementation was tightly coupled to Greenplum Database
    and used a simple string search to extract the version, which was not
    robust or extensible.
    
    Key changes:
    1. Introduces a DBType enum to identify the database (GPDB, CBDB).
    2. Replaces string manipulation with regular expressions in
    ParseVersionInfo for more accurate and reliable version parsing.
    3 . Adds helper functions (IsGPDB(), IsCBDB()) to easily check the
    database type.
    4. Updates unit tests in version_test.go to validate the new parsing
    logic for both supported databases.
    
    Additionally, this change lays the groundwork for resolving a similar
    version detection issue in the apache/cloudberry-gpbackup project.
    The backup utility currently cannot correctly identify Apache Cloudberry,
    and this foundational fix in the shared library is the first step
    toward enabling proper support in that tool.
    
    This new approach makes the system more maintainable and easier to
    extend with support for other database systems in the future.
---
 dbconn/version.go      | 96 ++++++++++++++++++++++++++++++++++++++++++++------
 dbconn/version_test.go | 78 ++++++++++++++++++++++++++++++----------
 2 files changed, 146 insertions(+), 28 deletions(-)

diff --git a/dbconn/version.go b/dbconn/version.go
index 080d1bf..b7351db 100644
--- a/dbconn/version.go
+++ b/dbconn/version.go
@@ -5,11 +5,40 @@ import (
        "strings"
 
        "github.com/blang/semver"
+       "github.com/cloudberrydb/gp-common-go-libs/gplog"
 )
 
+// DBType represents the type of database
+type DBType int
+
+const (
+       Unknown DBType = iota
+       GPDB           // Greenplum Database
+       CBDB           // Apache Cloudberry Database
+)
+
+const (
+       gpdbPattern = `\(Greenplum Database ([0-9]+\.[0-9]+\.[0-9]+)[^)]*\)`
+       cbdbPattern = `\(Apache Cloudberry ([0-9]+\.[0-9]+\.[0-9]+)[^)]*\)`
+)
+
+// String provides string representation of DBType
+func (t DBType) String() string {
+       switch t {
+       case GPDB:
+               return "Greenplum Database"
+       case CBDB:
+               return "Apache Cloudberry"
+       default:
+               return "Unknown Database"
+       }
+}
+
+// GPDBVersion represents version information for a database
 type GPDBVersion struct {
        VersionString string
        SemVer        semver.Version
+       Type          DBType
 }
 
 /*
@@ -26,26 +55,57 @@ func NewVersion(versionStr string) GPDBVersion {
        version := GPDBVersion{
                VersionString: versionStr,
                SemVer:        semver.MustParse(versionStr),
+               Type:          GPDB, // Default to GPDB for tests
        }
        return version
 }
 
+// InitializeVersion parses database version string and returns version 
information
+// It can distinguish between Greenplum Database and Apache Cloudberry 
Database.
 func InitializeVersion(dbconn *DBConn) (dbversion GPDBVersion, err error) {
        err = dbconn.Get(&dbversion, "SELECT pg_catalog.version() AS 
versionstring")
        if err != nil {
                return
        }
-       versionStart := strings.Index(dbversion.VersionString, "(Greenplum 
Database ") + len("(Greenplum Database ")
-       versionEnd := strings.Index(dbversion.VersionString, ")")
-       dbversion.VersionString = 
dbversion.VersionString[versionStart:versionEnd]
 
-       pattern := regexp.MustCompile(`\d+\.\d+\.\d+`)
-       threeDigitVersion := 
pattern.FindStringSubmatch(dbversion.VersionString)[0]
-       dbversion.SemVer, err = semver.Make(threeDigitVersion)
+       // Determine database type and parse version
+       dbversion.ParseVersionInfo(dbversion.VersionString)
+
+       gplog.Info("Initialized database version - Full Version: %s, Database 
Type: %s, Semantic Version: %s",
+               dbversion.VersionString, dbversion.Type, dbversion.SemVer)
        return
 }
 
-func StringToSemVerRange(versionStr string) semver.Range {
+func (dbversion *GPDBVersion) ParseVersionInfo(versionString string) {
+       dbversion.VersionString = versionString
+       dbversion.Type = Unknown
+
+       // Try to match each database type.
+       // We check for Apache Cloudberry first as its string may be a superset 
of others in the future.
+       if ver, ok := dbversion.extractVersion(cbdbPattern); ok {
+               dbversion.Type = CBDB
+               dbversion.SemVer = ver
+       } else if ver, ok := dbversion.extractVersion(gpdbPattern); ok {
+               dbversion.Type = GPDB
+               dbversion.SemVer = ver
+       }
+}
+
+func (dbversion GPDBVersion) extractVersion(pattern string) (semver.Version, 
bool) {
+       re := regexp.MustCompile(pattern)
+       matches := re.FindStringSubmatch(dbversion.VersionString)
+       if len(matches) < 2 {
+               return semver.Version{}, false
+       }
+
+       ver, err := semver.Make(matches[1])
+       if err != nil {
+               return semver.Version{}, false
+       }
+       return ver, true
+}
+
+func (dbversion GPDBVersion) StringToSemVerRange(versionStr string) 
semver.Range {
        numDigits := len(strings.Split(versionStr, "."))
        if numDigits < 3 {
                versionStr += ".x"
@@ -55,16 +115,32 @@ func StringToSemVerRange(versionStr string) semver.Range {
 }
 
 func (dbversion GPDBVersion) Before(targetVersion string) bool {
-       validRange := StringToSemVerRange("<" + targetVersion)
+       validRange := dbversion.StringToSemVerRange("<" + targetVersion)
        return validRange(dbversion.SemVer)
 }
 
 func (dbversion GPDBVersion) AtLeast(targetVersion string) bool {
-       validRange := StringToSemVerRange(">=" + targetVersion)
+       validRange := dbversion.StringToSemVerRange(">=" + targetVersion)
        return validRange(dbversion.SemVer)
 }
 
 func (dbversion GPDBVersion) Is(targetVersion string) bool {
-       validRange := StringToSemVerRange("==" + targetVersion)
+       validRange := dbversion.StringToSemVerRange("==" + targetVersion)
        return validRange(dbversion.SemVer)
 }
+
+func (dbversion GPDBVersion) IsGPDB() bool {
+       return dbversion.Type == GPDB
+}
+
+func (dbversion GPDBVersion) IsCBDB() bool {
+       return dbversion.Type == CBDB
+}
+
+func (srcVersion GPDBVersion) Equals(destVersion GPDBVersion) bool {
+       if srcVersion.Type != destVersion.Type {
+               return false
+       }
+
+       return srcVersion.SemVer.Major == destVersion.SemVer.Major
+}
diff --git a/dbconn/version_test.go b/dbconn/version_test.go
index ab33be3..9037fa8 100644
--- a/dbconn/version_test.go
+++ b/dbconn/version_test.go
@@ -9,30 +9,60 @@ import (
 )
 
 var _ = Describe("dbconn/version tests", func() {
-       fake43 := dbconn.GPDBVersion{VersionString: "4.3.0.0", SemVer: 
semver.MustParse("4.3.0")}
-       fake50 := dbconn.GPDBVersion{VersionString: "5.0.0", SemVer: 
semver.MustParse("5.0.0")}
-       fake51 := dbconn.GPDBVersion{VersionString: "5.1.0", SemVer: 
semver.MustParse("5.1.0")}
+       // Fake versions for testing
+       fakeGPDB43 := dbconn.GPDBVersion{VersionString: "4.3.0.0", SemVer: 
semver.MustParse("4.3.0"), Type: dbconn.GPDB}
+       fakeGPDB5 := dbconn.GPDBVersion{VersionString: "5.0.0", SemVer: 
semver.MustParse("5.0.0"), Type: dbconn.GPDB}
+       fakeGPDB51 := dbconn.GPDBVersion{VersionString: "5.1.0", SemVer: 
semver.MustParse("5.1.0"), Type: dbconn.GPDB}
+       fakeCBDB2 := dbconn.GPDBVersion{VersionString: "2.0.0", SemVer: 
semver.MustParse("2.0.0"), Type: dbconn.CBDB}
+
+       Describe("ParseVersionInfo", func() {
+               It("parses a GPDB version string", func() {
+                       versionStr := "PostgreSQL 12.12 (Greenplum Database 
7.0.0 build commit:bf073b87c0bac9759631746dca1c4c895a304afb) on 
x86_64-pc-linux-gnu"
+                       dbVersion := dbconn.GPDBVersion{}
+                       dbVersion.ParseVersionInfo(versionStr)
+                       Expect(dbVersion.Type).To(Equal(dbconn.GPDB))
+                       Expect(dbVersion.SemVer.String()).To(Equal("7.0.0"))
+                       Expect(dbVersion.IsGPDB()).To(BeTrue())
+                       Expect(dbVersion.IsCBDB()).To(BeFalse())
+               })
+               It("parses an Apache Cloudberry version string", func() {
+                       versionStr := "PostgreSQL 14.4 (Apache Cloudberry 2.0.0 
build commit:a071e3f8aa638786f01bbd08307b6474a1ba7890) on x86_64-pc-linux-gnu"
+                       dbVersion := dbconn.GPDBVersion{}
+                       dbVersion.ParseVersionInfo(versionStr)
+                       Expect(dbVersion.Type).To(Equal(dbconn.CBDB))
+                       Expect(dbVersion.SemVer.String()).To(Equal("2.0.0"))
+                       Expect(dbVersion.IsCBDB()).To(BeTrue())
+                       Expect(dbVersion.IsGPDB()).To(BeFalse())
+               })
+               It("handles an unknown version string", func() {
+                       versionStr := "Some Other Database 1.0.0"
+                       dbVersion := dbconn.GPDBVersion{}
+                       dbVersion.ParseVersionInfo(versionStr)
+                       Expect(dbVersion.Type).To(Equal(dbconn.Unknown))
+                       Expect(dbVersion.SemVer.String()).To(Equal("0.0.0"))
+               })
+       })
        Describe("StringToSemVerRange", func() {
                v400 := semver.MustParse("4.0.0")
                v500 := semver.MustParse("5.0.0")
                v510 := semver.MustParse("5.1.0")
                v501 := semver.MustParse("5.0.1")
                It(`turns "=5" into a range matching 5.x`, func() {
-                       resultRange := dbconn.StringToSemVerRange("=5")
+                       resultRange := fakeGPDB5.StringToSemVerRange("=5")
                        Expect(resultRange(v400)).To(BeFalse())
                        Expect(resultRange(v500)).To(BeTrue())
                        Expect(resultRange(v510)).To(BeTrue())
                        Expect(resultRange(v501)).To(BeTrue())
                })
                It(`turns "=5.0" into a range matching 5.0.x`, func() {
-                       resultRange := dbconn.StringToSemVerRange("=5.0")
+                       resultRange := fakeGPDB5.StringToSemVerRange("=5.0")
                        Expect(resultRange(v400)).To(BeFalse())
                        Expect(resultRange(v500)).To(BeTrue())
                        Expect(resultRange(v510)).To(BeFalse())
                        Expect(resultRange(v501)).To(BeTrue())
                })
                It(`turns "=5.0.0" into a range matching 5.0.0`, func() {
-                       resultRange := dbconn.StringToSemVerRange("=5.0.0")
+                       resultRange := fakeGPDB5.StringToSemVerRange("=5.0.0")
                        Expect(resultRange(v400)).To(BeFalse())
                        Expect(resultRange(v500)).To(BeTrue())
                        Expect(resultRange(v510)).To(BeFalse())
@@ -41,68 +71,80 @@ var _ = Describe("dbconn/version tests", func() {
        })
        Describe("Before", func() {
                It("returns true when comparing 4.3 to 5", func() {
-                       connection.Version = fake43
+                       connection.Version = fakeGPDB43
                        result := connection.Version.Before("5")
                        Expect(result).To(BeTrue())
                })
                It("returns true when comparing 5 to 5.1", func() {
-                       connection.Version = fake50
+                       connection.Version = fakeGPDB5
                        result := connection.Version.Before("5.1")
                        Expect(result).To(BeTrue())
                })
                It("returns false when comparing 5 to 5", func() {
-                       connection.Version = fake50
+                       connection.Version = fakeGPDB5
                        result := connection.Version.Before("5")
                        Expect(result).To(BeFalse())
                })
        })
        Describe("AtLeast", func() {
                It("returns true when comparing 5 to 4.3", func() {
-                       connection.Version = fake50
+                       connection.Version = fakeGPDB5
                        result := connection.Version.AtLeast("4")
                        Expect(result).To(BeTrue())
                })
                It("returns true when comparing 5 to 5", func() {
-                       connection.Version = fake50
+                       connection.Version = fakeGPDB5
                        result := connection.Version.AtLeast("5")
                        Expect(result).To(BeTrue())
                })
                It("returns true when comparing 5.1 to 5.0", func() {
-                       connection.Version = fake51
+                       connection.Version = fakeGPDB51
                        result := connection.Version.AtLeast("5")
                        Expect(result).To(BeTrue())
                })
                It("returns false when comparing 4.3 to 5", func() {
-                       connection.Version = fake43
+                       connection.Version = fakeGPDB43
                        result := connection.Version.AtLeast("5")
                        Expect(result).To(BeFalse())
                })
                It("returns false when comparing 5.0 to 5.1", func() {
-                       connection.Version = fake50
+                       connection.Version = fakeGPDB5
                        result := connection.Version.AtLeast("5.1")
                        Expect(result).To(BeFalse())
                })
        })
        Describe("Is", func() {
                It("returns true when comparing 5 to 5", func() {
-                       connection.Version = fake50
+                       connection.Version = fakeGPDB5
                        result := connection.Version.Is("5")
                        Expect(result).To(BeTrue())
                })
                It("returns true when comparing 5.1 to 5", func() {
-                       connection.Version = fake51
+                       connection.Version = fakeGPDB51
                        result := connection.Version.Is("5")
                        Expect(result).To(BeTrue())
                })
                It("returns false when comparing 5.0 to 5.1", func() {
-                       connection.Version = fake50
+                       connection.Version = fakeGPDB5
                        result := connection.Version.Is("5.1")
                        Expect(result).To(BeFalse())
                })
                It("returns false when comparing 4.3 to 5", func() {
-                       connection.Version = fake43
+                       connection.Version = fakeGPDB43
                        result := connection.Version.Is("5")
                        Expect(result).To(BeFalse())
                })
        })
+       Describe("Equals", func() {
+               It("returns false if db types are different", func() {
+                       Expect(fakeGPDB5.Equals(fakeCBDB2)).To(BeFalse())
+               })
+               It("returns true if db types are same and major version is 
same", func() {
+                       anotherGPDB5 := dbconn.GPDBVersion{SemVer: 
semver.MustParse("5.2.0"), Type: dbconn.GPDB}
+                       Expect(fakeGPDB5.Equals(anotherGPDB5)).To(BeTrue())
+               })
+               It("returns false if db types are same but major version is 
different", func() {
+                       Expect(fakeGPDB5.Equals(fakeGPDB43)).To(BeFalse())
+               })
+       })
 })


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to