Skip to content

Commit dd8447b

Browse files
committed
tool: add db upgrade command
Add a `db upgrade` command that ratches up the DB version to the newest one.
1 parent 5172b86 commit dd8447b

File tree

3 files changed

+202
-36
lines changed

3 files changed

+202
-36
lines changed

tool/data_test.go

+46-28
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/cockroachdb/datadriven"
1717
"github.com/cockroachdb/errors"
18+
"github.com/cockroachdb/pebble"
1819
"github.com/cockroachdb/pebble/internal/base"
1920
"github.com/cockroachdb/pebble/internal/testkeys"
2021
"github.com/cockroachdb/pebble/vfs"
@@ -49,34 +50,6 @@ func runTests(t *testing.T, path string) {
4950
fs := vfs.NewMem()
5051
t.Run(name, func(t *testing.T) {
5152
datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
52-
args := []string{d.Cmd}
53-
for _, arg := range d.CmdArgs {
54-
args = append(args, arg.String())
55-
}
56-
args = append(args, strings.Fields(d.Input)...)
57-
58-
// The testdata files contain paths with "/" path separators, but we
59-
// might be running on a system with a different path separator
60-
// (e.g. Windows). Copy the input data into a mem filesystem which
61-
// always uses "/" for the path separator.
62-
for i := range args {
63-
src := normalize(args[i])
64-
dest := vfs.Default.PathBase(src)
65-
if ok, err := vfs.Clone(vfs.Default, fs, src, dest); err != nil {
66-
return err.Error()
67-
} else if ok {
68-
args[i] = fs.PathBase(args[i])
69-
}
70-
}
71-
72-
var buf bytes.Buffer
73-
var secs int64
74-
timeNow = func() time.Time { secs++; return time.Unix(secs, 0) }
75-
76-
defer func() {
77-
timeNow = time.Now
78-
}()
79-
8053
// Register a test comparer and merger so that we can check the
8154
// behavior of tools when the comparer and merger do not match.
8255
comparer := func() *Comparer {
@@ -106,6 +79,51 @@ func runTests(t *testing.T, path string) {
10679
m.Name = "test-merger"
10780
return &m
10881
}()
82+
83+
if d.Cmd == "create" {
84+
dbDir := d.CmdArgs[0].String()
85+
opts := &pebble.Options{
86+
Comparer: comparer,
87+
Merger: merger,
88+
FS: fs,
89+
FormatMajorVersion: pebble.FormatMostCompatible,
90+
}
91+
db, err := pebble.Open(dbDir, opts)
92+
if err != nil {
93+
d.Fatalf(t, "%v", err)
94+
}
95+
db.Close()
96+
return ""
97+
}
98+
99+
args := []string{d.Cmd}
100+
for _, arg := range d.CmdArgs {
101+
args = append(args, arg.String())
102+
}
103+
args = append(args, strings.Fields(d.Input)...)
104+
105+
// The testdata files contain paths with "/" path separators, but we
106+
// might be running on a system with a different path separator
107+
// (e.g. Windows). Copy the input data into a mem filesystem which
108+
// always uses "/" for the path separator.
109+
for i := range args {
110+
src := normalize(args[i])
111+
dest := vfs.Default.PathBase(src)
112+
if ok, err := vfs.Clone(vfs.Default, fs, src, dest); err != nil {
113+
return err.Error()
114+
} else if ok {
115+
args[i] = fs.PathBase(args[i])
116+
}
117+
}
118+
119+
var buf bytes.Buffer
120+
var secs int64
121+
timeNow = func() time.Time { secs++; return time.Unix(secs, 0) }
122+
123+
defer func() {
124+
timeNow = time.Now
125+
}()
126+
109127
openErrEnhancer := func(err error) error {
110128
if errors.Is(err, base.ErrCorruption) {
111129
return base.CorruptionErrorf("%v\nCustom message in case of corruption error.", err)

tool/db.go

+97-8
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
package tool
66

77
import (
8+
"bufio"
89
"context"
910
"fmt"
1011
"io"
12+
"strings"
1113
"text/tabwriter"
1214

1315
"github.com/cockroachdb/errors"
@@ -29,6 +31,7 @@ import (
2931
type dbT struct {
3032
Root *cobra.Command
3133
Check *cobra.Command
34+
Upgrade *cobra.Command
3235
Checkpoint *cobra.Command
3336
Get *cobra.Command
3437
Logs *cobra.Command
@@ -58,6 +61,7 @@ type dbT struct {
5861
ioParallelism int
5962
ioSizes string
6063
verbose bool
64+
bypassPrompt bool
6165
}
6266

6367
func newDB(
@@ -93,6 +97,17 @@ database not be in use by another process.
9397
Args: cobra.ExactArgs(1),
9498
Run: d.runCheck,
9599
}
100+
d.Upgrade = &cobra.Command{
101+
Use: "upgrade <dir>",
102+
Short: "upgrade the DB internal format version",
103+
Long: `
104+
Upgrades the DB internal format version to the latest version.
105+
It is recommended to make a backup copy of the DB directory before upgrading.
106+
Requires that the specified database not be in use by another process.
107+
`,
108+
Args: cobra.ExactArgs(1),
109+
Run: d.runUpgrade,
110+
}
96111
d.Checkpoint = &cobra.Command{
97112
Use: "checkpoint <src-dir> <dest-dir>",
98113
Short: "create a checkpoint",
@@ -177,10 +192,10 @@ specified database.
177192
Run: d.runIOBench,
178193
}
179194

180-
d.Root.AddCommand(d.Check, d.Checkpoint, d.Get, d.Logs, d.LSM, d.Properties, d.Scan, d.Set, d.Space, d.IOBench)
195+
d.Root.AddCommand(d.Check, d.Upgrade, d.Checkpoint, d.Get, d.Logs, d.LSM, d.Properties, d.Scan, d.Set, d.Space, d.IOBench)
181196
d.Root.PersistentFlags().BoolVarP(&d.verbose, "verbose", "v", false, "verbose output")
182197

183-
for _, cmd := range []*cobra.Command{d.Check, d.Checkpoint, d.Get, d.LSM, d.Properties, d.Scan, d.Set, d.Space} {
198+
for _, cmd := range []*cobra.Command{d.Check, d.Upgrade, d.Checkpoint, d.Get, d.LSM, d.Properties, d.Scan, d.Set, d.Space} {
184199
cmd.Flags().StringVar(
185200
&d.comparerName, "comparer", "", "comparer name (use default if empty)")
186201
cmd.Flags().StringVar(
@@ -200,6 +215,10 @@ specified database.
200215
cmd.Flags().Var(
201216
&d.fmtValue, "value", "value formatter")
202217
}
218+
for _, cmd := range []*cobra.Command{d.Upgrade} {
219+
cmd.Flags().BoolVarP(
220+
&d.bypassPrompt, "yes", "y", false, "bypass prompt")
221+
}
203222

204223
d.Scan.Flags().Int64Var(
205224
&d.count, "count", 0, "key count for scan (0 is unlimited)")
@@ -350,13 +369,43 @@ func (d *dbT) runCheck(cmd *cobra.Command, args []string) {
350369
stats.NumPoints, makePlural("point", stats.NumPoints), stats.NumTombstones, makePlural("tombstone", int64(stats.NumTombstones)))
351370
}
352371

353-
type nonReadOnly struct{}
372+
func (d *dbT) runUpgrade(cmd *cobra.Command, args []string) {
373+
stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
374+
db, err := d.openDB(args[0], nonReadOnly{})
375+
if err != nil {
376+
fmt.Fprintf(stderr, "%s\n", err)
377+
return
378+
}
379+
defer d.closeDB(stderr, db)
354380

355-
func (n nonReadOnly) apply(opts *pebble.Options) {
356-
opts.ReadOnly = false
357-
// Increase the L0 compaction threshold to reduce the likelihood of an
358-
// unintended compaction changing test output.
359-
opts.L0CompactionThreshold = 10
381+
targetVersion := pebble.FormatNewest
382+
current := db.FormatMajorVersion()
383+
if current >= targetVersion {
384+
fmt.Fprintf(stdout, "DB is already at internal version %d.\n", current)
385+
return
386+
}
387+
fmt.Fprintf(stdout, "Upgrading DB from internal version %d to %d.\n", current, targetVersion)
388+
389+
prompt := `WARNING!!!
390+
This DB will not be usable with older versions of Pebble!
391+
392+
It is strongly recommended to back up the data before upgrading.
393+
`
394+
395+
if len(d.opts.BlockPropertyCollectors) == 0 {
396+
prompt += `
397+
If this DB uses custom block property collectors, the upgrade should be invoked
398+
through a custom binary that configures them. Otherwise, any new tables created
399+
during upgrade will not have the relevant block properties.
400+
`
401+
}
402+
if !d.promptForConfirmation(prompt, cmd.InOrStdin(), stdout, stderr) {
403+
return
404+
}
405+
if err := db.RatchetFormatMajorVersion(targetVersion); err != nil {
406+
fmt.Fprintf(stderr, "error: %s\n", err)
407+
}
408+
fmt.Fprintf(stdout, "Upgrade complete.\n")
360409
}
361410

362411
func (d *dbT) runCheckpoint(cmd *cobra.Command, args []string) {
@@ -665,6 +714,46 @@ func (d *dbT) runSet(cmd *cobra.Command, args []string) {
665714
}
666715
}
667716

717+
func (d *dbT) promptForConfirmation(prompt string, stdin io.Reader, stdout, stderr io.Writer) bool {
718+
if d.bypassPrompt {
719+
return true
720+
}
721+
if _, err := fmt.Fprintf(stdout, "%s\n", prompt); err != nil {
722+
fmt.Fprintf(stderr, "Error: %v\n", err)
723+
return false
724+
}
725+
reader := bufio.NewReader(stdin)
726+
for {
727+
if _, err := fmt.Fprintf(stdout, "Continue? [Y/N] "); err != nil {
728+
fmt.Fprintf(stderr, "Error: %v\n", err)
729+
return false
730+
}
731+
answer, err := reader.ReadString('\n')
732+
if err != nil {
733+
fmt.Fprintf(stderr, "Error: %v\n", err)
734+
return false
735+
}
736+
answer = strings.ToLower(strings.TrimSpace(answer))
737+
if answer == "y" || answer == "yes" {
738+
return true
739+
}
740+
741+
if answer == "n" || answer == "no" {
742+
_, _ = fmt.Fprintf(stderr, "Aborting\n")
743+
return false
744+
}
745+
}
746+
}
747+
748+
type nonReadOnly struct{}
749+
750+
func (n nonReadOnly) apply(opts *pebble.Options) {
751+
opts.ReadOnly = false
752+
// Increase the L0 compaction threshold to reduce the likelihood of an
753+
// unintended compaction changing test output.
754+
opts.L0CompactionThreshold = 10
755+
}
756+
668757
func propArgs(props []props, getProp func(*props) interface{}) []interface{} {
669758
args := make([]interface{}, 0, len(props))
670759
for _, p := range props {

tool/testdata/db_upgrade

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
create foo
2+
----
3+
4+
db set foo blue blue-val
5+
----
6+
7+
db set foo orange orange-val
8+
----
9+
10+
db set foo green green-val
11+
----
12+
13+
db set foo red red-val
14+
----
15+
16+
db set foo yellow yellow-val
17+
----
18+
19+
db get foo blue
20+
----
21+
[626c75652d76616c]
22+
23+
db get foo yellow
24+
----
25+
[79656c6c6f772d76616c]
26+
27+
db upgrade foo
28+
----
29+
----
30+
Upgrading DB from internal version 1 to 16.
31+
WARNING!!!
32+
This DB will not be usable with older versions of Pebble!
33+
34+
It is strongly recommended to back up the data before upgrading.
35+
36+
If this DB uses custom block property collectors, the upgrade should be invoked
37+
through a custom binary that configures them. Otherwise, any new tables created
38+
during upgrade will not have the relevant block properties.
39+
40+
Continue? [Y/N] Error: EOF
41+
----
42+
----
43+
44+
db upgrade foo --yes
45+
----
46+
Upgrading DB from internal version 1 to 16.
47+
Upgrade complete.
48+
49+
db get foo blue
50+
----
51+
[626c75652d76616c]
52+
53+
db get foo yellow
54+
----
55+
[79656c6c6f772d76616c]
56+
57+
db upgrade foo
58+
----
59+
DB is already at internal version 16.

0 commit comments

Comments
 (0)