feat(del): adds glob support for deletion
This commit is contained in:
parent
14897ba587
commit
badbf3b6bb
6 changed files with 145 additions and 19 deletions
122
cmd/del.go
122
cmd/del.go
|
|
@ -27,6 +27,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -47,21 +48,14 @@ func del(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetKeys := make([]string, 0, len(args))
|
||||
for _, arg := range args {
|
||||
exists, err := keyExists(store, arg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot remove '%s': %v", arg, err)
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("cannot remove '%s': No such key", arg)
|
||||
}
|
||||
targetKey, err := formatKeyForPrompt(store, arg)
|
||||
useGlob, err := cmd.Flags().GetBool("glob")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetKeys = append(targetKeys, targetKey)
|
||||
|
||||
targetKeys, deleteTargets, err := resolveDeleteTargets(store, args, useGlob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !force {
|
||||
|
|
@ -80,18 +74,17 @@ func del(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
arg := arg
|
||||
for _, target := range deleteTargets {
|
||||
trans := TransactionArgs{
|
||||
key: arg,
|
||||
key: target,
|
||||
readonly: false,
|
||||
sync: false,
|
||||
transact: func(tx *badger.Txn, k []byte) error {
|
||||
if err := tx.Delete(k); errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return fmt.Errorf("cannot remove '%s': No such key", arg)
|
||||
return fmt.Errorf("cannot remove '%s': No such key", target)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot remove '%s': %v", arg, err)
|
||||
return fmt.Errorf("cannot remove '%s': %v", target, err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
|
@ -107,6 +100,7 @@ func del(cmd *cobra.Command, args []string) error {
|
|||
|
||||
func init() {
|
||||
delCmd.Flags().BoolP("force", "f", false, "Force delete without confirmation")
|
||||
delCmd.Flags().BoolP("glob", "g", false, "Treat KEY arguments as glob patterns")
|
||||
rootCmd.AddCommand(delCmd)
|
||||
}
|
||||
|
||||
|
|
@ -144,3 +138,97 @@ func formatKeyForPrompt(store *Store, arg string) (string, error) {
|
|||
}
|
||||
return fmt.Sprintf("%s@%s", arg, db), nil
|
||||
}
|
||||
|
||||
func resolveDeleteTargets(store *Store, args []string, useGlob bool) ([]string, []string, error) {
|
||||
if !useGlob {
|
||||
targetKeys := make([]string, 0, len(args))
|
||||
for _, arg := range args {
|
||||
exists, err := keyExists(store, arg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot remove '%s': %v", arg, err)
|
||||
}
|
||||
if !exists {
|
||||
return nil, nil, fmt.Errorf("cannot remove '%s': No such key", arg)
|
||||
}
|
||||
targetKey, err := formatKeyForPrompt(store, arg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
targetKeys = append(targetKeys, targetKey)
|
||||
}
|
||||
return targetKeys, args, nil
|
||||
}
|
||||
|
||||
type compiledPattern struct {
|
||||
rawArg string
|
||||
db string
|
||||
matcher glob.Glob
|
||||
pattern string
|
||||
}
|
||||
|
||||
var compiled []compiledPattern
|
||||
for _, arg := range args {
|
||||
kb, db, err := store.parse(arg, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pattern := string(kb)
|
||||
m, err := glob.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot remove '%s': %v", arg, err)
|
||||
}
|
||||
compiled = append(compiled, compiledPattern{
|
||||
rawArg: arg,
|
||||
db: db,
|
||||
matcher: m,
|
||||
pattern: pattern,
|
||||
})
|
||||
}
|
||||
|
||||
keysByDB := make(map[string][]string)
|
||||
getKeys := func(db string) ([]string, error) {
|
||||
if keys, ok := keysByDB[db]; ok {
|
||||
return keys, nil
|
||||
}
|
||||
keys, err := store.Keys(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keysByDB[db] = keys
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
targetSet := make(map[string]struct{})
|
||||
var targetKeys []string
|
||||
var deleteTargets []string
|
||||
|
||||
for _, p := range compiled {
|
||||
keys, err := getKeys(p.db)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot remove '%s': %v", p.rawArg, err)
|
||||
}
|
||||
var matched []string
|
||||
for _, k := range keys {
|
||||
if p.matcher.Match(k) {
|
||||
matched = append(matched, fmt.Sprintf("%s@%s", k, p.db))
|
||||
}
|
||||
}
|
||||
if len(matched) == 0 {
|
||||
return nil, nil, fmt.Errorf("cannot remove '%s': No matches for pattern", p.rawArg)
|
||||
}
|
||||
for _, full := range matched {
|
||||
if _, seen := targetSet[full]; seen {
|
||||
continue
|
||||
}
|
||||
targetSet[full] = struct{}{}
|
||||
display, err := formatKeyForPrompt(store, full)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
targetKeys = append(targetKeys, display)
|
||||
deleteTargets = append(deleteTargets, full)
|
||||
}
|
||||
}
|
||||
|
||||
return targetKeys, deleteTargets, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,3 +253,26 @@ func formatExpiry(expiresAt uint64) string {
|
|||
}
|
||||
return fmt.Sprintf("%s (in %s)", expiry.Format(time.RFC3339), remaining.Round(time.Second))
|
||||
}
|
||||
|
||||
// Keys returns all keys for the provided database name (or default if empty).
|
||||
// Keys are returned in lowercase to mirror stored key format.
|
||||
func (s *Store) Keys(dbName string) ([]string, error) {
|
||||
db, err := s.open(dbName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
tx := db.NewTransaction(false)
|
||||
defer tx.Discard()
|
||||
|
||||
it := tx.NewIterator(badger.DefaultIteratorOptions)
|
||||
defer it.Close()
|
||||
|
||||
var keys []string
|
||||
for it.Rewind(); it.Valid(); it.Next() {
|
||||
item := it.Item()
|
||||
keys = append(keys, string(item.Key()))
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -5,6 +5,7 @@ go 1.25.3
|
|||
require (
|
||||
github.com/agnivade/levenshtein v1.2.1
|
||||
github.com/dgraph-io/badger/v4 v4.8.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/google/go-cmdtest v0.4.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.7.0
|
||||
github.com/muesli/go-app-paths v0.2.2
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -22,6 +22,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmdtest v0.4.0 h1:ToXh6W5spLp3npJV92tk6d5hIpUPYEzHLkD+rncbyhI=
|
||||
|
|
|
|||
10
testdata/del__glob__ok.ct
vendored
Normal file
10
testdata/del__glob__ok.ct
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
$ pda set a1 1
|
||||
$ pda set a2 2
|
||||
$ pda set b1 3
|
||||
$ pda del --glob a* --force
|
||||
$ pda get a1 --> FAIL
|
||||
Error: cannot get 'a1': Key not found
|
||||
$ pda get a2 --> FAIL
|
||||
Error: cannot get 'a2': Key not found
|
||||
$ pda get b1
|
||||
3
|
||||
2
testdata/help__del__ok.ct
vendored
2
testdata/help__del__ok.ct
vendored
|
|
@ -10,6 +10,7 @@ Aliases:
|
|||
|
||||
Flags:
|
||||
-f, --force Force delete without confirmation
|
||||
-g, --glob Treat KEY arguments as glob patterns
|
||||
-h, --help help for del
|
||||
Delete one or more keys. Optionally specify a db.
|
||||
|
||||
|
|
@ -21,4 +22,5 @@ Aliases:
|
|||
|
||||
Flags:
|
||||
-f, --force Force delete without confirmation
|
||||
-g, --glob Treat KEY arguments as glob patterns
|
||||
-h, --help help for del
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue