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