mirror of
https://github.com/etcd-io/bbolt.git
synced 2025-05-31 11:42:30 +00:00
Decompose command options
Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
This commit is contained in:
parent
179e0203f2
commit
f483d799ae
@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
@ -17,19 +18,6 @@ var (
|
||||
ErrSurgeryFreelistAlreadyExist = errors.New("the file already has freelist, please consider to abandon the freelist to forcibly rebuild it")
|
||||
)
|
||||
|
||||
type surgeryOptions struct {
|
||||
surgeryTargetDBFilePath string
|
||||
surgeryPageId uint64
|
||||
surgeryStartElementIdx int
|
||||
surgeryEndElementIdx int
|
||||
surgerySourcePageId uint64
|
||||
surgeryDestinationPageId uint64
|
||||
}
|
||||
|
||||
func defaultSurgeryOptions() surgeryOptions {
|
||||
return surgeryOptions{}
|
||||
}
|
||||
|
||||
func newSurgeryCobraCommand() *cobra.Command {
|
||||
surgeryCmd := &cobra.Command{
|
||||
Use: "surgery <subcommand>",
|
||||
@ -44,8 +32,23 @@ func newSurgeryCobraCommand() *cobra.Command {
|
||||
return surgeryCmd
|
||||
}
|
||||
|
||||
type surgeryBaseOptions struct {
|
||||
outputDBFilePath string
|
||||
}
|
||||
|
||||
func (o *surgeryBaseOptions) AddFlags(fs *pflag.FlagSet) {
|
||||
fs.StringVar(&o.outputDBFilePath, "output", o.outputDBFilePath, "path to the filePath db file")
|
||||
}
|
||||
|
||||
func (o *surgeryBaseOptions) Validate() error {
|
||||
if o.outputDBFilePath == "" {
|
||||
return fmt.Errorf("output database path wasn't given, specify output database file path with --output option")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSurgeryRevertMetaPageCommand() *cobra.Command {
|
||||
cfg := defaultSurgeryOptions()
|
||||
var o surgeryBaseOptions
|
||||
revertMetaPageCmd := &cobra.Command{
|
||||
Use: "revert-meta-page <bbolt-file> [options]",
|
||||
Short: "Revert the meta page to revert the changes performed by the latest transaction",
|
||||
@ -59,25 +62,26 @@ func newSurgeryRevertMetaPageCommand() *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return surgeryRevertMetaPageFunc(args[0], cfg)
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return surgeryRevertMetaPageFunc(args[0], o)
|
||||
},
|
||||
}
|
||||
|
||||
revertMetaPageCmd.Flags().StringVar(&cfg.surgeryTargetDBFilePath, "output", "", "path to the target db file")
|
||||
|
||||
o.AddFlags(revertMetaPageCmd.Flags())
|
||||
return revertMetaPageCmd
|
||||
}
|
||||
|
||||
func surgeryRevertMetaPageFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
if err := checkDBPaths(srcDBPath, cfg.surgeryTargetDBFilePath); err != nil {
|
||||
func surgeryRevertMetaPageFunc(srcDBPath string, cfg surgeryBaseOptions) error {
|
||||
if _, err := checkSourceDBPath(srcDBPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := common.CopyFile(srcDBPath, cfg.surgeryTargetDBFilePath); err != nil {
|
||||
if err := common.CopyFile(srcDBPath, cfg.outputDBFilePath); err != nil {
|
||||
return fmt.Errorf("[revert-meta-page] copy file failed: %w", err)
|
||||
}
|
||||
|
||||
if err := surgeon.RevertMetaPage(cfg.surgeryTargetDBFilePath); err != nil {
|
||||
if err := surgeon.RevertMetaPage(cfg.outputDBFilePath); err != nil {
|
||||
return fmt.Errorf("revert-meta-page command failed: %w", err)
|
||||
}
|
||||
|
||||
@ -86,8 +90,30 @@ func surgeryRevertMetaPageFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type surgeryCopyPageOptions struct {
|
||||
surgeryBaseOptions
|
||||
sourcePageId uint64
|
||||
destinationPageId uint64
|
||||
}
|
||||
|
||||
func (o *surgeryCopyPageOptions) AddFlags(fs *pflag.FlagSet) {
|
||||
o.surgeryBaseOptions.AddFlags(fs)
|
||||
fs.Uint64VarP(&o.sourcePageId, "from-page", "", o.sourcePageId, "source page Id")
|
||||
fs.Uint64VarP(&o.destinationPageId, "to-page", "", o.destinationPageId, "destination page Id")
|
||||
}
|
||||
|
||||
func (o *surgeryCopyPageOptions) Validate() error {
|
||||
if err := o.surgeryBaseOptions.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.sourcePageId == o.destinationPageId {
|
||||
return fmt.Errorf("'--from-page' and '--to-page' have the same value: %d", o.sourcePageId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSurgeryCopyPageCommand() *cobra.Command {
|
||||
cfg := defaultSurgeryOptions()
|
||||
var o surgeryCopyPageOptions
|
||||
copyPageCmd := &cobra.Command{
|
||||
Use: "copy-page <bbolt-file> [options]",
|
||||
Short: "Copy page from the source page Id to the destination page Id",
|
||||
@ -101,27 +127,22 @@ func newSurgeryCopyPageCommand() *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return surgeryCopyPageFunc(args[0], cfg)
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return surgeryCopyPageFunc(args[0], o)
|
||||
},
|
||||
}
|
||||
|
||||
copyPageCmd.Flags().StringVar(&cfg.surgeryTargetDBFilePath, "output", "", "path to the target db file")
|
||||
copyPageCmd.Flags().Uint64VarP(&cfg.surgerySourcePageId, "from-page", "", 0, "source page Id")
|
||||
copyPageCmd.Flags().Uint64VarP(&cfg.surgeryDestinationPageId, "to-page", "", 0, "destination page Id")
|
||||
|
||||
o.AddFlags(copyPageCmd.Flags())
|
||||
return copyPageCmd
|
||||
}
|
||||
|
||||
func surgeryCopyPageFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
if cfg.surgerySourcePageId == cfg.surgeryDestinationPageId {
|
||||
return fmt.Errorf("'--from-page' and '--to-page' have the same value: %d", cfg.surgerySourcePageId)
|
||||
}
|
||||
|
||||
if err := common.CopyFile(srcDBPath, cfg.surgeryTargetDBFilePath); err != nil {
|
||||
func surgeryCopyPageFunc(srcDBPath string, cfg surgeryCopyPageOptions) error {
|
||||
if err := common.CopyFile(srcDBPath, cfg.outputDBFilePath); err != nil {
|
||||
return fmt.Errorf("[copy-page] copy file failed: %w", err)
|
||||
}
|
||||
|
||||
if err := surgeon.CopyPage(cfg.surgeryTargetDBFilePath, common.Pgid(cfg.surgerySourcePageId), common.Pgid(cfg.surgeryDestinationPageId)); err != nil {
|
||||
if err := surgeon.CopyPage(cfg.outputDBFilePath, common.Pgid(cfg.sourcePageId), common.Pgid(cfg.destinationPageId)); err != nil {
|
||||
return fmt.Errorf("copy-page command failed: %w", err)
|
||||
}
|
||||
|
||||
@ -134,12 +155,36 @@ func surgeryCopyPageFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
fmt.Fprintf(os.Stdout, "Please consider executing `./bbolt surgery abandon-freelist ...`\n")
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "The page %d was successfully copied to page %d\n", cfg.surgerySourcePageId, cfg.surgeryDestinationPageId)
|
||||
fmt.Fprintf(os.Stdout, "The page %d was successfully copied to page %d\n", cfg.sourcePageId, cfg.destinationPageId)
|
||||
return nil
|
||||
}
|
||||
|
||||
type surgeryClearPageElementsOptions struct {
|
||||
surgeryBaseOptions
|
||||
pageId uint64
|
||||
startElementIdx int
|
||||
endElementIdx int
|
||||
}
|
||||
|
||||
func (o *surgeryClearPageElementsOptions) AddFlags(fs *pflag.FlagSet) {
|
||||
o.surgeryBaseOptions.AddFlags(fs)
|
||||
fs.Uint64VarP(&o.pageId, "pageId", "", o.pageId, "page id")
|
||||
fs.IntVarP(&o.startElementIdx, "from-index", "", o.startElementIdx, "start element index (included) to clear, starting from 0")
|
||||
fs.IntVarP(&o.endElementIdx, "to-index", "", o.endElementIdx, "end element index (excluded) to clear, starting from 0, -1 means to the end of page")
|
||||
}
|
||||
|
||||
func (o *surgeryClearPageElementsOptions) 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 newSurgeryClearPageElementsCommand() *cobra.Command {
|
||||
cfg := defaultSurgeryOptions()
|
||||
var o surgeryClearPageElementsOptions
|
||||
clearElementCmd := &cobra.Command{
|
||||
Use: "clear-page-elements <bbolt-file> [options]",
|
||||
Short: "Clears elements from the given page, which can be a branch or leaf page",
|
||||
@ -153,28 +198,22 @@ func newSurgeryClearPageElementsCommand() *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return surgeryClearPageElementFunc(args[0], cfg)
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return surgeryClearPageElementFunc(args[0], o)
|
||||
},
|
||||
}
|
||||
|
||||
clearElementCmd.Flags().StringVar(&cfg.surgeryTargetDBFilePath, "output", "", "path to the target db file")
|
||||
clearElementCmd.Flags().Uint64VarP(&cfg.surgeryPageId, "pageId", "", 0, "page id")
|
||||
clearElementCmd.Flags().IntVarP(&cfg.surgeryStartElementIdx, "from-index", "", 0, "start element index (included) to clear, starting from 0")
|
||||
clearElementCmd.Flags().IntVarP(&cfg.surgeryEndElementIdx, "to-index", "", 0, "end element index (excluded) to clear, starting from 0, -1 means to the end of page")
|
||||
|
||||
o.AddFlags(clearElementCmd.Flags())
|
||||
return clearElementCmd
|
||||
}
|
||||
|
||||
func surgeryClearPageElementFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
if err := common.CopyFile(srcDBPath, cfg.surgeryTargetDBFilePath); err != nil {
|
||||
func surgeryClearPageElementFunc(srcDBPath string, cfg surgeryClearPageElementsOptions) error {
|
||||
if err := common.CopyFile(srcDBPath, cfg.outputDBFilePath); err != nil {
|
||||
return fmt.Errorf("[clear-page-element] copy file failed: %w", err)
|
||||
}
|
||||
|
||||
if cfg.surgeryPageId < 2 {
|
||||
return fmt.Errorf("the pageId must be at least 2, but got %d", cfg.surgeryPageId)
|
||||
}
|
||||
|
||||
needAbandonFreelist, err := surgeon.ClearPageElements(cfg.surgeryTargetDBFilePath, common.Pgid(cfg.surgeryPageId), cfg.surgeryStartElementIdx, cfg.surgeryEndElementIdx, false)
|
||||
needAbandonFreelist, err := surgeon.ClearPageElements(cfg.outputDBFilePath, common.Pgid(cfg.pageId), cfg.startElementIdx, cfg.endElementIdx, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("clear-page-element command failed: %w", err)
|
||||
}
|
||||
@ -184,7 +223,7 @@ func surgeryClearPageElementFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
fmt.Fprintf(os.Stdout, "Please consider executing `./bbolt surgery abandon-freelist ...`\n")
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "All elements in [%d, %d) in page %d were cleared\n", cfg.surgeryStartElementIdx, cfg.surgeryEndElementIdx, cfg.surgeryPageId)
|
||||
fmt.Fprintf(os.Stdout, "All elements in [%d, %d) in page %d were cleared\n", cfg.startElementIdx, cfg.endElementIdx, cfg.pageId)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -204,7 +243,7 @@ func newSurgeryFreelistCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
func newSurgeryFreelistAbandonCommand() *cobra.Command {
|
||||
cfg := defaultSurgeryOptions()
|
||||
var o surgeryBaseOptions
|
||||
abandonFreelistCmd := &cobra.Command{
|
||||
Use: "abandon <bbolt-file> [options]",
|
||||
Short: "Abandon the freelist from both meta pages",
|
||||
@ -218,21 +257,23 @@ func newSurgeryFreelistAbandonCommand() *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return surgeryFreelistAbandonFunc(args[0], cfg)
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return surgeryFreelistAbandonFunc(args[0], o)
|
||||
},
|
||||
}
|
||||
|
||||
abandonFreelistCmd.Flags().StringVar(&cfg.surgeryTargetDBFilePath, "output", "", "path to the target db file")
|
||||
o.AddFlags(abandonFreelistCmd.Flags())
|
||||
|
||||
return abandonFreelistCmd
|
||||
}
|
||||
|
||||
func surgeryFreelistAbandonFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
if err := common.CopyFile(srcDBPath, cfg.surgeryTargetDBFilePath); err != nil {
|
||||
func surgeryFreelistAbandonFunc(srcDBPath string, cfg surgeryBaseOptions) error {
|
||||
if err := common.CopyFile(srcDBPath, cfg.outputDBFilePath); err != nil {
|
||||
return fmt.Errorf("[freelist abandon] copy file failed: %w", err)
|
||||
}
|
||||
|
||||
if err := surgeon.ClearFreelist(cfg.surgeryTargetDBFilePath); err != nil {
|
||||
if err := surgeon.ClearFreelist(cfg.outputDBFilePath); err != nil {
|
||||
return fmt.Errorf("abandom-freelist command failed: %w", err)
|
||||
}
|
||||
|
||||
@ -241,7 +282,7 @@ func surgeryFreelistAbandonFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
}
|
||||
|
||||
func newSurgeryFreelistRebuildCommand() *cobra.Command {
|
||||
cfg := defaultSurgeryOptions()
|
||||
var o surgeryBaseOptions
|
||||
rebuildFreelistCmd := &cobra.Command{
|
||||
Use: "rebuild <bbolt-file> [options]",
|
||||
Short: "Rebuild the freelist",
|
||||
@ -255,26 +296,22 @@ func newSurgeryFreelistRebuildCommand() *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return surgeryFreelistRebuildFunc(args[0], cfg)
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return surgeryFreelistRebuildFunc(args[0], o)
|
||||
},
|
||||
}
|
||||
|
||||
rebuildFreelistCmd.Flags().StringVar(&cfg.surgeryTargetDBFilePath, "output", "", "path to the target db file")
|
||||
o.AddFlags(rebuildFreelistCmd.Flags())
|
||||
|
||||
return rebuildFreelistCmd
|
||||
}
|
||||
|
||||
func surgeryFreelistRebuildFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
func surgeryFreelistRebuildFunc(srcDBPath string, cfg surgeryBaseOptions) error {
|
||||
// Ensure source file exists.
|
||||
fi, err := os.Stat(srcDBPath)
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("source database file %q doesn't exist", srcDBPath)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to open source database file %q: %v", srcDBPath, err)
|
||||
}
|
||||
|
||||
if cfg.surgeryTargetDBFilePath == "" {
|
||||
return fmt.Errorf("output database path wasn't given, specify output database file path with --output option")
|
||||
fi, err := checkSourceDBPath(srcDBPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure the freelist isn't present in the file.
|
||||
@ -286,12 +323,12 @@ func surgeryFreelistRebuildFunc(srcDBPath string, cfg surgeryOptions) error {
|
||||
return ErrSurgeryFreelistAlreadyExist
|
||||
}
|
||||
|
||||
if err := common.CopyFile(srcDBPath, cfg.surgeryTargetDBFilePath); err != nil {
|
||||
if err := common.CopyFile(srcDBPath, cfg.outputDBFilePath); err != nil {
|
||||
return fmt.Errorf("[freelist rebuild] copy file failed: %w", err)
|
||||
}
|
||||
|
||||
// bboltDB automatically reconstruct & sync freelist in write mode.
|
||||
db, err := bolt.Open(cfg.surgeryTargetDBFilePath, fi.Mode(), &bolt.Options{NoFreelistSync: false})
|
||||
db, err := bolt.Open(cfg.outputDBFilePath, fi.Mode(), &bolt.Options{NoFreelistSync: false})
|
||||
if err != nil {
|
||||
return fmt.Errorf("[freelist rebuild] open db file failed: %w", err)
|
||||
}
|
||||
@ -316,17 +353,12 @@ func readMetaPage(path string) (*common.Meta, error) {
|
||||
return common.LoadPageMeta(buf), nil
|
||||
}
|
||||
|
||||
func checkDBPaths(srcPath, dstPath string) error {
|
||||
_, err := os.Stat(srcPath)
|
||||
func checkSourceDBPath(srcPath string) (os.FileInfo, error) {
|
||||
fi, err := os.Stat(srcPath)
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("source database file %q doesn't exist", srcPath)
|
||||
return nil, fmt.Errorf("source database file %q doesn't exist", srcPath)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to open source database file %q: %v", srcPath, err)
|
||||
return nil, fmt.Errorf("failed to open source database file %q: %v", srcPath, err)
|
||||
}
|
||||
|
||||
if dstPath == "" {
|
||||
return fmt.Errorf("output database path wasn't given, specify output database file path with --output option")
|
||||
}
|
||||
|
||||
return nil
|
||||
return fi, nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user