Skip to content

Commit

Permalink
Enable minor schema version updates (#141)
Browse files Browse the repository at this point in the history
* enable minor schema version updates

* use schema version type

* in anticipation of future major minor schema v

* add unit test

* correct major minor

* comments

* correct schema version

* using newer schema versions
  • Loading branch information
louiseschmidtgen authored Jul 26, 2024
1 parent 8286d77 commit 6a3dd4a
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 16 deletions.
34 changes: 31 additions & 3 deletions pkg/kine/drivers/sqlite/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sqlite
import (
"context"
"database/sql"
"fmt"
)

// The database version that designates whether table migration
Expand All @@ -11,12 +12,39 @@ import (
// present anymore, and unexpired rows of the key_value table with
// the latest revisions must have been recorded in the Kine table
// already
const databaseSchemaVersion = 1

// applySchemaV1 moves the schema from version 0 to version 1,
type SchemaVersion int32

var (
databaseSchemaVersion = NewSchemaVersion(0, 1)
)

func NewSchemaVersion(major int16, minor int16) SchemaVersion {
return SchemaVersion(int32(major)<<16 | int32(minor))
}

func (sv SchemaVersion) Major() int16 {
// Extract the high 16 bits
return int16((int32(sv) >> 16))
}

func (sv SchemaVersion) Minor() int16 {
// Extract the lower 16 bits
return int16(sv)
}

func (sv SchemaVersion) CompatibleWith(targetSV SchemaVersion) error {
// Major version must be the same
if sv.Major() != targetSV.Major() {
return fmt.Errorf("can not migrate between different major versions")
}
return nil
}

// applySchemaV0_1 moves the schema from version 0 to version 1,
// taking into account the possible unversioned schema from
// upstream kine.
func applySchemaV1(ctx context.Context, txn *sql.Tx) error {
func applySchemaV0_1(ctx context.Context, txn *sql.Tx) error {
if kineTableExists, err := hasTable(ctx, txn, "kine"); err != nil {
return err
} else if !kineTableExists {
Expand Down
42 changes: 42 additions & 0 deletions pkg/kine/drivers/sqlite/schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package sqlite_test

import (
"fmt"
"testing"

"github.com/canonical/k8s-dqlite/pkg/kine/drivers/sqlite"
)

func TestCanMigrate(t *testing.T) {
tests := []struct {
name string
current sqlite.SchemaVersion
target sqlite.SchemaVersion
canMigrate bool
expectedErr error
}{
{
name: "can not migrate between different major versions",
current: sqlite.NewSchemaVersion(0, 0),
target: sqlite.NewSchemaVersion(1, 0),
expectedErr: fmt.Errorf("can not migrate between different major versions"),
},
{
name: "can migrate",
current: sqlite.NewSchemaVersion(0, 0),
target: sqlite.NewSchemaVersion(0, 1),
expectedErr: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.current.CompatibleWith(tt.target)
if err != nil && tt.expectedErr == nil {
if err.Error() != tt.expectedErr.Error() {
t.Errorf("expected error %v, got %v", tt.expectedErr, err)
}
}
})
}
}
32 changes: 19 additions & 13 deletions pkg/kine/drivers/sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,17 @@ func NewVariant(ctx context.Context, driverName, dataSourceName string) (server.
// changes are rolled back if an error occurs.
func setup(ctx context.Context, db *sql.DB) error {
// Optimistically ask for the user_version without starting a transaction
var schemaVersion int
var currentSchemaVersion SchemaVersion

row := db.QueryRowContext(ctx, `PRAGMA user_version`)
if err := row.Scan(&schemaVersion); err != nil {
if err := row.Scan(&currentSchemaVersion); err != nil {
return err
}

if schemaVersion == databaseSchemaVersion {
if err := currentSchemaVersion.CompatibleWith(databaseSchemaVersion); err != nil {
return err
}
if currentSchemaVersion >= databaseSchemaVersion {
return nil
}

Expand All @@ -148,24 +151,27 @@ func setup(ctx context.Context, db *sql.DB) error {
// migrate tries to migrate from a version of the database
// to the target one.
func migrate(ctx context.Context, txn *sql.Tx) error {
var userVersion int
var currentSchemaVersion SchemaVersion

row := txn.QueryRowContext(ctx, `PRAGMA user_version`)
if err := row.Scan(&userVersion); err != nil {
if err := row.Scan(&currentSchemaVersion); err != nil {
return err
}

if err := currentSchemaVersion.CompatibleWith(databaseSchemaVersion); err != nil {
return err
}
if currentSchemaVersion >= databaseSchemaVersion {
return nil
}

switch userVersion {
case 0:
if err := applySchemaV1(ctx, txn); err != nil {
switch currentSchemaVersion {
case NewSchemaVersion(0, 0):
if err := applySchemaV0_1(ctx, txn); err != nil {
return err
}
fallthrough
case databaseSchemaVersion:
break
default:
// FIXME this needs better handling
return errors.Errorf("unsupported version: %d", userVersion)
return nil
}

setUserVersionSQL := fmt.Sprintf(`PRAGMA user_version = %d`, databaseSchemaVersion)
Expand Down

0 comments on commit 6a3dd4a

Please sign in to comment.