Skip to content

Commit

Permalink
root: simplify database migration
Browse files Browse the repository at this point in the history
We had 2 schema states + migration code between the two. Simplify this
to just 2 state, and in the future just migrations have to be added.

This is in preparation of adding created & modified columns for
passwords, where migration will be needed, which is now much simpler.
  • Loading branch information
vmiklos committed Feb 7, 2024
1 parent 6589050 commit a8577cb
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 110 deletions.
2 changes: 2 additions & 0 deletions commands/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func newPullCommand(ctx *Context) *cobra.Command {
}

ctx.NoWriteBack = true
// Prefer the pull result over the migration result.
ctx.DatabaseMigrated = false
return nil
},
}
Expand Down
91 changes: 17 additions & 74 deletions commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,79 +128,29 @@ func openDatabase(ctx *Context) error {
if err != nil {
return fmt.Errorf("getDatabasePath() failed: %s", err)
}
createNew := true
if pathExists(ctx.PermanentPath) {
Remove(ctx.TempFile.Name())
command := Command("gpg", "--decrypt", "-a", "-o", ctx.TempFile.Name(), ctx.PermanentPath)
err = command.Run()
if err != nil {
return fmt.Errorf("Command() failed: %s", err)
}
createNew = false
}

ctx.Database, err = sql.Open("sqlite3", ctx.TempFile.Name())
if err != nil {
return fmt.Errorf("sql.Open() failed: %s", err)
}

err = initDatabase(ctx, createNew)
err = initDatabase(ctx)
if err != nil {
return fmt.Errorf("initDatabase() failed: %s", err)
}

return nil
}

func initDatabaseWithVersion(ctx *Context, version int) error {
var statement string
if version == 0 {
statement = `create table passwords (
machine text not null,
service text not null,
user text not null,
password text not null,
type text not null,
unique(machine, service, user, type)
)`
} else {
statement = `create table passwords (
id integer primary key autoincrement,
machine text not null,
service text not null,
user text not null,
password text not null,
type text not null,
unique(machine, service, user, type)
)`
}
query, err := ctx.Database.Prepare(statement)
if err != nil {
return fmt.Errorf("db.Prepare() failed: %s", err)
}
_, err = query.Exec()
if err != nil {
return fmt.Errorf("db.Exec() failed: %s", err)
}

return nil
}

func initDatabase(ctx *Context, createNew bool) error {
// We need createNew because both an empty db and the first schema was user_version == 0.
if createNew {
initDatabaseWithVersion(ctx, 1)

query, err := ctx.Database.Prepare("pragma user_version = 1")
if err != nil {
return fmt.Errorf("db.Prepare() failed: %s", err)
}
_, err = query.Exec()
if err != nil {
return fmt.Errorf("db.Exec() failed: %s", err)
}
}

func initDatabase(ctx *Context) error {
var version int
rows, err := ctx.Database.Query("pragma user_version")
if err != nil {
Expand All @@ -215,31 +165,24 @@ func initDatabase(ctx *Context, createNew bool) error {
}

if version < 1 {
statements := []string{`create table passwords_copy(
id integer primary key autoincrement,
machine text not null,
service text not null,
user text not null,
password text not null,
type text not null,
unique(machine, service, user, type)
)`,
"insert into passwords_copy(machine, service, user, password, type) select machine, service, user, password, type from passwords",
"drop table passwords",
"alter table passwords_copy rename to passwords",
query, err := ctx.Database.Prepare(`create table passwords (
id integer primary key autoincrement,
machine text not null,
service text not null,
user text not null,
password text not null,
type text not null,
unique(machine, service, user, type)
);`)
if err != nil {
return fmt.Errorf("db.Prepare() failed: %s", err)
}
for _, statement := range statements {
query, err := ctx.Database.Prepare(statement)
if err != nil {
return fmt.Errorf("db.Prepare() failed: %s", err)
}
_, err = query.Exec()
if err != nil {
return fmt.Errorf("db.Exec() failed: %s", err)
}
_, err = query.Exec()
if err != nil {
return fmt.Errorf("db.Exec() failed: %s", err)
}

query, err := ctx.Database.Prepare("pragma user_version = 1")
query, err = ctx.Database.Prepare("pragma user_version = 1")
if err != nil {
return fmt.Errorf("db.Prepare() failed: %s", err)
}
Expand Down
37 changes: 1 addition & 36 deletions commands/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func CreateContextForTesting(t *testing.T) Context {
t.Cleanup(func() { GenerateTotpCode = oldGenerateTotpCode })

ctx := Context{Database: db}
err = initDatabase(&ctx, true)
err = initDatabase(&ctx)
if err != nil {
t.Fatalf("initDatabase() = %q, want nil", err)
}
Expand Down Expand Up @@ -182,38 +182,3 @@ func TestGetDatabasePath(t *testing.T) {
t.Fatalf("getDatabasePath() = %q, want %q", actual, expected)
}
}

func TestMigration(t *testing.T) {
// Create an in-memory database.
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("sql.Open() failed: %s", err)
}
ctx := Context{Database: db}
err = initDatabaseWithVersion(&ctx, 0)
if err != nil {
t.Fatalf("initDatabaseWithVersion() failed: %s", err)
}

err = initDatabase(&ctx, false)
if err != nil {
t.Fatalf("initDatabase() failed: %s", err)
}

var actual int
rows, err := db.Query("pragma user_version")
if err != nil {
t.Fatalf("Query() failed: %s", err)
}
defer rows.Close()
for rows.Next() {
err = rows.Scan(&actual)
if err != nil {
t.Fatalf("rows.Scan() failed: %s", err)
}
}
expected := 1
if actual != expected {
t.Fatalf("initDatabase() = %q, want %q", actual, expected)
}
}
8 changes: 8 additions & 0 deletions guide/src/hacking.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ Changes to the shell completion can be tested, without restarting the shell usin
```console
source <(cpm completion bash)
```

## Go debugging

To run a single test:

```console
go test -run=TestInsert ./...
```
4 changes: 4 additions & 0 deletions guide/src/news.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## main

- drop compatibility with cpm < 7.5: update from old versions to 24.2 first

## 24.2

- create / update: new `-y` switch to generate more secure passwords (3 instead of 0 numeric
Expand Down

0 comments on commit a8577cb

Please sign in to comment.