From 7597059f8d4830dc4146d38a657e1b2cfbaf7383 Mon Sep 17 00:00:00 2001 From: Vlasdislav Date: Thu, 12 Nov 2026 10:22:27 +0300 Subject: [PATCH 1/2] Fix Yezzey extension version comparison --- pkg/database/database.go | 36 ++++++++++++++++++++++++++++--- pkg/database/database_test.go | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 pkg/database/database_test.go diff --git a/pkg/database/database.go b/pkg/database/database.go index dc0405e5..fffe7111 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -2,6 +2,8 @@ package database import ( "fmt" + "strconv" + "strings" "github.com/jackc/pgx" "github.com/jackc/pgx/pgtype" @@ -33,6 +35,36 @@ type LSN struct { lsn string } +// versionAtLeast compares numeric extension versions component by component. +func versionAtLeast(version, minimum string) (bool, error) { + actual, required := strings.Split(version, "."), strings.Split(minimum, ".") + if len(actual) > 3 || len(required) > 3 { + return false, fmt.Errorf("invalid yezzey version: %q or %q", version, minimum) + } + for len(actual) < 3 { + actual = append(actual, "0") + } + for len(required) < 3 { + required = append(required, "0") + } + var a, b [3]uint64 + for i := range a { + var err error + if a[i], err = strconv.ParseUint(actual[i], 10, 64); err != nil { + return false, fmt.Errorf("invalid yezzey version %q: %w", version, err) + } + if b[i], err = strconv.ParseUint(required[i], 10, 64); err != nil { + return false, fmt.Errorf("invalid yezzey version %q: %w", minimum, err) + } + } + for i := range a { + if a[i] != b[i] { + return a[i] > b[i], nil + } + } + return true, nil +} + func checkVersion(c *pgx.Conn, exp string) (bool, error) { rows, err := c.Query(`SELECT extversion FROM pg_catalog.pg_extension WHERE extname OPERATOR(pg_catalog.=) 'yezzey';`) if err != nil { @@ -51,8 +83,7 @@ func checkVersion(c *pgx.Conn, exp string) (bool, error) { return false, fmt.Errorf("unable to get yezzey extension version: duplicate output") } - /* we compare versions lexicographically */ - return ver >= exp, nil + return versionAtLeast(ver, exp) } return false, fmt.Errorf("unable to get yezzey extension version") @@ -82,7 +113,6 @@ func (database *DatabaseHandler) GetVirtualExpireIndexByPrefix(port uint64, db D upper := nextPrefix(prefix) usePrefix := lower != "" && upper != "" - /* Todo: check that yezzey version >= 1.8.4 */ if ch, err := checkVersion(conn, "1.8.4"); err != nil { ylogger.Zero.Warn().Err(err).Msg("GetVirtualExpireIndexByPrefix: failed") return err diff --git a/pkg/database/database_test.go b/pkg/database/database_test.go new file mode 100644 index 00000000..712976b7 --- /dev/null +++ b/pkg/database/database_test.go @@ -0,0 +1,40 @@ +package database + +import "testing" + +func TestVersionAtLeast(t *testing.T) { + tests := []struct { + version string + minimum string + want bool + wantErr bool + }{ + {"1.8.11", "1.8.4", true, false}, + {"1.8.10", "1.8.4", true, false}, + {"1.8.4", "1.8.4", true, false}, + {"1.8.3", "1.8.4", false, false}, + {"1.10.0", "1.8.4", true, false}, + {"2.0.0", "1.8.4", true, false}, + {"1.7.99", "1.8.4", false, false}, + {"1.8", "1.8.4", false, false}, + {"1.8", "1.8.0", true, false}, + {"1", "1.0.0", true, false}, + {"2", "1.8.4", true, false}, + {"1.0.0", "1", true, false}, + {"1.8.0.1", "1.8.4", false, true}, + {"", "1.8.4", false, true}, + {"1.8.", "1.8.4", false, true}, + {"1.8.11-rc1", "1.8.4", false, true}, + {"1..4", "1.8.4", false, true}, + {"1.8.18446744073709551616", "1.8.4", false, true}, + {"1.8.11", "invalid", false, true}, + } + for _, tt := range tests { + t.Run(tt.version+"_vs_"+tt.minimum, func(t *testing.T) { + got, err := versionAtLeast(tt.version, tt.minimum) + if (err != nil) != tt.wantErr || got != tt.want { + t.Fatalf("versionAtLeast(%q, %q) = %v, %v; want %v, error=%v", tt.version, tt.minimum, got, err, tt.want, tt.wantErr) + } + }) + } +} From 63ca3358d26713c54008093b8d37df1983b567d8 Mon Sep 17 00:00:00 2001 From: Vlasdislav Date: Thu, 12 Nov 2026 11:31:12 +0300 Subject: [PATCH 2/2] Remove obsolete Yezzey version check --- pkg/database/database.go | 107 ++++++++-------------------------- pkg/database/database_test.go | 40 ------------- 2 files changed, 23 insertions(+), 124 deletions(-) delete mode 100644 pkg/database/database_test.go diff --git a/pkg/database/database.go b/pkg/database/database.go index fffe7111..4080d811 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -2,8 +2,6 @@ package database import ( "fmt" - "strconv" - "strings" "github.com/jackc/pgx" "github.com/jackc/pgx/pgtype" @@ -35,60 +33,6 @@ type LSN struct { lsn string } -// versionAtLeast compares numeric extension versions component by component. -func versionAtLeast(version, minimum string) (bool, error) { - actual, required := strings.Split(version, "."), strings.Split(minimum, ".") - if len(actual) > 3 || len(required) > 3 { - return false, fmt.Errorf("invalid yezzey version: %q or %q", version, minimum) - } - for len(actual) < 3 { - actual = append(actual, "0") - } - for len(required) < 3 { - required = append(required, "0") - } - var a, b [3]uint64 - for i := range a { - var err error - if a[i], err = strconv.ParseUint(actual[i], 10, 64); err != nil { - return false, fmt.Errorf("invalid yezzey version %q: %w", version, err) - } - if b[i], err = strconv.ParseUint(required[i], 10, 64); err != nil { - return false, fmt.Errorf("invalid yezzey version %q: %w", minimum, err) - } - } - for i := range a { - if a[i] != b[i] { - return a[i] > b[i], nil - } - } - return true, nil -} - -func checkVersion(c *pgx.Conn, exp string) (bool, error) { - rows, err := c.Query(`SELECT extversion FROM pg_catalog.pg_extension WHERE extname OPERATOR(pg_catalog.=) 'yezzey';`) - if err != nil { - return false, fmt.Errorf("unable to get ao/aocs tables %v", err) //fix - } - - defer rows.Close() - ylogger.Zero.Debug().Msg("executed select") - - if rows.Next() { - var ver string - if err := rows.Scan(&ver); err != nil { - return false, fmt.Errorf("unable to parse query output %v", err) - } - if rows.Next() { - return false, fmt.Errorf("unable to get yezzey extension version: duplicate output") - } - - return versionAtLeast(ver, exp) - } - - return false, fmt.Errorf("unable to get yezzey extension version") -} - func nextPrefix(prefix string) string { b := []byte(prefix) for i := len(b) - 1; i >= 0; i-- { @@ -113,38 +57,33 @@ func (database *DatabaseHandler) GetVirtualExpireIndexByPrefix(port uint64, db D upper := nextPrefix(prefix) usePrefix := lower != "" && upper != "" - if ch, err := checkVersion(conn, "1.8.4"); err != nil { - ylogger.Zero.Warn().Err(err).Msg("GetVirtualExpireIndexByPrefix: failed") - return err - } else if ch { - var rows *pgx.Rows - if usePrefix { - rows, err = conn.Query(`SELECT x_path, lsn FROM yezzey.yezzey_expire_hint WHERE x_path COLLATE "C" OPERATOR(pg_catalog.>=) $1 COLLATE "C" AND x_path COLLATE "C" OPERATOR(pg_catalog.<) $2 COLLATE "C";`, lower, upper) - } else { - rows, err = conn.Query(`SELECT x_path, lsn FROM yezzey.yezzey_expire_hint;`) + var rows *pgx.Rows + if usePrefix { + rows, err = conn.Query(`SELECT x_path, lsn FROM yezzey.yezzey_expire_hint WHERE x_path COLLATE "C" OPERATOR(pg_catalog.>=) $1 COLLATE "C" AND x_path COLLATE "C" OPERATOR(pg_catalog.<) $2 COLLATE "C";`, lower, upper) + } else { + rows, err = conn.Query(`SELECT x_path, lsn FROM yezzey.yezzey_expire_hint;`) + } + if err != nil { + return fmt.Errorf("unable to get ao/aocs tables %v", err) //fix + } + defer rows.Close() + ylogger.Zero.Debug().Bool("prefix filtered", usePrefix).Msg("executed select") + + for rows.Next() { + row := ExpireHint{} + if err := rows.Scan(&row.x_path, &row.expireLsn); err != nil { + return fmt.Errorf("unable to parse query output %v", err) } + + lsn, err := pgx.ParseLSN(row.expireLsn) if err != nil { - return fmt.Errorf("unable to get ao/aocs tables %v", err) //fix - } - defer rows.Close() - ylogger.Zero.Debug().Bool("prefix filtered", usePrefix).Msg("executed select") - - for rows.Next() { - row := ExpireHint{} - if err := rows.Scan(&row.x_path, &row.expireLsn); err != nil { - return fmt.Errorf("unable to parse query output %v", err) - } - - lsn, err := pgx.ParseLSN(row.expireLsn) - if err != nil { - return fmt.Errorf("unable to parse query output %v", err) - } - - ylogger.Zero.Debug().Str("x_path", row.x_path).Str("lsn", row.expireLsn).Msg("added file to expire hint") - (*expireIndex)[row.x_path] = lsn + return fmt.Errorf("unable to parse query output %v", err) } - ylogger.Zero.Debug().Msg("fetched expire hint info") + + ylogger.Zero.Debug().Str("x_path", row.x_path).Str("lsn", row.expireLsn).Msg("added file to expire hint") + (*expireIndex)[row.x_path] = lsn } + ylogger.Zero.Debug().Msg("fetched expire hint info") var viRows *pgx.Rows if usePrefix { diff --git a/pkg/database/database_test.go b/pkg/database/database_test.go deleted file mode 100644 index 712976b7..00000000 --- a/pkg/database/database_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package database - -import "testing" - -func TestVersionAtLeast(t *testing.T) { - tests := []struct { - version string - minimum string - want bool - wantErr bool - }{ - {"1.8.11", "1.8.4", true, false}, - {"1.8.10", "1.8.4", true, false}, - {"1.8.4", "1.8.4", true, false}, - {"1.8.3", "1.8.4", false, false}, - {"1.10.0", "1.8.4", true, false}, - {"2.0.0", "1.8.4", true, false}, - {"1.7.99", "1.8.4", false, false}, - {"1.8", "1.8.4", false, false}, - {"1.8", "1.8.0", true, false}, - {"1", "1.0.0", true, false}, - {"2", "1.8.4", true, false}, - {"1.0.0", "1", true, false}, - {"1.8.0.1", "1.8.4", false, true}, - {"", "1.8.4", false, true}, - {"1.8.", "1.8.4", false, true}, - {"1.8.11-rc1", "1.8.4", false, true}, - {"1..4", "1.8.4", false, true}, - {"1.8.18446744073709551616", "1.8.4", false, true}, - {"1.8.11", "invalid", false, true}, - } - for _, tt := range tests { - t.Run(tt.version+"_vs_"+tt.minimum, func(t *testing.T) { - got, err := versionAtLeast(tt.version, tt.minimum) - if (err != nil) != tt.wantErr || got != tt.want { - t.Fatalf("versionAtLeast(%q, %q) = %v, %v; want %v, error=%v", tt.version, tt.minimum, got, err, tt.want, tt.wantErr) - } - }) - } -}