Initial QSfera import
This commit is contained in:
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright 2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// For dumping out a text representation of a tree.
|
||||
func (t *SubjectTree[T]) Dump(w io.Writer) {
|
||||
t.dump(w, t.root, 0)
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
|
||||
// Will dump out a node.
|
||||
func (t *SubjectTree[T]) dump(w io.Writer, n node, depth int) {
|
||||
if n == nil {
|
||||
fmt.Fprintf(w, "EMPTY\n")
|
||||
return
|
||||
}
|
||||
if n.isLeaf() {
|
||||
leaf := n.(*leaf[T])
|
||||
fmt.Fprintf(w, "%s LEAF: Suffix: %q Value: %+v\n", dumpPre(depth), leaf.suffix, leaf.value)
|
||||
n = nil
|
||||
} else {
|
||||
// We are a node type here, grab meta portion.
|
||||
bn := n.base()
|
||||
fmt.Fprintf(w, "%s %s Prefix: %q\n", dumpPre(depth), n.kind(), bn.prefix)
|
||||
depth++
|
||||
n.iter(func(n node) bool {
|
||||
t.dump(w, n, depth)
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// For individual node/leaf dumps.
|
||||
func (n *leaf[T]) kind() string { return "LEAF" }
|
||||
func (n *node4) kind() string { return "NODE4" }
|
||||
func (n *node10) kind() string { return "NODE10" }
|
||||
func (n *node16) kind() string { return "NODE16" }
|
||||
func (n *node48) kind() string { return "NODE48" }
|
||||
func (n *node256) kind() string { return "NODE256" }
|
||||
|
||||
// Calculates the indendation, etc.
|
||||
func dumpPre(depth int) string {
|
||||
if depth == 0 {
|
||||
return "-- "
|
||||
} else {
|
||||
var b strings.Builder
|
||||
for i := 0; i < depth; i++ {
|
||||
b.WriteString(" ")
|
||||
}
|
||||
b.WriteString("|__ ")
|
||||
return b.String()
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// Copyright 2023-2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// Leaf node
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type leaf[T any] struct {
|
||||
value T
|
||||
// This could be the whole subject, but most likely just the suffix portion.
|
||||
// We will only store the suffix here and assume all prior prefix paths have
|
||||
// been checked once we arrive at this leafnode.
|
||||
suffix []byte
|
||||
}
|
||||
|
||||
func newLeaf[T any](suffix []byte, value T) *leaf[T] {
|
||||
return &leaf[T]{value, copyBytes(suffix)}
|
||||
}
|
||||
|
||||
func (n *leaf[T]) isLeaf() bool { return true }
|
||||
func (n *leaf[T]) base() *meta { return nil }
|
||||
func (n *leaf[T]) match(subject []byte) bool { return bytes.Equal(subject, n.suffix) }
|
||||
func (n *leaf[T]) setSuffix(suffix []byte) { n.suffix = copyBytes(suffix) }
|
||||
func (n *leaf[T]) isFull() bool { return true }
|
||||
func (n *leaf[T]) matchParts(parts [][]byte) ([][]byte, bool) { return matchParts(parts, n.suffix) }
|
||||
func (n *leaf[T]) iter(f func(node) bool) {}
|
||||
func (n *leaf[T]) children() []node { return nil }
|
||||
func (n *leaf[T]) numChildren() uint16 { return 0 }
|
||||
func (n *leaf[T]) path() []byte { return n.suffix }
|
||||
|
||||
// Not applicable to leafs and should not be called, so panic if we do.
|
||||
func (n *leaf[T]) setPrefix(pre []byte) { panic("setPrefix called on leaf") }
|
||||
func (n *leaf[T]) addChild(_ byte, _ node) { panic("addChild called on leaf") }
|
||||
func (n *leaf[T]) findChild(_ byte) *node { panic("findChild called on leaf") }
|
||||
func (n *leaf[T]) grow() node { panic("grow called on leaf") }
|
||||
func (n *leaf[T]) deleteChild(_ byte) { panic("deleteChild called on leaf") }
|
||||
func (n *leaf[T]) shrink() node { panic("shrink called on leaf") }
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright 2023-2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
// Internal node interface.
|
||||
type node interface {
|
||||
isLeaf() bool
|
||||
base() *meta
|
||||
setPrefix(pre []byte)
|
||||
addChild(c byte, n node)
|
||||
findChild(c byte) *node
|
||||
deleteChild(c byte)
|
||||
isFull() bool
|
||||
grow() node
|
||||
shrink() node
|
||||
matchParts(parts [][]byte) ([][]byte, bool)
|
||||
kind() string
|
||||
iter(f func(node) bool)
|
||||
children() []node
|
||||
numChildren() uint16
|
||||
path() []byte
|
||||
}
|
||||
|
||||
type meta struct {
|
||||
prefix []byte
|
||||
size uint16
|
||||
}
|
||||
|
||||
func (n *meta) isLeaf() bool { return false }
|
||||
func (n *meta) base() *meta { return n }
|
||||
|
||||
func (n *meta) setPrefix(pre []byte) {
|
||||
n.prefix = append([]byte(nil), pre...)
|
||||
}
|
||||
|
||||
func (n *meta) numChildren() uint16 { return n.size }
|
||||
func (n *meta) path() []byte { return n.prefix }
|
||||
|
||||
// Will match parts against our prefix.
|
||||
func (n *meta) matchParts(parts [][]byte) ([][]byte, bool) {
|
||||
return matchParts(parts, n.prefix)
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright 2023-2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
// Node with 10 children
|
||||
// This node size is for the particular case that a part of the subject is numeric
|
||||
// in nature, i.e. it only needs to satisfy the range 0-9 without wasting bytes
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node10 struct {
|
||||
child [10]node
|
||||
meta
|
||||
key [10]byte
|
||||
}
|
||||
|
||||
func newNode10(prefix []byte) *node10 {
|
||||
nn := &node10{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
// Currently we do not keep node10 sorted or use bitfields for traversal so just add to the end.
|
||||
// TODO(dlc) - We should revisit here with more detailed benchmarks.
|
||||
func (n *node10) addChild(c byte, nn node) {
|
||||
if n.size >= 10 {
|
||||
panic("node10 full!")
|
||||
}
|
||||
n.key[n.size] = c
|
||||
n.child[n.size] = nn
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node10) findChild(c byte) *node {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
return &n.child[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node10) isFull() bool { return n.size >= 10 }
|
||||
|
||||
func (n *node10) grow() node {
|
||||
nn := newNode16(n.prefix)
|
||||
for i := 0; i < 10; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node10) deleteChild(c byte) {
|
||||
for i, last := uint16(0), n.size-1; i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
// Unsorted so just swap in last one here, else nil if last.
|
||||
if i < last {
|
||||
n.key[i] = n.key[last]
|
||||
n.child[i] = n.child[last]
|
||||
n.key[last] = 0
|
||||
n.child[last] = nil
|
||||
} else {
|
||||
n.key[i] = 0
|
||||
n.child[i] = nil
|
||||
}
|
||||
n.size--
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node10) shrink() node {
|
||||
if n.size > 4 {
|
||||
return nil
|
||||
}
|
||||
nn := newNode4(nil)
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node10) iter(f func(node) bool) {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if !f(n.child[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node10) children() []node {
|
||||
return n.child[:n.size]
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Copyright 2023-2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
// Node with 16 children
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node16 struct {
|
||||
child [16]node
|
||||
meta
|
||||
key [16]byte
|
||||
}
|
||||
|
||||
func newNode16(prefix []byte) *node16 {
|
||||
nn := &node16{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
// Currently we do not keep node16 sorted or use bitfields for traversal so just add to the end.
|
||||
// TODO(dlc) - We should revisit here with more detailed benchmarks.
|
||||
func (n *node16) addChild(c byte, nn node) {
|
||||
if n.size >= 16 {
|
||||
panic("node16 full!")
|
||||
}
|
||||
n.key[n.size] = c
|
||||
n.child[n.size] = nn
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node16) findChild(c byte) *node {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
return &n.child[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node16) isFull() bool { return n.size >= 16 }
|
||||
|
||||
func (n *node16) grow() node {
|
||||
nn := newNode48(n.prefix)
|
||||
for i := 0; i < 16; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node16) deleteChild(c byte) {
|
||||
for i, last := uint16(0), n.size-1; i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
// Unsorted so just swap in last one here, else nil if last.
|
||||
if i < last {
|
||||
n.key[i] = n.key[last]
|
||||
n.child[i] = n.child[last]
|
||||
n.key[last] = 0
|
||||
n.child[last] = nil
|
||||
} else {
|
||||
n.key[i] = 0
|
||||
n.child[i] = nil
|
||||
}
|
||||
n.size--
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node16) shrink() node {
|
||||
if n.size > 10 {
|
||||
return nil
|
||||
}
|
||||
nn := newNode10(nil)
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node16) iter(f func(node) bool) {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if !f(n.child[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node16) children() []node {
|
||||
return n.child[:n.size]
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright 2023-2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
// Node with 256 children
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node256 struct {
|
||||
child [256]node
|
||||
meta
|
||||
}
|
||||
|
||||
func newNode256(prefix []byte) *node256 {
|
||||
nn := &node256{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
func (n *node256) addChild(c byte, nn node) {
|
||||
n.child[c] = nn
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node256) findChild(c byte) *node {
|
||||
if n.child[c] != nil {
|
||||
return &n.child[c]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node256) isFull() bool { return false }
|
||||
func (n *node256) grow() node { panic("grow can not be called on node256") }
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node256) deleteChild(c byte) {
|
||||
if n.child[c] != nil {
|
||||
n.child[c] = nil
|
||||
n.size--
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node256) shrink() node {
|
||||
if n.size > 48 {
|
||||
return nil
|
||||
}
|
||||
nn := newNode48(nil)
|
||||
for c, child := range n.child {
|
||||
if child != nil {
|
||||
nn.addChild(byte(c), n.child[c])
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node256) iter(f func(node) bool) {
|
||||
for i := 0; i < 256; i++ {
|
||||
if n.child[i] != nil {
|
||||
if !f(n.child[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node256) children() []node {
|
||||
return n.child[:256]
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright 2023-2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
// Node with 4 children
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node4 struct {
|
||||
child [4]node
|
||||
meta
|
||||
key [4]byte
|
||||
}
|
||||
|
||||
func newNode4(prefix []byte) *node4 {
|
||||
nn := &node4{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
// Currently we do not need to keep sorted for traversal so just add to the end.
|
||||
func (n *node4) addChild(c byte, nn node) {
|
||||
if n.size >= 4 {
|
||||
panic("node4 full!")
|
||||
}
|
||||
n.key[n.size] = c
|
||||
n.child[n.size] = nn
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node4) findChild(c byte) *node {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
return &n.child[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node4) isFull() bool { return n.size >= 4 }
|
||||
|
||||
func (n *node4) grow() node {
|
||||
nn := newNode10(n.prefix)
|
||||
for i := 0; i < 4; i++ {
|
||||
nn.addChild(n.key[i], n.child[i])
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node4) deleteChild(c byte) {
|
||||
for i, last := uint16(0), n.size-1; i < n.size; i++ {
|
||||
if n.key[i] == c {
|
||||
// Unsorted so just swap in last one here, else nil if last.
|
||||
if i < last {
|
||||
n.key[i] = n.key[last]
|
||||
n.child[i] = n.child[last]
|
||||
n.key[last] = 0
|
||||
n.child[last] = nil
|
||||
} else {
|
||||
n.key[i] = 0
|
||||
n.child[i] = nil
|
||||
}
|
||||
n.size--
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node4) shrink() node {
|
||||
if n.size == 1 {
|
||||
return n.child[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node4) iter(f func(node) bool) {
|
||||
for i := uint16(0); i < n.size; i++ {
|
||||
if !f(n.child[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node4) children() []node {
|
||||
return n.child[:n.size]
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
// Copyright 2023-2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
// Node with 48 children
|
||||
// Memory saving vs node256 comes from the fact that the child array is 16 bytes
|
||||
// per `node` entry, so node256's 256*16=4096 vs node48's 256+(48*16)=1024
|
||||
// Note that `key` is effectively 1-indexed, as 0 means no entry, so offset by 1
|
||||
// Order of struct fields for best memory alignment (as per govet/fieldalignment)
|
||||
type node48 struct {
|
||||
child [48]node
|
||||
meta
|
||||
key [256]byte
|
||||
}
|
||||
|
||||
func newNode48(prefix []byte) *node48 {
|
||||
nn := &node48{}
|
||||
nn.setPrefix(prefix)
|
||||
return nn
|
||||
}
|
||||
|
||||
func (n *node48) addChild(c byte, nn node) {
|
||||
if n.size >= 48 {
|
||||
panic("node48 full!")
|
||||
}
|
||||
n.child[n.size] = nn
|
||||
n.key[c] = byte(n.size + 1) // 1-indexed
|
||||
n.size++
|
||||
}
|
||||
|
||||
func (n *node48) findChild(c byte) *node {
|
||||
i := n.key[c]
|
||||
if i == 0 {
|
||||
return nil
|
||||
}
|
||||
return &n.child[i-1]
|
||||
}
|
||||
|
||||
func (n *node48) isFull() bool { return n.size >= 48 }
|
||||
|
||||
func (n *node48) grow() node {
|
||||
nn := newNode256(n.prefix)
|
||||
for c := 0; c < len(n.key); c++ {
|
||||
if i := n.key[byte(c)]; i > 0 {
|
||||
nn.addChild(byte(c), n.child[i-1])
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Deletes a child from the node.
|
||||
func (n *node48) deleteChild(c byte) {
|
||||
i := n.key[c]
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
i-- // Adjust for 1-indexing
|
||||
last := byte(n.size - 1)
|
||||
if i < last {
|
||||
n.child[i] = n.child[last]
|
||||
for ic := 0; ic < len(n.key); ic++ {
|
||||
if n.key[byte(ic)] == last+1 {
|
||||
n.key[byte(ic)] = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
n.child[last] = nil
|
||||
n.key[c] = 0
|
||||
n.size--
|
||||
}
|
||||
|
||||
// Shrink if needed and return new node, otherwise return nil.
|
||||
func (n *node48) shrink() node {
|
||||
if n.size > 16 {
|
||||
return nil
|
||||
}
|
||||
nn := newNode16(nil)
|
||||
for c := 0; c < len(n.key); c++ {
|
||||
if i := n.key[byte(c)]; i > 0 {
|
||||
nn.addChild(byte(c), n.child[i-1])
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// Iterate over all children calling func f.
|
||||
func (n *node48) iter(f func(node) bool) {
|
||||
for _, c := range n.child {
|
||||
if c != nil && !f(c) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return our children as a slice.
|
||||
func (n *node48) children() []node {
|
||||
return n.child[:n.size]
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
// Copyright 2023-2025 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// genParts will break a filter subject up into parts.
|
||||
// We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'.
|
||||
// We do not care about other tokens per se, just parts that are separated by wildcards with an optional end fwc.
|
||||
func genParts(filter []byte, parts [][]byte) [][]byte {
|
||||
var start int
|
||||
for i, e := 0, len(filter)-1; i < len(filter); i++ {
|
||||
if filter[i] == tsep {
|
||||
// See if next token is pwc. Either internal or end pwc.
|
||||
if i < e && filter[i+1] == pwc && (i+2 <= e && filter[i+2] == tsep || i+1 == e) {
|
||||
if i > start {
|
||||
parts = append(parts, filter[start:i+1])
|
||||
}
|
||||
parts = append(parts, filter[i+1:i+2])
|
||||
i++ // Skip pwc
|
||||
if i+2 <= e {
|
||||
i++ // Skip next tsep from next part too.
|
||||
}
|
||||
start = i + 1
|
||||
} else if i < e && filter[i+1] == fwc && i+1 == e {
|
||||
if i > start {
|
||||
parts = append(parts, filter[start:i+1])
|
||||
}
|
||||
parts = append(parts, filter[i+1:i+2])
|
||||
i++ // Skip fwc
|
||||
start = i + 1
|
||||
}
|
||||
} else if filter[i] == pwc || filter[i] == fwc {
|
||||
// Wildcard must be at the start or preceded by tsep.
|
||||
if prev := i - 1; prev >= 0 && filter[prev] != tsep {
|
||||
continue
|
||||
}
|
||||
// Wildcard must be at the end or followed by tsep.
|
||||
if next := i + 1; next == e || next < e && filter[next] != tsep {
|
||||
continue
|
||||
}
|
||||
// Full wildcard must be terminal.
|
||||
if filter[i] == fwc && i < e {
|
||||
break
|
||||
}
|
||||
// We start with a pwc or fwc.
|
||||
parts = append(parts, filter[i:i+1])
|
||||
if i+1 <= e {
|
||||
i++ // Skip next tsep from next part too.
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
if start < len(filter) {
|
||||
// Check to see if we need to eat a leading tsep.
|
||||
if filter[start] == tsep {
|
||||
start++
|
||||
}
|
||||
parts = append(parts, filter[start:])
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
// Match our parts against a fragment, which could be prefix for nodes or a suffix for leafs.
|
||||
func matchParts(parts [][]byte, frag []byte) ([][]byte, bool) {
|
||||
lf := len(frag)
|
||||
if lf == 0 {
|
||||
return parts, true
|
||||
}
|
||||
|
||||
var si int
|
||||
lpi := len(parts) - 1
|
||||
|
||||
for i, part := range parts {
|
||||
if si >= lf {
|
||||
return parts[i:], true
|
||||
}
|
||||
lp := len(part)
|
||||
// Check for pwc or fwc place holders.
|
||||
if lp == 1 {
|
||||
if part[0] == pwc {
|
||||
index := bytes.IndexByte(frag[si:], tsep)
|
||||
// We are trying to match pwc and did not find our tsep.
|
||||
// Will need to move to next node from caller.
|
||||
if index < 0 {
|
||||
if i == lpi {
|
||||
return nil, true
|
||||
}
|
||||
return parts[i:], true
|
||||
}
|
||||
si += index + 1
|
||||
continue
|
||||
} else if part[0] == fwc {
|
||||
// If we are here we should be good.
|
||||
return nil, true
|
||||
}
|
||||
}
|
||||
end := min(si+lp, lf)
|
||||
// If part is bigger then the remaining fragment, adjust to a portion on the part.
|
||||
if si+lp > end {
|
||||
// Frag is smaller then part itself.
|
||||
part = part[:end-si]
|
||||
}
|
||||
if !bytes.Equal(part, frag[si:end]) {
|
||||
return parts, false
|
||||
}
|
||||
// If we still have a portion of the fragment left, update and continue.
|
||||
if end < lf {
|
||||
si = end
|
||||
continue
|
||||
}
|
||||
// If we matched a partial, do not move past current part
|
||||
// but update the part to what was consumed. This allows upper layers to continue.
|
||||
if end < si+lp {
|
||||
if end >= lf {
|
||||
// Create a copy before modifying. Reuse slice capacity available at the
|
||||
// end of the parts slice, since this saves us additional allocations.
|
||||
lp := len(parts)
|
||||
parts = append(parts[lp:], parts[:lp]...)
|
||||
parts[i] = parts[i][lf-si:]
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
return parts[i:], true
|
||||
}
|
||||
if i == lpi {
|
||||
return nil, true
|
||||
}
|
||||
// If we are here we are not the last part which means we have a wildcard
|
||||
// gap, so we need to match anything up to next tsep.
|
||||
si += len(part)
|
||||
}
|
||||
return parts, false
|
||||
}
|
||||
+540
@@ -0,0 +1,540 @@
|
||||
// Copyright 2023-2025 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"slices"
|
||||
"unsafe"
|
||||
|
||||
"github.com/nats-io/nats-server/v2/server/gsl"
|
||||
)
|
||||
|
||||
// SubjectTree is an adaptive radix trie (ART) for storing subject information on literal subjects.
|
||||
// Will use dynamic nodes, path compression and lazy expansion.
|
||||
// The reason this exists is to not only save some memory in our filestore but to greatly optimize matching
|
||||
// a wildcard subject to certain members, e.g. consumer NumPending calculations.
|
||||
type SubjectTree[T any] struct {
|
||||
root node
|
||||
size int
|
||||
}
|
||||
|
||||
// NewSubjectTree creates a new SubjectTree with values T.
|
||||
func NewSubjectTree[T any]() *SubjectTree[T] {
|
||||
return &SubjectTree[T]{}
|
||||
}
|
||||
|
||||
// Size returns the number of elements stored.
|
||||
func (t *SubjectTree[T]) Size() int {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return t.size
|
||||
}
|
||||
|
||||
// Will empty out the tree, or if tree is nil create a new one.
|
||||
func (t *SubjectTree[T]) Empty() *SubjectTree[T] {
|
||||
if t == nil {
|
||||
return NewSubjectTree[T]()
|
||||
}
|
||||
t.root, t.size = nil, 0
|
||||
return t
|
||||
}
|
||||
|
||||
// Insert a value into the tree. Will return if the value was updated and if so the old value.
|
||||
func (t *SubjectTree[T]) Insert(subject []byte, value T) (*T, bool) {
|
||||
if t == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Make sure we never insert anything with a noPivot byte.
|
||||
if bytes.IndexByte(subject, noPivot) >= 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
old, updated := t.insert(&t.root, subject, value, 0)
|
||||
if !updated {
|
||||
t.size++
|
||||
}
|
||||
return old, updated
|
||||
}
|
||||
|
||||
// Find will find the value and return it or false if it was not found.
|
||||
func (t *SubjectTree[T]) Find(subject []byte) (*T, bool) {
|
||||
if t == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var si int
|
||||
for n := t.root; n != nil; {
|
||||
if n.isLeaf() {
|
||||
if ln := n.(*leaf[T]); ln.match(subject[si:]) {
|
||||
return &ln.value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
// We are a node type here, grab meta portion.
|
||||
if bn := n.base(); len(bn.prefix) > 0 {
|
||||
end := min(si+len(bn.prefix), len(subject))
|
||||
if !bytes.Equal(subject[si:end], bn.prefix) {
|
||||
return nil, false
|
||||
}
|
||||
// Increment our subject index.
|
||||
si += len(bn.prefix)
|
||||
}
|
||||
if an := n.findChild(pivot(subject, si)); an != nil {
|
||||
n = *an
|
||||
} else {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Delete will delete the item and return its value, or not found if it did not exist.
|
||||
func (t *SubjectTree[T]) Delete(subject []byte) (*T, bool) {
|
||||
if t == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
val, deleted := t.delete(&t.root, subject, 0)
|
||||
if deleted {
|
||||
t.size--
|
||||
}
|
||||
return val, deleted
|
||||
}
|
||||
|
||||
// Match will match against a subject that can have wildcards and invoke the callback func for each matched value.
|
||||
func (t *SubjectTree[T]) Match(filter []byte, cb func(subject []byte, val *T)) {
|
||||
if t == nil || t.root == nil || len(filter) == 0 || cb == nil {
|
||||
return
|
||||
}
|
||||
// We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'.
|
||||
var raw [16][]byte
|
||||
parts := genParts(filter, raw[:0])
|
||||
var _pre [256]byte
|
||||
t.match(t.root, parts, _pre[:0], func(subject []byte, val *T) bool {
|
||||
cb(subject, val)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// MatchUntil will match against a subject that can have wildcards and invoke
|
||||
// the callback func for each matched value.
|
||||
// Returning false from the callback will stop matching immediately.
|
||||
// Returns true if matching ran to completion, false if callback stopped it early.
|
||||
func (t *SubjectTree[T]) MatchUntil(filter []byte, cb func(subject []byte, val *T) bool) bool {
|
||||
if t == nil || t.root == nil || len(filter) == 0 || cb == nil {
|
||||
return true
|
||||
}
|
||||
// We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'.
|
||||
var raw [16][]byte
|
||||
parts := genParts(filter, raw[:0])
|
||||
var _pre [256]byte
|
||||
return t.match(t.root, parts, _pre[:0], cb)
|
||||
}
|
||||
|
||||
// IterOrdered will walk all entries in the SubjectTree lexicographically. The callback can return false to terminate the walk.
|
||||
func (t *SubjectTree[T]) IterOrdered(cb func(subject []byte, val *T) bool) {
|
||||
if t == nil || t.root == nil {
|
||||
return
|
||||
}
|
||||
var _pre [256]byte
|
||||
t.iter(t.root, _pre[:0], true, cb)
|
||||
}
|
||||
|
||||
// IterFast will walk all entries in the SubjectTree with no guarantees of ordering. The callback can return false to terminate the walk.
|
||||
func (t *SubjectTree[T]) IterFast(cb func(subject []byte, val *T) bool) {
|
||||
if t == nil || t.root == nil {
|
||||
return
|
||||
}
|
||||
var _pre [256]byte
|
||||
t.iter(t.root, _pre[:0], false, cb)
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
// Internal call to insert that can be recursive.
|
||||
func (t *SubjectTree[T]) insert(np *node, subject []byte, value T, si int) (*T, bool) {
|
||||
n := *np
|
||||
if n == nil {
|
||||
*np = newLeaf(subject, value)
|
||||
return nil, false
|
||||
}
|
||||
if n.isLeaf() {
|
||||
ln := n.(*leaf[T])
|
||||
if ln.match(subject[si:]) {
|
||||
// Replace with new value.
|
||||
old := ln.value
|
||||
ln.value = value
|
||||
return &old, true
|
||||
}
|
||||
// Here we need to split this leaf.
|
||||
cpi := commonPrefixLen(ln.suffix, subject[si:])
|
||||
nn := newNode4(subject[si : si+cpi])
|
||||
ln.setSuffix(ln.suffix[cpi:])
|
||||
si += cpi
|
||||
// Make sure we have different pivot, normally this will be the case unless we have overflowing prefixes.
|
||||
if p := pivot(ln.suffix, 0); cpi > 0 && si < len(subject) && p == subject[si] {
|
||||
// We need to split the original leaf. Recursively call into insert.
|
||||
t.insert(np, subject, value, si)
|
||||
// Now add the update version of *np as a child to the new node4.
|
||||
nn.addChild(p, *np)
|
||||
} else {
|
||||
// Can just add this new leaf as a sibling.
|
||||
nl := newLeaf(subject[si:], value)
|
||||
nn.addChild(pivot(nl.suffix, 0), nl)
|
||||
// Add back original.
|
||||
nn.addChild(pivot(ln.suffix, 0), ln)
|
||||
}
|
||||
*np = nn
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Non-leaf nodes.
|
||||
bn := n.base()
|
||||
if len(bn.prefix) > 0 {
|
||||
cpi := commonPrefixLen(bn.prefix, subject[si:])
|
||||
if pli := len(bn.prefix); cpi >= pli {
|
||||
// Move past this node. We look for an existing child node to recurse into.
|
||||
// If one does not exist we can create a new leaf node.
|
||||
si += pli
|
||||
if nn := n.findChild(pivot(subject, si)); nn != nil {
|
||||
return t.insert(nn, subject, value, si)
|
||||
}
|
||||
if n.isFull() {
|
||||
n = n.grow()
|
||||
*np = n
|
||||
}
|
||||
n.addChild(pivot(subject, si), newLeaf(subject[si:], value))
|
||||
return nil, false
|
||||
} else {
|
||||
// We did not match the prefix completely here.
|
||||
// Calculate new prefix for this node.
|
||||
prefix := subject[si : si+cpi]
|
||||
si += len(prefix)
|
||||
// We will insert a new node4 and attach our current node below after adjusting prefix.
|
||||
nn := newNode4(prefix)
|
||||
// Shift the prefix for our original node.
|
||||
n.setPrefix(bn.prefix[cpi:])
|
||||
nn.addChild(pivot(bn.prefix[:], 0), n)
|
||||
// Add in our new leaf.
|
||||
nn.addChild(pivot(subject[si:], 0), newLeaf(subject[si:], value))
|
||||
// Update our node reference.
|
||||
*np = nn
|
||||
}
|
||||
} else {
|
||||
if nn := n.findChild(pivot(subject, si)); nn != nil {
|
||||
return t.insert(nn, subject, value, si)
|
||||
}
|
||||
// No prefix and no matched child, so add in new leafnode as needed.
|
||||
if n.isFull() {
|
||||
n = n.grow()
|
||||
*np = n
|
||||
}
|
||||
n.addChild(pivot(subject, si), newLeaf(subject[si:], value))
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// internal function to recursively find the leaf to delete. Will do compaction if the item is found and removed.
|
||||
func (t *SubjectTree[T]) delete(np *node, subject []byte, si int) (*T, bool) {
|
||||
if t == nil || np == nil || *np == nil || len(subject) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
n := *np
|
||||
if n.isLeaf() {
|
||||
ln := n.(*leaf[T])
|
||||
if ln.match(subject[si:]) {
|
||||
*np = nil
|
||||
return &ln.value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
// Not a leaf node.
|
||||
if bn := n.base(); len(bn.prefix) > 0 {
|
||||
// subject could be shorter and would panic on bad index into subject slice.
|
||||
if len(subject) < si+len(bn.prefix) {
|
||||
return nil, false
|
||||
}
|
||||
if !bytes.Equal(subject[si:si+len(bn.prefix)], bn.prefix) {
|
||||
return nil, false
|
||||
}
|
||||
// Increment our subject index.
|
||||
si += len(bn.prefix)
|
||||
}
|
||||
p := pivot(subject, si)
|
||||
nna := n.findChild(p)
|
||||
if nna == nil {
|
||||
return nil, false
|
||||
}
|
||||
nn := *nna
|
||||
if nn.isLeaf() {
|
||||
ln := nn.(*leaf[T])
|
||||
if ln.match(subject[si:]) {
|
||||
n.deleteChild(p)
|
||||
|
||||
if sn := n.shrink(); sn != nil {
|
||||
bn := n.base()
|
||||
// Make sure to set cap so we force an append to copy below.
|
||||
pre := bn.prefix[:len(bn.prefix):len(bn.prefix)]
|
||||
// Need to fix up prefixes/suffixes.
|
||||
if sn.isLeaf() {
|
||||
ln := sn.(*leaf[T])
|
||||
// Make sure to set cap so we force an append to copy.
|
||||
ln.suffix = append(pre, ln.suffix...)
|
||||
} else {
|
||||
// We are a node here, we need to add in the old prefix.
|
||||
if len(pre) > 0 {
|
||||
bsn := sn.base()
|
||||
sn.setPrefix(append(pre, bsn.prefix...))
|
||||
}
|
||||
}
|
||||
*np = sn
|
||||
}
|
||||
|
||||
return &ln.value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
return t.delete(nna, subject, si)
|
||||
}
|
||||
|
||||
// Internal function which can be called recursively to match all leaf nodes to a given filter subject which
|
||||
// once here has been decomposed to parts. These parts only care about wildcards, both pwc and fwc.
|
||||
// Returns false if the callback requested to stop matching.
|
||||
func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subject []byte, val *T) bool) bool {
|
||||
// Capture if we are sitting on a terminal fwc.
|
||||
var hasFWC bool
|
||||
if lp := len(parts); lp > 0 && len(parts[lp-1]) > 0 && parts[lp-1][0] == fwc {
|
||||
hasFWC = true
|
||||
}
|
||||
|
||||
for n != nil {
|
||||
nparts, matched := n.matchParts(parts)
|
||||
// Check if we did not match.
|
||||
if !matched {
|
||||
return true
|
||||
}
|
||||
// We have matched here. If we are a leaf and have exhausted all parts or he have a FWC fire callback.
|
||||
if n.isLeaf() {
|
||||
if len(nparts) == 0 || (hasFWC && len(nparts) == 1) {
|
||||
ln := n.(*leaf[T])
|
||||
if !cb(append(pre, ln.suffix...), &ln.value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// We have normal nodes here.
|
||||
// We need to append our prefix
|
||||
bn := n.base()
|
||||
if len(bn.prefix) > 0 {
|
||||
// Note that this append may reallocate, but it doesn't modify "pre" at the "match" callsite.
|
||||
pre = append(pre, bn.prefix...)
|
||||
}
|
||||
|
||||
// Check our remaining parts.
|
||||
if len(nparts) == 0 && !hasFWC {
|
||||
// We are a node with no parts left and we are not looking at a fwc.
|
||||
// We could have a leafnode with no suffix which would be a match.
|
||||
// We could also have a terminal pwc. Check for those here.
|
||||
var hasTermPWC bool
|
||||
if lp := len(parts); lp > 0 && len(parts[lp-1]) == 1 && parts[lp-1][0] == pwc {
|
||||
// If we are sitting on a terminal pwc, put the pwc back and continue.
|
||||
nparts = parts[len(parts)-1:]
|
||||
hasTermPWC = true
|
||||
}
|
||||
for _, cn := range n.children() {
|
||||
if cn == nil {
|
||||
continue
|
||||
}
|
||||
if cn.isLeaf() {
|
||||
ln := cn.(*leaf[T])
|
||||
if len(ln.suffix) == 0 {
|
||||
if !cb(append(pre, ln.suffix...), &ln.value) {
|
||||
return false
|
||||
}
|
||||
} else if hasTermPWC && bytes.IndexByte(ln.suffix, tsep) < 0 {
|
||||
if !cb(append(pre, ln.suffix...), &ln.value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else if hasTermPWC {
|
||||
// We have terminal pwc so call into match again with the child node.
|
||||
if !t.match(cn, nparts, pre, cb) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return regardless.
|
||||
return true
|
||||
}
|
||||
// If we are sitting on a terminal fwc, put back and continue.
|
||||
if hasFWC && len(nparts) == 0 {
|
||||
nparts = parts[len(parts)-1:]
|
||||
}
|
||||
|
||||
// Here we are a node type with a partial match.
|
||||
// Check if the first part is a wildcard.
|
||||
fp := nparts[0]
|
||||
p := pivot(fp, 0)
|
||||
// Check if we have a pwc/fwc part here. This will cause us to iterate.
|
||||
if len(fp) == 1 && (p == pwc || p == fwc) {
|
||||
// We need to iterate over all children here for the current node
|
||||
// to see if we match further down.
|
||||
for _, cn := range n.children() {
|
||||
if cn != nil {
|
||||
if !t.match(cn, nparts, pre, cb) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// Here we have normal traversal, so find the next child.
|
||||
nn := n.findChild(p)
|
||||
if nn == nil {
|
||||
return true
|
||||
}
|
||||
n, parts = *nn, nparts
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Internal iter function to walk nodes in lexicographical order.
|
||||
func (t *SubjectTree[T]) iter(n node, pre []byte, ordered bool, cb func(subject []byte, val *T) bool) bool {
|
||||
if n.isLeaf() {
|
||||
ln := n.(*leaf[T])
|
||||
return cb(append(pre, ln.suffix...), &ln.value)
|
||||
}
|
||||
// We are normal node here.
|
||||
bn := n.base()
|
||||
// Note that this append may reallocate, but it doesn't modify "pre" at the "iter" callsite.
|
||||
pre = append(pre, bn.prefix...)
|
||||
// Not everything requires lexicographical sorting, so support a fast path for iterating in
|
||||
// whatever order the stree has things stored instead.
|
||||
if !ordered {
|
||||
for _, cn := range n.children() {
|
||||
if cn == nil {
|
||||
continue
|
||||
}
|
||||
if !t.iter(cn, pre, false, cb) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// Collect nodes since unsorted.
|
||||
var _nodes [256]node
|
||||
nodes := _nodes[:0]
|
||||
for _, cn := range n.children() {
|
||||
if cn != nil {
|
||||
nodes = append(nodes, cn)
|
||||
}
|
||||
}
|
||||
// Now sort.
|
||||
slices.SortStableFunc(nodes, func(a, b node) int { return bytes.Compare(a.path(), b.path()) })
|
||||
// Now walk the nodes in order and call into next iter.
|
||||
for i := range nodes {
|
||||
if !t.iter(nodes[i], pre, true, cb) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// LazyIntersect iterates the smaller of the two provided subject trees and
|
||||
// looks for matching entries in the other. It is lazy in that it does not
|
||||
// aggressively optimize against repeated walks, but is considerably faster
|
||||
// in most cases than intersecting against a potentially large sublist.
|
||||
func LazyIntersect[TL, TR any](tl *SubjectTree[TL], tr *SubjectTree[TR], cb func([]byte, *TL, *TR)) {
|
||||
if tl == nil || tr == nil || tl.root == nil || tr.root == nil {
|
||||
return
|
||||
}
|
||||
// Iterate over the smaller tree to reduce the number of rounds.
|
||||
if tl.Size() <= tr.Size() {
|
||||
tl.IterFast(func(key []byte, v1 *TL) bool {
|
||||
if v2, ok := tr.Find(key); ok {
|
||||
cb(key, v1, v2)
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
tr.IterFast(func(key []byte, v2 *TR) bool {
|
||||
if v1, ok := tl.Find(key); ok {
|
||||
cb(key, v1, v2)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// IntersectGSL will match all items in the given subject tree that
|
||||
// have interest expressed in the given sublist. The callback will only be called
|
||||
// once for each subject, regardless of overlapping subscriptions in the sublist.
|
||||
func IntersectGSL[T any, SL comparable](t *SubjectTree[T], sl *gsl.GenericSublist[SL], cb func(subject []byte, val *T)) {
|
||||
if t == nil || t.root == nil || sl == nil {
|
||||
return
|
||||
}
|
||||
var _pre [256]byte
|
||||
_intersectGSL(t.root, _pre[:0], sl, cb)
|
||||
}
|
||||
|
||||
func _intersectGSL[T any, SL comparable](n node, pre []byte, sl *gsl.GenericSublist[SL], cb func(subject []byte, val *T)) {
|
||||
if n.isLeaf() {
|
||||
ln := n.(*leaf[T])
|
||||
subj := append(pre, ln.suffix...)
|
||||
if sl.HasInterest(bytesToString(subj)) {
|
||||
cb(subj, &ln.value)
|
||||
}
|
||||
return
|
||||
}
|
||||
bn := n.base()
|
||||
pre = append(pre, bn.prefix...)
|
||||
for _, cn := range n.children() {
|
||||
if cn == nil {
|
||||
continue
|
||||
}
|
||||
subj := append(pre, cn.path()...)
|
||||
if !hasInterestForTokens(sl, subj, len(pre)) {
|
||||
continue
|
||||
}
|
||||
_intersectGSL(cn, pre, sl, cb)
|
||||
}
|
||||
}
|
||||
|
||||
// The subject tree can return partial tokens so we need to check starting interest
|
||||
// only from whole tokens when we encounter a tsep.
|
||||
func hasInterestForTokens[SL comparable](sl *gsl.GenericSublist[SL], subj []byte, since int) bool {
|
||||
for i := since; i < len(subj); i++ {
|
||||
if subj[i] == tsep {
|
||||
if !sl.HasInterestStartingIn(bytesToString(subj[:i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Note this will avoid a copy of the data used for the string, but it will also reference the existing slice's data pointer.
|
||||
// So this should be used sparingly when we know the encompassing byte slice's lifetime is the same.
|
||||
func bytesToString(b []byte) string {
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
p := unsafe.SliceData(b)
|
||||
return unsafe.String(p, len(b))
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Copyright 2023-2025 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stree
|
||||
|
||||
// For subject matching.
|
||||
const (
|
||||
pwc = '*'
|
||||
fwc = '>'
|
||||
tsep = '.'
|
||||
)
|
||||
|
||||
// Determine index of common prefix. No match at all is 0, etc.
|
||||
func commonPrefixLen(s1, s2 []byte) int {
|
||||
limit := min(len(s1), len(s2))
|
||||
var i int
|
||||
for ; i < limit; i++ {
|
||||
if s1[i] != s2[i] {
|
||||
break
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// Helper to copy bytes.
|
||||
func copyBytes(src []byte) []byte {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
dst := make([]byte, len(src))
|
||||
copy(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
type position interface{ int | uint16 }
|
||||
|
||||
// No pivot available.
|
||||
const noPivot = byte(127)
|
||||
|
||||
// Can return 127 (DEL) if we have all the subject as prefixes.
|
||||
// We used to use 0, but when that was in the subject would cause infinite recursion in some situations.
|
||||
func pivot[N position](subject []byte, pos N) byte {
|
||||
if int(pos) >= len(subject) {
|
||||
return noPivot
|
||||
}
|
||||
return subject[pos]
|
||||
}
|
||||
Reference in New Issue
Block a user