diff --git a/cmd/bolt.go b/cmd/bolt.go new file mode 100644 index 000000000..2bfedae79 --- /dev/null +++ b/cmd/bolt.go @@ -0,0 +1,49 @@ +// Copyright 2022 Molecula Corp. (DBA FeatureBase). +// SPDX-License-Identifier: Apache-2.0 +package cmd + +import ( + "fmt" + + "github.com/featurebasedb/featurebase/v3/ctl" + "github.com/featurebasedb/featurebase/v3/logger" + "github.com/spf13/cobra" +) + +func newBoltCommand(logdest logger.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "bolt", + Short: "Inspect bolt data files.", + Long: ` +Provides a set of commands for inspecting bolt data files. +`, + } + cmd.AddCommand(newBoltKeysCommand(logdest)) + return cmd +} + +func newBoltKeysCommand(logdest logger.Logger) *cobra.Command { + c := ctl.NewBoltKeysCommand(logdest) + cmd := &cobra.Command{ + Use: "keys [flags] PATH", + Short: "Get keys from bolt data file.", + Long: ` +"Get keys from bolt data file." +`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("data directory path required") + } else if len(args) > 1 { + return fmt.Errorf("too many command line arguments") + } + c.Path = args[0] + return nil + }, + RunE: UsageErrorWrapper(c), + } + + flags := cmd.Flags() + flags.BoolVar(&c.Hexa, "hexa", false, "Print hexadecimal rather than plain text") + + return cmd +} diff --git a/cmd/root.go b/cmd/root.go index 00ba0cba1..485689468 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -109,6 +109,7 @@ at https://docs.featurebase.com/. rc.AddCommand(newDataframeCsvLoaderCommand(logdest)) rc.AddCommand(newPreSortCommand(logdest)) rc.AddCommand(newParquetInfoCommand(logdest)) + rc.AddCommand(newBoltCommand(logdest)) rc.SetOutput(stderr) return rc diff --git a/ctl/bolt_keys.go b/ctl/bolt_keys.go new file mode 100644 index 000000000..4a3ddaae7 --- /dev/null +++ b/ctl/bolt_keys.go @@ -0,0 +1,93 @@ +// Copyright 2022 Molecula Corp. (DBA FeatureBase). +// SPDX-License-Identifier: Apache-2.0 +package ctl + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "time" + + "github.com/featurebasedb/featurebase/v3/logger" + bolt "go.etcd.io/bbolt" +) + +type BoltKeysCommand struct { + // Filepath to the bolt database. + Path string + + Hexa bool + + // Standard input/output + stdout io.Writer + logDest logger.Logger +} + +// NewRBFCheckCommand returns a new instance of RBFCheckCommand. +func NewBoltKeysCommand(logdest logger.Logger) *BoltKeysCommand { + return &BoltKeysCommand{ + stdout: os.Stdout, + logDest: logdest, + } +} + +// The pilosa package makes these vars unexportable +// Copying and pasting these from translate_boltdb.go +// It may be useful to check on these constants in that file +// if something isn't working as expected +var ( + // ErrTranslateStoreClosed is returned when reading from an TranslateEntryReader + // and the underlying store is closed. + ErrBoltTranslateStoreClosed = errors.New("boltdb: translate store closing") + + // ErrTranslateKeyNotFound is returned when translating key + // and the underlying store returns an empty set + ErrTranslateKeyNotFound = errors.New("boltdb: translating key returned empty set") + + bucketKeys = []byte("keys") + bucketIDs = []byte("ids") + bucketFree = []byte("free") + freeKey = []byte("free") +) + +// Run executes a consistency check of an RBF database. +func (cmd *BoltKeysCommand) Run(ctx context.Context) error { + + db, err := bolt.Open(cmd.Path, 0666, &bolt.Options{Timeout: 1 * time.Second}) + if err != nil { + return err + } + defer db.Close() + + err = db.View(func(tx *bolt.Tx) error { + // Assume bucket exists and has keys + + for _, bucket := range [][]byte{bucketKeys, bucketIDs, bucketFree} { + fmt.Fprintf(cmd.stdout, "Checking for keys in bucket named %s...\n", string(bucket)) + b := tx.Bucket(bucket) + if b == nil { + fmt.Fprintf(cmd.stdout, "Unable to find a bucket named %s...\n", string(bucket)) + continue + } + + c := b.Cursor() + + if cmd.Hexa { + for k, v := c.First(); k != nil; k, v = c.Next() { + fmt.Printf("key=%x, value=%x\n", k, v) + } + } else { + for k, v := c.First(); k != nil; k, v = c.Next() { + fmt.Printf("key=%s, value=%s\n", k, v) + } + } + + } + + return nil + }) + + return nil +}