mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-17 03:24:40 -07:00
Initial Sublist
This commit is contained in:
277
sublist.go
Normal file
277
sublist.go
Normal file
@@ -0,0 +1,277 @@
|
||||
// Copyright 2012 Apcera Inc. All rights reserved.
|
||||
|
||||
package gnatsd
|
||||
|
||||
import (
|
||||
// "bytes"
|
||||
// "fmt"
|
||||
|
||||
// "github.com/apcera/gnatsd/hash"
|
||||
"github.com/apcera/gnatsd/hashmap"
|
||||
)
|
||||
|
||||
type node struct {
|
||||
next *level
|
||||
subs []interface{}
|
||||
}
|
||||
|
||||
type level struct {
|
||||
nodes *hashmap.HashMap
|
||||
pwc, fwc *node
|
||||
}
|
||||
|
||||
type Sublist struct {
|
||||
root *level
|
||||
count uint32
|
||||
}
|
||||
|
||||
func newNode() *node {
|
||||
return &node{subs: make([]interface{}, 0, 4)}
|
||||
}
|
||||
|
||||
func newLevel() *level {
|
||||
return &level{nodes: hashmap.New()}
|
||||
}
|
||||
|
||||
func New() *Sublist {
|
||||
return &Sublist{root: newLevel()}
|
||||
}
|
||||
|
||||
var (
|
||||
_PWC = byte('*')
|
||||
_FWC = byte('>')
|
||||
_SEP = byte('.')
|
||||
)
|
||||
|
||||
func split(subject []byte, tokens [][]byte) [][]byte {
|
||||
start := 0
|
||||
for i, b := range subject {
|
||||
if b == _SEP {
|
||||
tokens = append(tokens, subject[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
return append(tokens, subject[start:])
|
||||
}
|
||||
|
||||
func (s *Sublist) Insert(subject []byte, sub interface{}) {
|
||||
tsa := [16][]byte{}
|
||||
toks := split(subject, tsa[:0])
|
||||
|
||||
l := s.root
|
||||
var n *node
|
||||
|
||||
for _, t := range toks {
|
||||
switch t[0] {
|
||||
case _PWC:
|
||||
n = l.pwc
|
||||
case _FWC:
|
||||
n = l.fwc
|
||||
default:
|
||||
if v := l.nodes.Get(t); v == nil {
|
||||
n = nil
|
||||
} else {
|
||||
n = v.(*node)
|
||||
}
|
||||
}
|
||||
|
||||
if n == nil {
|
||||
n = newNode()
|
||||
switch t[0] {
|
||||
case _PWC:
|
||||
l.pwc = n
|
||||
case _FWC:
|
||||
l.fwc = n
|
||||
default:
|
||||
l.nodes.Set(t, n)
|
||||
}
|
||||
}
|
||||
if n.next == nil {
|
||||
n.next = newLevel()
|
||||
}
|
||||
l = n.next
|
||||
}
|
||||
n.subs = append(n.subs, sub)
|
||||
s.count++
|
||||
}
|
||||
|
||||
func (s *Sublist) Match(subject []byte) []interface{} {
|
||||
tsa := [16][]byte{}
|
||||
toks := split(subject, tsa[:0])
|
||||
// FIXME: Let them pass in?
|
||||
results := make([]interface{}, 0, 4)
|
||||
matchLevel(s.root, toks, &results)
|
||||
return results
|
||||
}
|
||||
|
||||
func matchLevel(l *level, toks [][]byte, results *[]interface{}) {
|
||||
var pwc, n *node
|
||||
for i, t := range toks {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
if l.fwc != nil {
|
||||
*results = append(*results, l.fwc.subs...)
|
||||
}
|
||||
if pwc = l.pwc; pwc != nil {
|
||||
matchLevel(pwc.next, toks[i+1:], results)
|
||||
}
|
||||
if v := l.nodes.Get(t); v == nil {
|
||||
n = nil
|
||||
} else {
|
||||
n = v.(*node)
|
||||
}
|
||||
if n != nil {
|
||||
l = n.next
|
||||
} else {
|
||||
l = nil
|
||||
}
|
||||
}
|
||||
if n != nil {
|
||||
*results = append(*results, n.subs...)
|
||||
}
|
||||
if pwc != nil {
|
||||
*results = append(*results, pwc.subs...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type lnt struct {
|
||||
l *level
|
||||
n *node
|
||||
t []byte
|
||||
}
|
||||
|
||||
func (s *Sublist) Remove(subject []byte, sub interface{}) {
|
||||
tsa := [16][]byte{}
|
||||
toks := split(subject, tsa[:0])
|
||||
l := s.root
|
||||
var n *node
|
||||
|
||||
var lnts [32]lnt
|
||||
levels := lnts[:0]
|
||||
|
||||
for _, t := range toks {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
switch t[0] {
|
||||
case _PWC:
|
||||
n = l.pwc
|
||||
case _FWC:
|
||||
n = l.fwc
|
||||
default:
|
||||
if v := l.nodes.Get(t); v == nil {
|
||||
n = nil
|
||||
} else {
|
||||
n = v.(*node)
|
||||
}
|
||||
}
|
||||
if n != nil {
|
||||
levels = append(levels, lnt{l, n, t})
|
||||
l = n.next
|
||||
} else {
|
||||
l = nil
|
||||
}
|
||||
}
|
||||
if !s.removeFromNode(n, sub) {
|
||||
return
|
||||
}
|
||||
for i := len(levels) - 1; i >= 0; i-- {
|
||||
l, n, t := levels[i].l, levels[i].n, levels[i].t
|
||||
if n.isEmpty() {
|
||||
l.pruneNode(n, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *level) pruneNode(n *node, t []byte) {
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
if n == l.fwc {
|
||||
l.fwc = nil
|
||||
} else if n == l.pwc {
|
||||
l.pwc = nil
|
||||
} else {
|
||||
l.nodes.Remove(t)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) isEmpty() bool {
|
||||
if len(n.subs) == 0 {
|
||||
if n.next == nil || n.next.numNodes() == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *level) numNodes() uint32 {
|
||||
num := l.nodes.Count()
|
||||
if l.pwc != nil {
|
||||
num += 1
|
||||
}
|
||||
if l.fwc != nil {
|
||||
num += 1
|
||||
}
|
||||
return num
|
||||
}
|
||||
|
||||
func (s *Sublist) removeFromNode(n *node, sub interface{}) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
for i, v := range n.subs {
|
||||
if v == sub {
|
||||
s.count--
|
||||
num := len(n.subs)
|
||||
a := n.subs
|
||||
copy(a[i:num-1], a[i+1:num])
|
||||
n.subs = a[0 : num-1]
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Sublist) Count() uint32 { return s.count }
|
||||
|
||||
func (s *Sublist) DebugNumLevels() int {
|
||||
return visitLevel(s.root, 0)
|
||||
}
|
||||
|
||||
func visitLevel(l *level, depth int) int {
|
||||
if l == nil || l.numNodes() == 0 {
|
||||
return depth
|
||||
}
|
||||
|
||||
depth += 1
|
||||
maxDepth := depth
|
||||
|
||||
all := l.nodes.All()
|
||||
for _, a := range all {
|
||||
if a == nil {
|
||||
continue
|
||||
}
|
||||
n := a.(*node)
|
||||
newDepth := visitLevel(n.next, depth)
|
||||
if newDepth > maxDepth {
|
||||
maxDepth = newDepth
|
||||
}
|
||||
}
|
||||
|
||||
if l.pwc != nil {
|
||||
pwcDepth := visitLevel(l.pwc.next, depth)
|
||||
if pwcDepth > maxDepth {
|
||||
maxDepth = pwcDepth
|
||||
}
|
||||
}
|
||||
if l.fwc != nil {
|
||||
fwcDepth := visitLevel(l.fwc.next, depth)
|
||||
if fwcDepth > maxDepth {
|
||||
maxDepth = fwcDepth
|
||||
}
|
||||
}
|
||||
return maxDepth
|
||||
}
|
||||
171
sublist_test.go
Normal file
171
sublist_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package gnatsd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func verifyCount(s *Sublist, count uint32, t *testing.T) {
|
||||
if s.Count() != count {
|
||||
t.Errorf("Count is %d, should be %d", s.Count(), count)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyLen(r []interface{}, l int, t *testing.T) {
|
||||
if len(r) != l {
|
||||
t.Errorf("Results len is %d, should be %d", len(r), l)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyMember(r []interface{}, val string, t *testing.T) {
|
||||
for _, v := range r {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if v.(string) == val {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("Value '%s' not found in results", val)
|
||||
}
|
||||
|
||||
func verifyNumLevels(s *Sublist, expected int, t *testing.T) {
|
||||
dl := s.DebugNumLevels()
|
||||
if dl != expected {
|
||||
t.Errorf("NumLevels is %d, should be %d", dl, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
s := New()
|
||||
verifyCount(s, 0, t)
|
||||
}
|
||||
|
||||
func TestInsertCount(t *testing.T) {
|
||||
s := New()
|
||||
s.Insert([]byte("foo"), "a")
|
||||
s.Insert([]byte("bar"), "b")
|
||||
s.Insert([]byte("foo.bar"), "b")
|
||||
verifyCount(s, 3, t)
|
||||
}
|
||||
|
||||
func TestSimple(t *testing.T) {
|
||||
s := New()
|
||||
val := "a"
|
||||
sub := []byte("foo")
|
||||
s.Insert(sub, val)
|
||||
r := s.Match(sub)
|
||||
verifyLen(r, 1, t)
|
||||
verifyMember(r, val, t)
|
||||
}
|
||||
|
||||
func TestSimpleMultiTokens(t *testing.T) {
|
||||
s := New()
|
||||
val := "a"
|
||||
sub := []byte("foo.bar.baz")
|
||||
s.Insert(sub, val)
|
||||
r := s.Match(sub)
|
||||
verifyLen(r, 1, t)
|
||||
verifyMember(r, val, t)
|
||||
}
|
||||
|
||||
func TestPartialWildcard(t *testing.T) {
|
||||
s := New()
|
||||
literal := []byte("a.b.c")
|
||||
pwc := []byte("a.*.c")
|
||||
a, b := "a", "b"
|
||||
s.Insert(literal, a)
|
||||
s.Insert(pwc, b)
|
||||
r := s.Match(literal)
|
||||
verifyLen(r, 2, t)
|
||||
verifyMember(r, a, t)
|
||||
verifyMember(r, b, t)
|
||||
}
|
||||
|
||||
func TestPartialWildcardAtEnd(t *testing.T) {
|
||||
s := New()
|
||||
literal := []byte("a.b.c")
|
||||
pwc := []byte("a.b.*")
|
||||
a, b := "a", "b"
|
||||
s.Insert(literal, a)
|
||||
s.Insert(pwc, b)
|
||||
r := s.Match(literal)
|
||||
verifyLen(r, 2, t)
|
||||
verifyMember(r, a, t)
|
||||
verifyMember(r, b, t)
|
||||
}
|
||||
|
||||
func TestFullWildcard(t *testing.T) {
|
||||
s := New()
|
||||
literal := []byte("a.b.c")
|
||||
fwc := []byte("a.>")
|
||||
a, b := "a", "b"
|
||||
s.Insert(literal, a)
|
||||
s.Insert(fwc, b)
|
||||
r := s.Match(literal)
|
||||
verifyLen(r, 2, t)
|
||||
verifyMember(r, a, t)
|
||||
verifyMember(r, b, t)
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
s := New()
|
||||
literal := []byte("a.b.c.d")
|
||||
value := "foo"
|
||||
s.Insert(literal, value)
|
||||
verifyCount(s, 1, t)
|
||||
s.Remove(literal, "bar")
|
||||
verifyCount(s, 1, t)
|
||||
s.Remove([]byte("a.b.c"), value)
|
||||
verifyCount(s, 1, t)
|
||||
s.Remove(literal, value)
|
||||
verifyCount(s, 0, t)
|
||||
r := s.Match(literal)
|
||||
verifyLen(r, 0, t)
|
||||
}
|
||||
|
||||
func TestRemoveWildcard(t *testing.T) {
|
||||
s := New()
|
||||
literal := []byte("a.b.c.d")
|
||||
pwc := []byte("a.b.*.d")
|
||||
fwc := []byte("a.b.>")
|
||||
value := "foo"
|
||||
s.Insert(pwc, value)
|
||||
s.Insert(fwc, value)
|
||||
s.Insert(literal, value)
|
||||
verifyCount(s, 3, t)
|
||||
r := s.Match(literal)
|
||||
verifyLen(r, 3, t)
|
||||
s.Remove(literal, value)
|
||||
verifyCount(s, 2, t)
|
||||
s.Remove(fwc, value)
|
||||
verifyCount(s, 1, t)
|
||||
s.Remove(pwc, value)
|
||||
verifyCount(s, 0, t)
|
||||
}
|
||||
|
||||
func TestRemoveCleanup(t *testing.T) {
|
||||
s := New()
|
||||
literal := []byte("a.b.c.d.e.f")
|
||||
depth := len(bytes.Split(literal, []byte(".")))
|
||||
value := "foo"
|
||||
verifyNumLevels(s, 0, t)
|
||||
s.Insert(literal, value)
|
||||
verifyNumLevels(s, depth, t)
|
||||
s.Remove(literal, value)
|
||||
verifyNumLevels(s, 0, t)
|
||||
}
|
||||
|
||||
func TestRemoveCleanupWildcards(t *testing.T) {
|
||||
s := New()
|
||||
literal := []byte("a.b.*.d.e.>")
|
||||
depth := len(bytes.Split(literal, []byte(".")))
|
||||
value := "foo"
|
||||
verifyNumLevels(s, 0, t)
|
||||
s.Insert(literal, value)
|
||||
verifyNumLevels(s, depth, t)
|
||||
s.Remove(literal, value)
|
||||
verifyNumLevels(s, 0, t)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user