mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-05 00:42:43 +08:00
use regex implementing wildcard
This commit is contained in:
@@ -280,7 +280,10 @@ func execPersist(db *DB, args [][]byte) redis.Reply {
|
||||
|
||||
// execKeys returns all keys matching the given pattern
|
||||
func execKeys(db *DB, args [][]byte) redis.Reply {
|
||||
pattern := wildcard.CompilePattern(string(args[0]))
|
||||
pattern, err := wildcard.CompilePattern(string(args[0]))
|
||||
if err != nil {
|
||||
return protocol.MakeErrReply("ERR illegal wildcard")
|
||||
}
|
||||
result := make([][]byte, 0)
|
||||
db.data.ForEach(func(key string, val interface{}) bool {
|
||||
if pattern.IsMatch(key) {
|
||||
|
@@ -1,131 +1,75 @@
|
||||
package wildcard
|
||||
|
||||
const (
|
||||
normal = iota
|
||||
all // *
|
||||
any // ?
|
||||
setSymbol // []
|
||||
rangSymbol // [a-b]
|
||||
negSymbol // [^a]
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type item struct {
|
||||
character byte
|
||||
set map[byte]bool
|
||||
typeCode int
|
||||
}
|
||||
|
||||
func (i *item) contains(c byte) bool {
|
||||
if i.typeCode == setSymbol {
|
||||
_, ok := i.set[c]
|
||||
return ok
|
||||
} else if i.typeCode == rangSymbol {
|
||||
if _, ok := i.set[c]; ok {
|
||||
return true
|
||||
}
|
||||
var (
|
||||
min uint8 = 255
|
||||
max uint8 = 0
|
||||
)
|
||||
for k := range i.set {
|
||||
if min > k {
|
||||
min = k
|
||||
}
|
||||
if max < k {
|
||||
max = k
|
||||
}
|
||||
}
|
||||
return c >= min && c <= max
|
||||
} else {
|
||||
_, ok := i.set[c]
|
||||
return !ok
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern represents a wildcard pattern
|
||||
type Pattern struct {
|
||||
items []*item
|
||||
exp *regexp.Regexp
|
||||
}
|
||||
|
||||
var replaceMap = map[byte]string{
|
||||
// characters in the wildcard that must be escaped in the regexp
|
||||
'+': `\+`,
|
||||
')': `\)`,
|
||||
'$': `\$`,
|
||||
'.': `\.`,
|
||||
'{': `\{`,
|
||||
'}': `\}`,
|
||||
'|': `\|`,
|
||||
'*': ".*",
|
||||
'?': ".",
|
||||
}
|
||||
|
||||
// CompilePattern convert wildcard string to Pattern
|
||||
func CompilePattern(src string) *Pattern {
|
||||
items := make([]*item, 0)
|
||||
escape := false
|
||||
inSet := false
|
||||
var set map[byte]bool
|
||||
for _, v := range src {
|
||||
c := byte(v)
|
||||
if escape {
|
||||
items = append(items, &item{typeCode: normal, character: c})
|
||||
escape = false
|
||||
} else if c == '*' {
|
||||
items = append(items, &item{typeCode: all})
|
||||
} else if c == '?' {
|
||||
items = append(items, &item{typeCode: any})
|
||||
} else if c == '\\' {
|
||||
escape = true
|
||||
} else if c == '[' {
|
||||
if !inSet {
|
||||
inSet = true
|
||||
set = make(map[byte]bool)
|
||||
} else {
|
||||
set[c] = true
|
||||
func CompilePattern(src string) (*Pattern, error) {
|
||||
regexSrc := strings.Builder{}
|
||||
regexSrc.WriteByte('^')
|
||||
for i := 0; i < len(src); i++ {
|
||||
ch := src[i]
|
||||
if ch == '\\' {
|
||||
if i == len(src)-1 {
|
||||
return nil, errors.New("end with escape \\")
|
||||
}
|
||||
} else if c == ']' {
|
||||
if inSet {
|
||||
inSet = false
|
||||
typeCode := setSymbol
|
||||
if _, ok := set['-']; ok {
|
||||
typeCode = rangSymbol
|
||||
delete(set, '-')
|
||||
regexSrc.WriteByte(ch)
|
||||
regexSrc.WriteByte(src[i+1])
|
||||
i++ // skip escaped character
|
||||
} else if ch == '^' {
|
||||
if i == 0 {
|
||||
regexSrc.WriteString(`\^`)
|
||||
} else if i == 1 {
|
||||
if src[i-1] == '[' {
|
||||
regexSrc.WriteString(`^`) // src is: [^
|
||||
} else {
|
||||
regexSrc.WriteString(`\^`)
|
||||
}
|
||||
if _, ok := set['^']; ok {
|
||||
typeCode = negSymbol
|
||||
delete(set, '^')
|
||||
}
|
||||
items = append(items, &item{typeCode: typeCode, set: set})
|
||||
} else {
|
||||
items = append(items, &item{typeCode: normal, character: c})
|
||||
if src[i-1] == '[' && src[i-2] != '\\' {
|
||||
regexSrc.WriteString(`^`) // src is: [^, except \[^
|
||||
} else {
|
||||
regexSrc.WriteString(`\^`)
|
||||
}
|
||||
}
|
||||
} else if escaped, toEscape := replaceMap[ch]; toEscape {
|
||||
regexSrc.WriteString(escaped)
|
||||
} else {
|
||||
if inSet {
|
||||
set[c] = true
|
||||
} else {
|
||||
items = append(items, &item{typeCode: normal, character: c})
|
||||
}
|
||||
regexSrc.WriteByte(ch)
|
||||
}
|
||||
}
|
||||
return &Pattern{
|
||||
items: items,
|
||||
regexSrc.WriteByte('$')
|
||||
re, err := regexp.Compile(regexSrc.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Pattern{
|
||||
exp: re,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsMatch returns whether the given string matches pattern
|
||||
func (p *Pattern) IsMatch(s string) bool {
|
||||
if len(p.items) == 0 {
|
||||
return len(s) == 0
|
||||
}
|
||||
m := len(s)
|
||||
n := len(p.items)
|
||||
table := make([][]bool, m+1)
|
||||
for i := 0; i < m+1; i++ {
|
||||
table[i] = make([]bool, n+1)
|
||||
}
|
||||
table[0][0] = true
|
||||
for j := 1; j < n+1; j++ {
|
||||
table[0][j] = table[0][j-1] && p.items[j-1].typeCode == all
|
||||
}
|
||||
for i := 1; i < m+1; i++ {
|
||||
for j := 1; j < n+1; j++ {
|
||||
if p.items[j-1].typeCode == all {
|
||||
table[i][j] = table[i-1][j] || table[i][j-1]
|
||||
} else {
|
||||
table[i][j] = table[i-1][j-1] &&
|
||||
(p.items[j-1].typeCode == any ||
|
||||
(p.items[j-1].typeCode == normal && uint8(s[i-1]) == p.items[j-1].character) ||
|
||||
(p.items[j-1].typeCode >= setSymbol && p.items[j-1].contains(s[i-1])))
|
||||
}
|
||||
}
|
||||
}
|
||||
return table[m][n]
|
||||
return p.exp.Match([]byte(s))
|
||||
}
|
||||
|
@@ -3,11 +3,19 @@ package wildcard
|
||||
import "testing"
|
||||
|
||||
func TestWildCard(t *testing.T) {
|
||||
p := CompilePattern("")
|
||||
p, err := CompilePattern("")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !p.IsMatch("") {
|
||||
t.Error("expect true actually false")
|
||||
}
|
||||
p = CompilePattern("a")
|
||||
p, err = CompilePattern("a")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !p.IsMatch("a") {
|
||||
t.Error("expect true actually false")
|
||||
}
|
||||
@@ -16,7 +24,11 @@ func TestWildCard(t *testing.T) {
|
||||
}
|
||||
|
||||
// test '?'
|
||||
p = CompilePattern("a?")
|
||||
p, err = CompilePattern("a?")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !p.IsMatch("ab") {
|
||||
t.Error("expect true actually false")
|
||||
}
|
||||
@@ -31,7 +43,11 @@ func TestWildCard(t *testing.T) {
|
||||
}
|
||||
|
||||
// test *
|
||||
p = CompilePattern("a*")
|
||||
p, err = CompilePattern("a*")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !p.IsMatch("ab") {
|
||||
t.Error("expect true actually false")
|
||||
}
|
||||
@@ -46,7 +62,11 @@ func TestWildCard(t *testing.T) {
|
||||
}
|
||||
|
||||
// test []
|
||||
p = CompilePattern("a[ab[]")
|
||||
p, err = CompilePattern("a[ab[]")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !p.IsMatch("ab") {
|
||||
t.Error("expect true actually false")
|
||||
}
|
||||
@@ -64,7 +84,11 @@ func TestWildCard(t *testing.T) {
|
||||
}
|
||||
|
||||
// test [a-c]
|
||||
p = CompilePattern("h[a-c]llo")
|
||||
p, err = CompilePattern("h[a-c]llo")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !p.IsMatch("hallo") {
|
||||
t.Error("expect true actually false")
|
||||
}
|
||||
@@ -81,8 +105,12 @@ func TestWildCard(t *testing.T) {
|
||||
t.Error("expect false actually true")
|
||||
}
|
||||
|
||||
// test [^]
|
||||
p = CompilePattern("h[^ab]llo")
|
||||
//test [^]
|
||||
p, err = CompilePattern("h[^ab]llo")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if p.IsMatch("hallo") {
|
||||
t.Error("expect false actually true")
|
||||
}
|
||||
@@ -94,12 +122,20 @@ func TestWildCard(t *testing.T) {
|
||||
}
|
||||
|
||||
// test escape
|
||||
p = CompilePattern("\\\\") // pattern: \\
|
||||
if !p.IsMatch("\\") {
|
||||
p, err = CompilePattern(`\\\\`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !p.IsMatch(`\\`) {
|
||||
t.Error("expect true actually false")
|
||||
}
|
||||
|
||||
p = CompilePattern("\\*")
|
||||
p, err = CompilePattern("\\*")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !p.IsMatch("*") {
|
||||
t.Error("expect true actually false")
|
||||
}
|
||||
|
Reference in New Issue
Block a user