Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: migrate 'surgery clear-page' command to cobra style command #484

Merged
merged 3 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func newSurgeryCobraCommand() *cobra.Command {

surgeryCmd.AddCommand(newSurgeryRevertMetaPageCommand())
surgeryCmd.AddCommand(newSurgeryCopyPageCommand())
surgeryCmd.AddCommand(newSurgeryClearPageCommand())
surgeryCmd.AddCommand(newSurgeryClearPageElementsCommand())
surgeryCmd.AddCommand(newSurgeryFreelistCommand())

Expand Down Expand Up @@ -138,6 +139,10 @@ func newSurgeryCopyPageCommand() *cobra.Command {
}

func surgeryCopyPageFunc(srcDBPath string, cfg surgeryCopyPageOptions) error {
if _, err := checkSourceDBPath(srcDBPath); err != nil {
return err
}

if err := common.CopyFile(srcDBPath, cfg.outputDBFilePath); err != nil {
return fmt.Errorf("[copy-page] copy file failed: %w", err)
}
Expand All @@ -159,6 +164,74 @@ func surgeryCopyPageFunc(srcDBPath string, cfg surgeryCopyPageOptions) error {
return nil
}

type surgeryClearPageOptions struct {
surgeryBaseOptions
pageId uint64
}

func (o *surgeryClearPageOptions) AddFlags(fs *pflag.FlagSet) {
o.surgeryBaseOptions.AddFlags(fs)
fs.Uint64VarP(&o.pageId, "pageId", "", o.pageId, "page Id")
}

func (o *surgeryClearPageOptions) Validate() error {
if err := o.surgeryBaseOptions.Validate(); err != nil {
return err
}
if o.pageId < 2 {
return fmt.Errorf("the pageId must be at least 2, but got %d", o.pageId)
}
return nil
}

func newSurgeryClearPageCommand() *cobra.Command {
var o surgeryClearPageOptions
clearPageCmd := &cobra.Command{
Use: "clear-page <bbolt-file> [options]",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shell we mention --pageId as mandatory option here ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. We can use cobra.MarkFlagRequired to mark the field required. We should apply the same restriction for other surgery command as well.

Let me do it as a followup. thx

Short: "Clears all elements from the given page, which can be a branch or leaf page",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("db file path not provided")
}
if len(args) > 1 {
return errors.New("too many arguments")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := o.Validate(); err != nil {
return err
}
return surgeryClearPageFunc(args[0], o)
},
}
o.AddFlags(clearPageCmd.Flags())
return clearPageCmd
}

func surgeryClearPageFunc(srcDBPath string, cfg surgeryClearPageOptions) error {
if _, err := checkSourceDBPath(srcDBPath); err != nil {
return err
}

if err := common.CopyFile(srcDBPath, cfg.outputDBFilePath); err != nil {
return fmt.Errorf("[clear-page] copy file failed: %w", err)
}

needAbandonFreelist, err := surgeon.ClearPage(cfg.outputDBFilePath, common.Pgid(cfg.pageId))
if err != nil {
return fmt.Errorf("clear-page command failed: %w", err)
}

if needAbandonFreelist {
fmt.Fprintf(os.Stdout, "WARNING: The clearing has abandoned some pages that are not yet referenced from free list.\n")
fmt.Fprintf(os.Stdout, "Please consider executing `./bbolt surgery abandon-freelist ...`\n")
}

fmt.Fprintf(os.Stdout, "The page (%d) was cleared\n", cfg.pageId)
return nil
}

type surgeryClearPageElementsOptions struct {
surgeryBaseOptions
pageId uint64
Expand Down Expand Up @@ -209,6 +282,10 @@ func newSurgeryClearPageElementsCommand() *cobra.Command {
}

func surgeryClearPageElementFunc(srcDBPath string, cfg surgeryClearPageElementsOptions) error {
if _, err := checkSourceDBPath(srcDBPath); err != nil {
return err
}

if err := common.CopyFile(srcDBPath, cfg.outputDBFilePath); err != nil {
return fmt.Errorf("[clear-page-element] copy file failed: %w", err)
}
Expand All @@ -227,9 +304,6 @@ func surgeryClearPageElementFunc(srcDBPath string, cfg surgeryClearPageElementsO
return nil
}

// TODO(ahrtr): add `bbolt surgery freelist rebuild/check ...` commands,
// and move all `surgery freelist` commands into a separate file,
// e.g command_surgery_freelist.go.
func newSurgeryFreelistCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "freelist <subcommand>",
Expand Down Expand Up @@ -269,6 +343,10 @@ func newSurgeryFreelistAbandonCommand() *cobra.Command {
}

func surgeryFreelistAbandonFunc(srcDBPath string, cfg surgeryBaseOptions) error {
if _, err := checkSourceDBPath(srcDBPath); err != nil {
return err
}

if err := common.CopyFile(srcDBPath, cfg.outputDBFilePath); err != nil {
return fmt.Errorf("[freelist abandon] copy file failed: %w", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,43 @@ func TestSurgery_CopyPage(t *testing.T) {
assert.Equal(t, pageDataWithoutPageId(srcPageId3Data), pageDataWithoutPageId(dstPageId2Data))
}

// TODO(ahrtr): add test case below for `surgery clear-page` command:
// 1. The page is a branch page. All its children should become free pages.
func TestSurgery_ClearPage(t *testing.T) {
pageSize := 4096
db := btesting.MustCreateDBWithOption(t, &bolt.Options{PageSize: pageSize})
srcPath := db.Path()

// Insert some sample data
t.Log("Insert some sample data")
err := db.Fill([]byte("data"), 1, 20,
func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
func(tx int, k int) []byte { return make([]byte, 10) },
)
require.NoError(t, err)

defer requireDBNoChange(t, dbData(t, srcPath), srcPath)

// clear page 3
t.Log("clear page 3")
rootCmd := main.NewRootCommand()
output := filepath.Join(t.TempDir(), "dstdb")
rootCmd.SetArgs([]string{
"surgery", "clear-page", srcPath,
"--output", output,
"--pageId", "3",
})
err = rootCmd.Execute()
require.NoError(t, err)

t.Log("Verify result")
dstPageId3Data := readPage(t, output, 3, pageSize)

p := common.LoadPage(dstPageId3Data)
assert.Equal(t, uint16(0), p.Count())
assert.Equal(t, uint32(0), p.Overflow())
}

func TestSurgery_ClearPageElements_Without_Overflow(t *testing.T) {
testCases := []struct {
name string
Expand Down
2 changes: 0 additions & 2 deletions cmd/bbolt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ func (m *Main) Run(args ...string) error {
return newPagesCommand(m).Run(args[1:]...)
case "stats":
return newStatsCommand(m).Run(args[1:]...)
case "surgery":
return newSurgeryCommand(m).Run(args[1:]...)
default:
return ErrUnknownCommand
}
Expand Down
146 changes: 0 additions & 146 deletions cmd/bbolt/surgery_commands.go

This file was deleted.

47 changes: 0 additions & 47 deletions cmd/bbolt/surgery_commands_test.go

This file was deleted.