From 6b42af17217858d8e30dcb5138d3bf7895e9afb2 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:33:50 +0400 Subject: [PATCH 01/20] fast lid representation for optimized comparison --- frac/fraction_test.go | 193 ++++++++++++++++++++++++------ frac/processor/aggregator.go | 34 +++--- frac/processor/aggregator_test.go | 34 +++--- frac/processor/eval_test.go | 6 +- frac/processor/eval_tree.go | 10 +- frac/processor/search.go | 7 +- frac/sealed/lids/iterator_asc.go | 7 +- frac/sealed/lids/iterator_desc.go | 8 +- node/bench_test.go | 44 +++---- node/builder.go | 4 +- node/cmp_lid.go | 59 +++++++++ node/cmp_lid_test.go | 38 ++++++ node/less_fn.go | 13 -- node/node.go | 4 +- node/node_and.go | 39 +++--- node/node_nand.go | 39 +++--- node/node_not.go | 2 +- node/node_or.go | 77 +++++------- node/node_range.go | 21 ++-- node/node_static.go | 16 ++- node/node_test.go | 34 +++--- node/sourced_node_wrapper.go | 6 +- 22 files changed, 434 insertions(+), 261 deletions(-) create mode 100644 node/cmp_lid.go create mode 100644 node/cmp_lid_test.go delete mode 100644 node/less_fn.go diff --git a/frac/fraction_test.go b/frac/fraction_test.go index 374e4c23..d8de64f1 100644 --- a/frac/fraction_test.go +++ b/frac/fraction_test.go @@ -1264,6 +1264,74 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { fromTime: fromTime, toTime: midTime, }, + // AND operator queries + { + name: "message:request AND message:failed", + query: "message:request AND message:failed", + filter: func(doc *testDoc) bool { + return strings.Contains(doc.message, "request") && strings.Contains(doc.message, "failed") + }, + fromTime: fromTime, + toTime: toTime, + }, + { + name: "service:gateway AND message:processing AND message:retry AND level:5", + query: "service:gateway AND message:processing AND message:retry AND level:5", + filter: func(doc *testDoc) bool { + return doc.service == "gateway" && strings.Contains(doc.message, "processing") && + strings.Contains(doc.message, "retry") && doc.level == 5 + }, + fromTime: fromTime, + toTime: toTime, + }, + + // OR operator queries + { + name: "trace_id OR", + query: "trace_id:trace-1000 OR trace_id:trace-1500 OR trace_id:trace-2000 OR trace_id:trace-2500 OR trace_id:trace-3000", + filter: func(doc *testDoc) bool { + return doc.traceId == "trace-1000" || + doc.traceId == "trace-1500" || + doc.traceId == "trace-2000" || + doc.traceId == "trace-2500" || + doc.traceId == "trace-3000" + }, + fromTime: fromTime, + toTime: toTime, + }, + + // mixed AND/OR + { + name: "message:request AND (level:1 OR level:3 OR level:5) AND trace_id:trace-2*", + query: "message:request AND (level:1 OR level:3 OR level:5) AND trace_id:trace-2*", + filter: func(doc *testDoc) bool { + return strings.Contains(doc.message, "request") && (doc.level == 1 || doc.level == 3 || doc.level == 5) && + strings.Contains(doc.traceId, "trace-2") + }, + fromTime: fromTime, + toTime: toTime, + }, + { + name: "complex AND+OR", + query: "(service:gateway OR service:proxy OR service:scheduler) AND " + + "(message:request OR message:failed) AND level:[1 to 3]", + filter: func(doc *testDoc) bool { + return (doc.service == "gateway" || doc.service == "proxy" || doc.service == "scheduler") && + (strings.Contains(doc.message, "request") || strings.Contains(doc.message, "failed")) && + (doc.level >= 1 && doc.level <= 3) + }, + fromTime: fromTime, + toTime: toTime, + }, + + // other queries + { + name: "trace_id:trace-4*", + query: "trace_id:trace-4*", + filter: func(doc *testDoc) bool { return strings.Contains(doc.traceId, "trace-4") }, + fromTime: fromTime, + toTime: toTime, + }, } for _, tc := range searchTestCases { @@ -1298,37 +1366,85 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { } s.Run("service:kafka | group by pod unique_count(client_ip)", func() { - ips := make(map[string]map[string]struct{}) - for _, doc := range testDocs { - if doc.service != "kafka" { - continue + // Check both sort orders simply for aggTree to be iterated in a different order + orders := []seq.DocsOrder{seq.DocsOrderDesc, seq.DocsOrderAsc} + + for _, ord := range orders { + ips := make(map[string]map[string]struct{}) + for _, doc := range testDocs { + if doc.service != "kafka" { + continue + } + if ips[doc.pod] == nil { + ips[doc.pod] = make(map[string]struct{}) + } + + ips[doc.pod][doc.clientIp] = struct{}{} } - if ips[doc.pod] == nil { - ips[doc.pod] = make(map[string]struct{}) + + var expectedBuckets []seq.AggregationBucket + for pod, podIps := range ips { + expectedBuckets = append(expectedBuckets, seq.AggregationBucket{ + Name: pod, + Value: float64(len(podIps)), + NotExists: 0, + }) } - ips[doc.pod][doc.clientIp] = struct{}{} + searchParams := s.query( + "service:kafka", + withTo(toTime.Format(time.RFC3339Nano)), + withAggQuery(processor.AggQuery{ + Field: aggField("client_ip"), + GroupBy: aggField("pod"), + Func: seq.AggFuncUniqueCount, + })) + searchParams.Order = ord + + s.AssertAggregation(searchParams, seq.AggregateArgs{Func: seq.AggFuncUniqueCount}, expectedBuckets) } + }) - var expectedBuckets []seq.AggregationBucket - for pod, podIps := range ips { - expectedBuckets = append(expectedBuckets, seq.AggregationBucket{ - Name: pod, - Value: float64(len(podIps)), - NotExists: 0, - }) - } + s.Run("service:scheduler | group by pod avg(level)", func() { + // Check both sort orders simply for aggTree to be iterated in a different order + orders := []seq.DocsOrder{seq.DocsOrderDesc, seq.DocsOrderAsc} - searchParams := s.query( - "service:kafka", - withTo(toTime.Format(time.RFC3339Nano)), - withAggQuery(processor.AggQuery{ - Field: aggField("client_ip"), - GroupBy: aggField("pod"), - Func: seq.AggFuncUniqueCount, - })) + for _, ord := range orders { + levelsByPod := make(map[string][]int) + for _, doc := range testDocs { + if doc.service != "scheduler" { + continue + } + + levelsByPod[doc.pod] = append(levelsByPod[doc.pod], doc.level) + } + + var expectedBuckets []seq.AggregationBucket + for pod, levels := range levelsByPod { + sum := 0 + for _, level := range levels { + sum += level + } + avg := float64(sum) / float64(len(levels)) + expectedBuckets = append(expectedBuckets, seq.AggregationBucket{ + Name: pod, + Value: avg, + NotExists: 0, + }) + } - s.AssertAggregation(searchParams, seq.AggregateArgs{Func: seq.AggFuncUniqueCount}, expectedBuckets) + searchParams := s.query( + "service:scheduler", + withTo(toTime.Format(time.RFC3339Nano)), + withAggQuery(processor.AggQuery{ + Field: aggField("level"), + GroupBy: aggField("pod"), + Func: seq.AggFuncAvg, + })) + searchParams.Order = ord + + s.AssertAggregation(searchParams, seq.AggregateArgs{Func: seq.AggFuncAvg}, expectedBuckets) + } }) s.Run("NOT message:retry | group by service avg(level)", func() { @@ -1369,18 +1485,27 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { }) s.Run("service:database AND level:3 | hist 1s", func() { - histBuckets := make(map[string]uint64) - for _, doc := range testDocs { - if doc.service == "database" && doc.level == 3 { - bucketTime := doc.timestamp.Truncate(time.Second) - bucketKey := bucketTime.Format(time.RFC3339Nano) - histBuckets[bucketKey]++ + // Check both sort orders simply for lid tree to be iterated in a different order + orders := []seq.DocsOrder{seq.DocsOrderDesc, seq.DocsOrderAsc} + + for _, ord := range orders { + histBuckets := make(map[string]uint64) + for _, doc := range testDocs { + if doc.service == "database" && doc.level == 3 { + bucketTime := doc.timestamp.Truncate(time.Second) + bucketKey := bucketTime.Format(time.RFC3339Nano) + histBuckets[bucketKey]++ + } } - } - s.AssertHist( - s.query("service:database AND level:3", withTo(toTime.Format(time.RFC3339Nano)), withHist(1000)), - histBuckets) + searchParams := s.query( + "service:database AND level:3", + withTo(toTime.Format(time.RFC3339Nano)), + withHist(1000)) + searchParams.Order = ord + + s.AssertHist(searchParams, histBuckets) + } }) s.Run("scroll with offset id", func() { diff --git a/frac/processor/aggregator.go b/frac/processor/aggregator.go index c9a35e72..ec5b0e94 100644 --- a/frac/processor/aggregator.go +++ b/frac/processor/aggregator.go @@ -70,7 +70,7 @@ func NewGroupAndFieldAggregator( } // Next iterates over groupBy and field iterators (actually trees) to count occurrence. -func (n *TwoSourceAggregator) Next(lid uint32) error { +func (n *TwoSourceAggregator) Next(lid node.CmpLID) error { groupBySource, hasGroupBy, err := n.groupBy.ConsumeTokenSource(lid) if err != nil { return err @@ -100,7 +100,7 @@ func (n *TwoSourceAggregator) Next(lid uint32) error { // Both group and field exist, increment the count for the combined sources. source := AggBin[twoSources]{ - MID: n.extractMID(seq.LID(lid)), + MID: n.extractMID(seq.LID(lid.Unpack())), Source: twoSources{ GroupBySource: groupBySource, FieldSource: fieldSource, @@ -204,14 +204,14 @@ func NewSingleSourceCountAggregator( } // Next iterates over groupBy tree to count occurrence. -func (n *SingleSourceCountAggregator) Next(lid uint32) error { +func (n *SingleSourceCountAggregator) Next(lid node.CmpLID) error { source, has, err := n.group.ConsumeTokenSource(lid) if err != nil { return err } if has { - mid := n.extractMID(seq.LID(lid)) + mid := n.extractMID(seq.LID(lid.Unpack())) n.countBySource[AggBin[uint32]{ MID: mid, @@ -273,7 +273,7 @@ func NewSingleSourceUniqueAggregator(iterator *SourcedNodeIterator) *SingleSourc } // Next iterates over groupBy tree to count occurrence. -func (n *SingleSourceUniqueAggregator) Next(lid uint32) error { +func (n *SingleSourceUniqueAggregator) Next(lid node.CmpLID) error { source, has, err := n.group.ConsumeTokenSource(lid) if err != nil { return err @@ -325,13 +325,13 @@ func NewSingleSourceHistogramAggregator( } } -func (n *SingleSourceHistogramAggregator) Next(lid uint32) error { +func (n *SingleSourceHistogramAggregator) Next(lid node.CmpLID) error { source, has, err := n.field.ConsumeTokenSource(lid) if err != nil { return err } - mid := n.extractMID(seq.LID(lid)) + mid := n.extractMID(seq.LID(lid.Unpack())) if _, ok := n.histogram[mid]; !ok { n.histogram[mid] = seq.NewSamplesContainers() } @@ -381,15 +381,14 @@ type SourcedNodeIterator struct { uniqSourcesLimit iteratorLimit countBySource map[uint32]int - lastID uint32 + lastID node.CmpLID lastSource uint32 - has bool - less node.LessFn + reverse bool } func NewSourcedNodeIterator(sourced node.Sourced, ti tokenIndex, tids []uint32, limit iteratorLimit, reverse bool) *SourcedNodeIterator { - lastID, lastSource, has := sourced.NextSourced() + lastID, lastSource := sourced.NextSourced() return &SourcedNodeIterator{ sourcedNode: sourced, ti: ti, @@ -399,17 +398,16 @@ func NewSourcedNodeIterator(sourced node.Sourced, ti tokenIndex, tids []uint32, countBySource: make(map[uint32]int), lastID: lastID, lastSource: lastSource, - has: has, - less: node.GetLessFn(reverse), + reverse: reverse, } } -func (s *SourcedNodeIterator) ConsumeTokenSource(lid uint32) (uint32, bool, error) { - for s.has && s.less(s.lastID, lid) { - s.lastID, s.lastSource, s.has = s.sourcedNode.NextSourced() +func (s *SourcedNodeIterator) ConsumeTokenSource(lid node.CmpLID) (uint32, bool, error) { + for !s.lastID.IsNull() && s.lastID.Less(lid) { + s.lastID, s.lastSource = s.sourcedNode.NextSourced() } - exists := s.has && s.lastID == lid + exists := !s.lastID.IsNull() && s.lastID == lid if !exists { return 0, false, nil } @@ -421,7 +419,7 @@ func (s *SourcedNodeIterator) ConsumeTokenSource(lid uint32) (uint32, bool, erro s.countBySource[s.lastSource]++ if len(s.countBySource) > s.uniqSourcesLimit.limit { - return lid, true, fmt.Errorf("%w: iterator limit is exceeded", s.uniqSourcesLimit.err) + return lid.Unpack(), true, fmt.Errorf("%w: iterator limit is exceeded", s.uniqSourcesLimit.err) } return s.lastSource, true, nil diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index d5f12351..6d7f375a 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -32,7 +32,7 @@ func TestSingleSourceCountAggregator(t *testing.T) { iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) agg := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) for _, id := range searchDocs { - if err := agg.Next(id); err != nil { + if err := agg.Next(node.NewCmpLIDOrderDesc(id)); err != nil { t.Fatal(err) } } @@ -64,7 +64,7 @@ func TestSingleSourceCountAggregatorWithInterval(t *testing.T) { }) for _, id := range searchDocs { - if err := agg.Next(id); err != nil { + if err := agg.Next(node.NewCmpLIDOrderDesc(id)); err != nil { t.Fatal(err) } } @@ -102,7 +102,7 @@ func BenchmarkAggDeep(b *testing.B) { for b.Loop() { for _, v := range vals { - if err := n.Next(v); err != nil { + if err := n.Next(node.NewCmpLIDOrderDesc(v)); err != nil { b.Fatal(err) } } @@ -135,7 +135,7 @@ func BenchmarkAggWide(b *testing.B) { for b.Loop() { for _, v := range vals { - if err := n.Next(v); err != nil { + if err := n.Next(node.NewCmpLIDOrderDesc(v)); err != nil { b.Fatal(err) } } @@ -153,7 +153,7 @@ func (m *MockTokenIndex) GetValByTID(tid uint32) []byte { } type IDSourcePair struct { - LID uint32 + LID node.CmpLID Source uint32 } @@ -166,13 +166,13 @@ func (m *MockNode) String() string { return reflect.TypeOf(m).String() } -func (m *MockNode) NextSourced() (uint32, uint32, bool) { +func (m *MockNode) NextSourced() (node.CmpLID, uint32) { if len(m.Pairs) == 0 { - return 0, 0, false + return node.NullCmpLID(), 0 } first := m.Pairs[0] m.Pairs = m.Pairs[1:] - return first.LID, first.Source, true + return first.LID, first.Source } func TestTwoSourceAggregator(t *testing.T) { @@ -182,14 +182,14 @@ func TestTwoSourceAggregator(t *testing.T) { dp := &MockTokenIndex{} field := &MockNode{ Pairs: []IDSourcePair{ - {LID: 1, Source: 0}, - {LID: 2, Source: 1}, + {LID: node.NewCmpLIDOrderDesc(1), Source: 0}, + {LID: node.NewCmpLIDOrderDesc(2), Source: 1}, }, } groupBy := &MockNode{ Pairs: []IDSourcePair{ - {LID: 1, Source: 0}, - {LID: 2, Source: 1}, + {LID: node.NewCmpLIDOrderDesc(1), Source: 0}, + {LID: node.NewCmpLIDOrderDesc(2), Source: 1}, }, } @@ -203,8 +203,8 @@ func TestTwoSourceAggregator(t *testing.T) { ) // Call Next for two data points. - r.NoError(aggregator.Next(1)) - r.NoError(aggregator.Next(2)) + r.NoError(aggregator.Next(node.NewCmpLIDOrderDesc(1))) + r.NoError(aggregator.Next(node.NewCmpLIDOrderDesc(2))) // Verify countBySource map. expectedCountBySource := map[twoSources]int64{ @@ -244,14 +244,14 @@ func TestSingleTreeCountAggregator(t *testing.T) { dp := &MockTokenIndex{} field := &MockNode{ Pairs: []IDSourcePair{ - {LID: 1, Source: 0}, + {LID: node.NewCmpLIDOrderDesc(1), Source: 0}, }, } iter := NewSourcedNodeIterator(field, dp, []uint32{0}, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) aggregator := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) - r.NoError(aggregator.Next(1)) + r.NoError(aggregator.Next(node.NewCmpLIDOrderDesc(1))) result, err := aggregator.Aggregate() if err != nil { @@ -293,7 +293,7 @@ func TestAggregatorLimitExceeded(t *testing.T) { var limitIteration int for i, id := range searchDocs { - if err := agg.Next(id); err != nil { + if err := agg.Next(node.NewCmpLIDOrderDesc(id)); err != nil { limitErr = err limitIteration = i break diff --git a/frac/processor/eval_test.go b/frac/processor/eval_test.go index 249f63c1..eaa433f3 100644 --- a/frac/processor/eval_test.go +++ b/frac/processor/eval_test.go @@ -50,10 +50,8 @@ func (p *staticProvider) newStatic(literal *parser.Literal) (node.Node, error) { } func readAllInto(n node.Node, ids []uint32) []uint32 { - id, has := n.Next() - for has { - ids = append(ids, id) - id, has = n.Next() + for cmp := n.Next(); !cmp.IsNull(); cmp = n.Next() { + ids = append(ids, cmp.Unpack()) } return ids } diff --git a/frac/processor/eval_tree.go b/frac/processor/eval_tree.go index 99a7769c..8c9ca326 100644 --- a/frac/processor/eval_tree.go +++ b/frac/processor/eval_tree.go @@ -34,13 +34,13 @@ func buildEvalTree(root *parser.ASTNode, minVal, maxVal uint32, stats *searchSta switch token.Operator { case parser.LogicalAnd: stats.NodesTotal++ - return node.NewAnd(children[0], children[1], reverse), nil + return node.NewAnd(children[0], children[1]), nil case parser.LogicalOr: stats.NodesTotal++ - return node.NewOr(children[0], children[1], reverse), nil + return node.NewOr(children[0], children[1]), nil case parser.LogicalNAnd: stats.NodesTotal++ - return node.NewNAnd(children[0], children[1], reverse), nil + return node.NewNAnd(children[0], children[1]), nil case parser.LogicalNot: stats.NodesTotal++ return node.NewNot(children[0], minVal, maxVal, reverse), nil @@ -74,12 +74,12 @@ func evalLeaf( stats.NodesTotal += len(lidsTids)*2 - 1 } - return node.BuildORTree(lidsTids, order.IsReverse()), nil + return node.BuildORTree(lidsTids), nil } type Aggregator interface { // Next iterates to count the next lid. - Next(lid uint32) error + Next(lid node.CmpLID) error // Aggregate processes and returns the final aggregation result. Aggregate() (seq.AggregatableSamples, error) } diff --git a/frac/processor/search.go b/frac/processor/search.go index 21737712..b84b5b99 100644 --- a/frac/processor/search.go +++ b/frac/processor/search.go @@ -189,12 +189,13 @@ func iterateEvalTree( } timerEval.Start() - lid, has := evalTree.Next() + cmpLid := evalTree.Next() timerEval.Stop() - if !has { + if cmpLid.IsNull() { break } + lid := cmpLid.Unpack() if needMore || hasHist { timerMID.Start() @@ -232,7 +233,7 @@ func iterateEvalTree( if len(aggs) > 0 { timerAgg.Start() for i := range aggs { - if err := aggs[i].Next(lid); err != nil { + if err := aggs[i].Next(cmpLid); err != nil { timerAgg.Stop() return total, ids, histogram, err } diff --git a/frac/sealed/lids/iterator_asc.go b/frac/sealed/lids/iterator_asc.go index 11bb48d1..368adc65 100644 --- a/frac/sealed/lids/iterator_asc.go +++ b/frac/sealed/lids/iterator_asc.go @@ -6,6 +6,7 @@ import ( "go.uber.org/zap" "github.com/ozontech/seq-db/logger" + "github.com/ozontech/seq-db/node" ) type IteratorAsc Cursor @@ -55,10 +56,10 @@ func (it *IteratorAsc) loadNextLIDsBlock() { it.blockIndex-- } -func (it *IteratorAsc) Next() (uint32, bool) { +func (it *IteratorAsc) Next() node.CmpLID { for len(it.lids) == 0 { if !it.tryNextBlock { - return 0, false + return node.NewCmpLIDOrderAsc(0) } it.loadNextLIDsBlock() // last chunk in block but not last for tid; need load next block @@ -69,5 +70,5 @@ func (it *IteratorAsc) Next() (uint32, bool) { i := len(it.lids) - 1 lid := it.lids[i] it.lids = it.lids[:i] - return lid, true + return node.NewCmpLIDOrderAsc(lid) } diff --git a/frac/sealed/lids/iterator_desc.go b/frac/sealed/lids/iterator_desc.go index f3fa741b..20ea1552 100644 --- a/frac/sealed/lids/iterator_desc.go +++ b/frac/sealed/lids/iterator_desc.go @@ -1,11 +1,13 @@ package lids import ( + "math" "sort" "go.uber.org/zap" "github.com/ozontech/seq-db/logger" + "github.com/ozontech/seq-db/node" ) type IteratorDesc Cursor @@ -55,10 +57,10 @@ func (it *IteratorDesc) loadNextLIDsBlock() { it.blockIndex++ } -func (it *IteratorDesc) Next() (uint32, bool) { +func (it *IteratorDesc) Next() node.CmpLID { for len(it.lids) == 0 { if !it.tryNextBlock { - return 0, false + return node.NewCmpLIDOrderDesc(math.MaxUint32) } it.loadNextLIDsBlock() // last chunk in block but not last for tid; need load next block @@ -68,5 +70,5 @@ func (it *IteratorDesc) Next() (uint32, bool) { lid := it.lids[0] it.lids = it.lids[1:] - return lid, true + return node.NewCmpLIDOrderDesc(lid) } diff --git a/node/bench_test.go b/node/bench_test.go index 6f2c5b7c..8ed51a05 100644 --- a/node/bench_test.go +++ b/node/bench_test.go @@ -65,7 +65,7 @@ func BenchmarkOr(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) + n := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) for b.Loop() { res = readAllInto(n, res) @@ -82,7 +82,7 @@ func BenchmarkAnd(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s) - n := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + n := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) for b.Loop() { res = readAllInto(n, res) @@ -99,7 +99,7 @@ func BenchmarkNAnd(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s) - n := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) + n := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s)) for b.Loop() { res = readAllInto(n, res) @@ -115,13 +115,13 @@ func BenchmarkAndTree(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) - n2 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) - n3 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) - n4 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) - n12 := NewAnd(n1, n2, false) - n34 := NewAnd(n3, n4, false) - n := NewAnd(n12, n34, false) + n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n2 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n3 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n4 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n12 := NewAnd(n1, n2) + n34 := NewAnd(n3, n4) + n := NewAnd(n12, n34) res := make([]uint32, 0, s) for b.Loop() { @@ -138,13 +138,13 @@ func BenchmarkOrTree(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - n1 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) - n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) - n3 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) - n4 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) - n12 := NewOr(n1, n2, false) - n34 := NewOr(n3, n4, false) - n := NewOr(n12, n34, false) + n1 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n3 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n4 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n12 := NewOr(n1, n2) + n34 := NewOr(n3, n4) + n := NewOr(n12, n34) res := make([]uint32, 0, s*8) for b.Loop() { @@ -163,11 +163,11 @@ func BenchmarkComplex(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) - n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s), false) - n3 := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s), false) - n12 := NewOr(n1, n2, false) - n := NewAnd(n12, n3, false) + n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n3 := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n12 := NewOr(n1, n2) + n := NewAnd(n12, n3) for b.Loop() { res = readAllInto(n, res) diff --git a/node/builder.go b/node/builder.go index d0cd8906..69c4cb6e 100644 --- a/node/builder.go +++ b/node/builder.go @@ -5,9 +5,9 @@ var ( emptyNodeSourced = NewSourcedNodeWrapper(emptyNode, 0) ) -func BuildORTree(nodes []Node, reverse bool) Node { +func BuildORTree(nodes []Node) Node { return TreeFold( - func(l, r Node) Node { return NewOr(l, r, reverse) }, + func(l, r Node) Node { return NewOr(l, r) }, emptyNode, nodes, ) diff --git a/node/cmp_lid.go b/node/cmp_lid.go new file mode 100644 index 00000000..fd381a2d --- /dev/null +++ b/node/cmp_lid.go @@ -0,0 +1,59 @@ +package node + +import "math" + +// CmpLID is an encoded representation of LID and reverse flag made specifically for fast compare operations. +// +// For reverse order LID is inverted as follows: "MaxUint32 - LID" formula using XOR mask. Terminal LID value is 0 instead +// of MaxUint32 in reverse order, but 0 is XORed to MaxUint32. Which means, null value will always have lid field set to +// 0xFFFFFFFF (math.MaxUint32) regardless of reverse (order) flag. +type CmpLID struct { + lid uint32 // do not read this field, use Unpack instead + mask uint32 +} + +func NullCmpLID() CmpLID { + // reverse flag does not matter, as null values are never unpacked + return NewCmpLID(math.MaxUint32, false) +} + +// NewCmpLIDOrderDesc returns LIDs for desc sort order +func NewCmpLIDOrderDesc(lid uint32) CmpLID { + return CmpLID{ + lid: lid, + mask: uint32(0), + } +} + +// NewCmpLIDOrderAsc returns LIDs for asc sort order +func NewCmpLIDOrderAsc(lid uint32) CmpLID { + mask := uint32(0xFFFFFFFF) + return CmpLID{ + lid: lid ^ mask, + mask: mask, + } +} + +func NewCmpLID(lid uint32, reverse bool) CmpLID { + if reverse { + return NewCmpLIDOrderAsc(lid) + } else { + return NewCmpLIDOrderDesc(lid) + } +} + +func (c CmpLID) Less(other CmpLID) bool { + return c.lid < other.lid +} + +func (c CmpLID) Eq(other CmpLID) bool { + return c.lid == other.lid +} + +func (c CmpLID) Unpack() uint32 { + return c.lid ^ c.mask +} + +func (c CmpLID) IsNull() bool { + return c.lid == math.MaxUint32 +} diff --git a/node/cmp_lid_test.go b/node/cmp_lid_test.go new file mode 100644 index 00000000..f612a1da --- /dev/null +++ b/node/cmp_lid_test.go @@ -0,0 +1,38 @@ +package node + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCmpLID_ConvertionDesc(t *testing.T) { + x := NewCmpLIDOrderDesc(5) + assert.Equal(t, uint32(5), x.Unpack()) + + x = NewCmpLIDOrderDesc(math.MaxUint32) + assert.Equal(t, uint32(math.MaxUint32), x.Unpack()) + + x = NewCmpLIDOrderDesc(0) + assert.Equal(t, uint32(0), x.Unpack()) + + x = NullCmpLID() + + assert.True(t, x.IsNull()) +} + +func TestCmpLID_ConvertionAsc(t *testing.T) { + x := NewCmpLIDOrderAsc(5) + assert.Equal(t, uint32(5), x.Unpack()) + + x = NewCmpLIDOrderAsc(0) + assert.Equal(t, uint32(0), x.Unpack()) + + x = NewCmpLIDOrderAsc(math.MaxUint32) + assert.Equal(t, uint32(math.MaxUint32), x.Unpack()) + + x = NullCmpLID() + + assert.True(t, x.IsNull()) +} diff --git a/node/less_fn.go b/node/less_fn.go deleted file mode 100644 index afe2f981..00000000 --- a/node/less_fn.go +++ /dev/null @@ -1,13 +0,0 @@ -package node - -type LessFn func(uint32, uint32) bool - -var lessAsc LessFn = func(u1, u2 uint32) bool { return u1 < u2 } -var lessDesc LessFn = func(u1, u2 uint32) bool { return u2 < u1 } - -func GetLessFn(reverse bool) LessFn { - if reverse { - return lessDesc - } - return lessAsc -} diff --git a/node/node.go b/node/node.go index e6525b45..4f5bc0b2 100644 --- a/node/node.go +++ b/node/node.go @@ -6,11 +6,11 @@ import ( type Node interface { fmt.Stringer // for testing - Next() (id uint32, has bool) + Next() CmpLID } type Sourced interface { fmt.Stringer // for testing // aggregation need source - NextSourced() (id uint32, source uint32, has bool) + NextSourced() (id CmpLID, source uint32) } diff --git a/node/node_and.go b/node/node_and.go index 58bc9391..842965d9 100644 --- a/node/node_and.go +++ b/node/node_and.go @@ -1,57 +1,50 @@ package node -import "fmt" +import ( + "fmt" +) type nodeAnd struct { - less LessFn - left Node right Node - leftID uint32 - hasLeft bool - rightID uint32 - hasRight bool + leftID CmpLID + rightID CmpLID } func (n *nodeAnd) String() string { return fmt.Sprintf("(%s AND %s)", n.left.String(), n.right.String()) } -func NewAnd(left, right Node, reverse bool) *nodeAnd { - node := &nodeAnd{ - less: GetLessFn(reverse), - - left: left, - right: right, - } +func NewAnd(left, right Node) *nodeAnd { + node := &nodeAnd{left: left, right: right} node.readLeft() node.readRight() return node } func (n *nodeAnd) readLeft() { - n.leftID, n.hasLeft = n.left.Next() + n.leftID = n.left.Next() } func (n *nodeAnd) readRight() { - n.rightID, n.hasRight = n.right.Next() + n.rightID = n.right.Next() } -func (n *nodeAnd) Next() (uint32, bool) { - for n.hasLeft && n.hasRight && n.leftID != n.rightID { - for n.hasLeft && n.hasRight && n.less(n.leftID, n.rightID) { +func (n *nodeAnd) Next() CmpLID { + for !n.leftID.IsNull() && !n.rightID.IsNull() && n.leftID.Unpack() != n.rightID.Unpack() { + for !n.leftID.IsNull() && !n.rightID.IsNull() && n.leftID.Less(n.rightID) { n.readLeft() } - for n.hasLeft && n.hasRight && n.less(n.rightID, n.leftID) { + for !n.leftID.IsNull() && !n.rightID.IsNull() && n.rightID.Less(n.leftID) { n.readRight() } } - if !n.hasLeft || !n.hasRight { - return 0, false + if n.leftID.IsNull() || n.rightID.IsNull() { + return NullCmpLID() } cur := n.leftID n.readLeft() n.readRight() - return cur, true + return cur } diff --git a/node/node_nand.go b/node/node_nand.go index 7f7b7a89..f1fd5b39 100644 --- a/node/node_nand.go +++ b/node/node_nand.go @@ -3,52 +3,43 @@ package node import "fmt" type nodeNAnd struct { - less LessFn + neg Node + reg Node - neg Node - negID uint32 - hasNeg bool - - reg Node - regID uint32 - hasReg bool + negCmp CmpLID + regCmp CmpLID } func (n *nodeNAnd) String() string { return fmt.Sprintf("(%s NAND %s)", n.neg.String(), n.reg.String()) } -func NewNAnd(negative, regular Node, reverse bool) *nodeNAnd { - node := &nodeNAnd{ - less: GetLessFn(reverse), - - neg: negative, - reg: regular, - } +func NewNAnd(negative, regular Node) *nodeNAnd { + node := &nodeNAnd{neg: negative, reg: regular} node.readNeg() node.readReg() return node } func (n *nodeNAnd) readNeg() { - n.negID, n.hasNeg = n.neg.Next() + n.negCmp = n.neg.Next() } func (n *nodeNAnd) readReg() { - n.regID, n.hasReg = n.reg.Next() + n.regCmp = n.reg.Next() } -func (n *nodeNAnd) Next() (uint32, bool) { - for n.hasReg { - for n.hasNeg && n.less(n.negID, n.regID) { +func (n *nodeNAnd) Next() CmpLID { + for !n.regCmp.IsNull() { + for !n.negCmp.IsNull() && n.negCmp.Less(n.regCmp) { n.readNeg() } - if !n.hasNeg || n.negID != n.regID { // i.e. n.negID > regID - cur := n.regID + if n.negCmp.IsNull() || !n.negCmp.Eq(n.regCmp) { + cur := n.regCmp n.readReg() - return cur, true + return cur } n.readReg() } - return 0, false + return NullCmpLID() } diff --git a/node/node_not.go b/node/node_not.go index 098f7f1e..17cb87a1 100644 --- a/node/node_not.go +++ b/node/node_not.go @@ -12,6 +12,6 @@ func (n *nodeNot) String() string { func NewNot(child Node, minVal, maxVal uint32, reverse bool) *nodeNot { nodeRange := NewRange(minVal, maxVal, reverse) - nodeNAnd := NewNAnd(child, nodeRange, reverse) + nodeNAnd := NewNAnd(child, nodeRange) return &nodeNot{nodeNAnd: *(nodeNAnd)} } diff --git a/node/node_or.go b/node/node_or.go index 1cfec7ac..4f79a932 100644 --- a/node/node_or.go +++ b/node/node_or.go @@ -3,78 +3,64 @@ package node import "fmt" type nodeOr struct { - less LessFn - left Node right Node - leftID uint32 - hasLeft bool - rightID uint32 - hasRight bool + leftID CmpLID + rightID CmpLID } func (n *nodeOr) String() string { return fmt.Sprintf("(%s OR %s)", n.left.String(), n.right.String()) } -func NewOr(left, right Node, reverse bool) *nodeOr { - n := &nodeOr{ - less: GetLessFn(reverse), - - left: left, - right: right, - } +func NewOr(left, right Node) *nodeOr { + n := &nodeOr{left: left, right: right} n.readLeft() n.readRight() return n } func (n *nodeOr) readLeft() { - n.leftID, n.hasLeft = n.left.Next() + n.leftID = n.left.Next() } func (n *nodeOr) readRight() { - n.rightID, n.hasRight = n.right.Next() + n.rightID = n.right.Next() } -func (n *nodeOr) Next() (uint32, bool) { - if !n.hasLeft && !n.hasRight { - return 0, false +func (n *nodeOr) Next() CmpLID { + if n.leftID.IsNull() && n.rightID.IsNull() { + return n.leftID } - if n.hasLeft && (!n.hasRight || n.less(n.leftID, n.rightID)) { + if !n.leftID.IsNull() && (n.rightID.IsNull() || n.leftID.Less(n.rightID)) { cur := n.leftID n.readLeft() - return cur, true + return cur } - - if n.hasRight && (!n.hasLeft || n.less(n.rightID, n.leftID)) { + if !n.rightID.IsNull() && (n.leftID.IsNull() || n.rightID.Less(n.leftID)) { cur := n.rightID n.readRight() - return cur, true + return cur } - cur := n.leftID n.readLeft() n.readRight() - - return cur, true + return cur } type nodeOrAgg struct { + reverse bool + left Sourced right Sourced - leftID uint32 + leftID CmpLID leftSource uint32 - hasLeft bool - rightID uint32 + rightID CmpLID rightSource uint32 - hasRight bool - - less LessFn } func (n *nodeOrAgg) String() string { @@ -82,41 +68,32 @@ func (n *nodeOrAgg) String() string { } func NewNodeOrAgg(left, right Sourced, reverse bool) Sourced { - n := &nodeOrAgg{ - left: left, - right: right, - less: GetLessFn(reverse), - } + n := &nodeOrAgg{reverse: reverse, left: left, right: right} n.readLeft() n.readRight() return n } func (n *nodeOrAgg) readLeft() { - n.leftID, n.leftSource, n.hasLeft = n.left.NextSourced() + n.leftID, n.leftSource = n.left.NextSourced() } func (n *nodeOrAgg) readRight() { - n.rightID, n.rightSource, n.hasRight = n.right.NextSourced() + n.rightID, n.rightSource = n.right.NextSourced() } -func (n *nodeOrAgg) NextSourced() (uint32, uint32, bool) { - if !n.hasLeft && !n.hasRight { - return 0, 0, false +func (n *nodeOrAgg) NextSourced() (CmpLID, uint32) { + if n.leftID.IsNull() && n.rightID.IsNull() { + return n.leftID, 0 } - - if n.hasLeft && (!n.hasRight || n.less(n.leftID, n.rightID)) { + if !n.leftID.IsNull() && (n.rightID.IsNull() || n.leftID.Less(n.rightID)) { cur := n.leftID curSource := n.leftSource n.readLeft() - - return cur, curSource, true + return cur, curSource } - - // we don't need deduplication cur := n.rightID curSource := n.rightSource n.readRight() - - return cur, curSource, true + return cur, curSource } diff --git a/node/node_range.go b/node/node_range.go index ed5d0486..52498450 100644 --- a/node/node_range.go +++ b/node/node_range.go @@ -1,7 +1,7 @@ package node type nodeRange struct { - less LessFn + reverse bool maxVal uint32 cur int @@ -19,19 +19,20 @@ func NewRange(minVal, maxVal uint32, reverse bool) *nodeRange { minVal, maxVal = maxVal, minVal } return &nodeRange{ - less: GetLessFn(reverse), - - cur: int(minVal), - maxVal: maxVal, - step: step, + reverse: reverse, + cur: int(minVal), + maxVal: maxVal, + step: step, } } -func (n *nodeRange) Next() (uint32, bool) { - if n.less(n.maxVal, uint32(n.cur)) { - return 0, false +func (n *nodeRange) Next() CmpLID { + curCmp := NewCmpLID(uint32(n.cur), n.reverse) + maxCmp := NewCmpLID(n.maxVal, n.reverse) + if maxCmp.Less(curCmp) { + return NullCmpLID() } cur := uint32(n.cur) n.cur += n.step - return cur, true + return NewCmpLID(cur, n.reverse) } diff --git a/node/node_static.go b/node/node_static.go index d40135d2..a6dabaf4 100644 --- a/node/node_static.go +++ b/node/node_static.go @@ -1,5 +1,7 @@ package node +import "math" + type staticCursor struct { ptr int data []uint32 @@ -31,22 +33,24 @@ func NewStatic(data []uint32, reverse bool) Node { }} } -func (n *staticAsc) Next() (uint32, bool) { +func (n *staticAsc) Next() CmpLID { + // staticAsc is used in docs order desc, hence we return CmpLID with desc order if n.ptr >= len(n.data) { - return 0, false + return NewCmpLIDOrderDesc(math.MaxUint32) } cur := n.data[n.ptr] n.ptr++ - return cur, true + return NewCmpLIDOrderDesc(cur) } -func (n *staticDesc) Next() (uint32, bool) { +func (n *staticDesc) Next() CmpLID { + // staticDesc is used in docs order asc, hence we return CmpLID with asc order if n.ptr < 0 { - return 0, false + return NewCmpLIDOrderAsc(0) } cur := n.data[n.ptr] n.ptr-- - return cur, true + return NewCmpLIDOrderAsc(cur) } // MakeStaticNodes is currently used only for tests diff --git a/node/node_test.go b/node/node_test.go index 6da2d8a1..e72a03a4 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -8,10 +8,8 @@ import ( ) func readAllInto(node Node, ids []uint32) []uint32 { - id, has := node.Next() - for has { - ids = append(ids, id) - id, has = node.Next() + for cur := node.Next(); !cur.IsNull(); cur = node.Next() { + ids = append(ids, cur.Unpack()) } return ids } @@ -35,19 +33,19 @@ var ( func TestNodeAnd(t *testing.T) { expect := []uint32{5, 6, 13} - and := NewAnd(NewStatic(data[0], false), NewStatic(data[1], false), false) + and := NewAnd(NewStatic(data[0], false), NewStatic(data[1], false)) assert.Equal(t, expect, readAll(and)) } func TestNodeOr(t *testing.T) { expect := []uint32{1, 2, 3, 5, 6, 7, 8, 9, 13, 14} - or := NewOr(NewStatic(data[0], false), NewStatic(data[1], false), false) + or := NewOr(NewStatic(data[0], false), NewStatic(data[1], false)) assert.Equal(t, expect, readAll(or)) } func TestNodeNAnd(t *testing.T) { expect := []uint32{2, 3, 14} - nand := NewNAnd(NewStatic(data[0], false), NewStatic(data[1], false), false) + nand := NewNAnd(NewStatic(data[0], false), NewStatic(data[1], false)) assert.Equal(t, expect, readAll(nand)) } @@ -61,7 +59,7 @@ func TestNodeNot(t *testing.T) { func TestNodeLazyAnd(t *testing.T) { left := []uint32{1, 2} right := []uint32{1, 2, 3, 4, 5, 6} - and := NewAnd(NewStatic(left, false), NewStatic(right, false), false) + and := NewAnd(NewStatic(left, false), NewStatic(right, false)) assert.Equal(t, []uint32{1, 2}, readAll(and)) assert.Equal(t, []uint32{4, 5, 6}, getRemainingSlice(t, and.right)) assert.Equal(t, []uint32(nil), readAll(and)) @@ -72,7 +70,7 @@ func TestNodeLazyAnd(t *testing.T) { func TestNodeLazyNAnd(t *testing.T) { left := []uint32{1, 2, 5, 6, 7, 8} right := []uint32{2, 4} - nand := NewNAnd(NewStatic(left, false), NewStatic(right, false), false) + nand := NewNAnd(NewStatic(left, false), NewStatic(right, false)) assert.Equal(t, []uint32{4}, readAll(nand)) assert.Equal(t, []uint32{6, 7, 8}, getRemainingSlice(t, nand.neg)) assert.Equal(t, []uint32(nil), readAll(nand)) @@ -92,44 +90,44 @@ func isEmptyNode(node any) bool { func TestNodeTreeBuilding(t *testing.T) { t.Run("size_0", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 0)) - assert.True(t, isEmptyNode(BuildORTree(dn, false)), "expected empty node") + assert.True(t, isEmptyNode(BuildORTree(dn)), "expected empty node") assert.True(t, isEmptyNode(BuildORTreeAgg(dn, false)), "expected empty node") }) t.Run("size_1", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 1)) - assert.Equal(t, "STATIC", BuildORTree(dn, false).String()) + assert.Equal(t, "STATIC", BuildORTree(dn).String()) assert.Equal(t, "SOURCED", BuildORTreeAgg(dn, false).String()) }) t.Run("size_2", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 2)) - assert.Equal(t, "(STATIC OR STATIC)", BuildORTree(dn, false).String()) + assert.Equal(t, "(STATIC OR STATIC)", BuildORTree(dn).String()) assert.Equal(t, "(SOURCED OR SOURCED)", BuildORTreeAgg(dn, false).String()) }) t.Run("size_3", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 3)) - assert.Equal(t, "(STATIC OR (STATIC OR STATIC))", BuildORTree(dn, false).String()) + assert.Equal(t, "(STATIC OR (STATIC OR STATIC))", BuildORTree(dn).String()) assert.Equal(t, "(SOURCED OR (SOURCED OR SOURCED))", BuildORTreeAgg(dn, false).String()) }) t.Run("size_4", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 4)) - assert.Equal(t, "((STATIC OR STATIC) OR (STATIC OR STATIC))", BuildORTree(dn, false).String()) + assert.Equal(t, "((STATIC OR STATIC) OR (STATIC OR STATIC))", BuildORTree(dn).String()) assert.Equal(t, "((SOURCED OR SOURCED) OR (SOURCED OR SOURCED))", BuildORTreeAgg(dn, false).String()) }) t.Run("size_5", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 5)) - assert.Equal(t, "((STATIC OR STATIC) OR (STATIC OR (STATIC OR STATIC)))", BuildORTree(dn, false).String()) + assert.Equal(t, "((STATIC OR STATIC) OR (STATIC OR (STATIC OR STATIC)))", BuildORTree(dn).String()) assert.Equal(t, "((SOURCED OR SOURCED) OR (SOURCED OR (SOURCED OR SOURCED)))", BuildORTreeAgg(dn, false).String()) }) t.Run("size_6", func(t *testing.T) { - labels := BuildORTree(MakeStaticNodes(make([][]uint32, 6)), false).String() + labels := BuildORTree(MakeStaticNodes(make([][]uint32, 6))).String() assert.Equal(t, "((STATIC OR (STATIC OR STATIC)) OR (STATIC OR (STATIC OR STATIC)))", labels) }) t.Run("size_7", func(t *testing.T) { - labels := BuildORTree(MakeStaticNodes(make([][]uint32, 7)), false).String() + labels := BuildORTree(MakeStaticNodes(make([][]uint32, 7))).String() assert.Equal(t, "((STATIC OR (STATIC OR STATIC)) OR ((STATIC OR STATIC) OR (STATIC OR STATIC)))", labels) }) t.Run("size_8", func(t *testing.T) { - labels := BuildORTree(MakeStaticNodes(make([][]uint32, 8)), false).String() + labels := BuildORTree(MakeStaticNodes(make([][]uint32, 8))).String() assert.Equal(t, "(((STATIC OR STATIC) OR (STATIC OR STATIC)) OR ((STATIC OR STATIC) OR (STATIC OR STATIC)))", labels) }) } diff --git a/node/sourced_node_wrapper.go b/node/sourced_node_wrapper.go index 369e2eed..0023f4ac 100644 --- a/node/sourced_node_wrapper.go +++ b/node/sourced_node_wrapper.go @@ -9,9 +9,9 @@ func (*sourcedNodeWrapper) String() string { return "SOURCED" } -func (w *sourcedNodeWrapper) NextSourced() (uint32, uint32, bool) { - id, has := w.node.Next() - return id, w.source, has +func (w *sourcedNodeWrapper) NextSourced() (CmpLID, uint32) { + cmp := w.node.Next() + return cmp, w.source } func NewSourcedNodeWrapper(d Node, source int) Sourced { From c4d671e832bf2565ef3d31730809479b606c6285 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:38:58 +0400 Subject: [PATCH 02/20] add tests --- frac/fraction_test.go | 27 +++++++++++++++++++++++++-- node/cmp_lid_test.go | 29 +++++++++++++++++++++++++++-- node/node_nand.go | 16 ++++++++-------- node/node_range.go | 6 +++--- 4 files changed, 63 insertions(+), 15 deletions(-) diff --git a/frac/fraction_test.go b/frac/fraction_test.go index d8de64f1..a29faccf 100644 --- a/frac/fraction_test.go +++ b/frac/fraction_test.go @@ -1284,7 +1284,6 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { fromTime: fromTime, toTime: toTime, }, - // OR operator queries { name: "trace_id OR", @@ -1300,7 +1299,7 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { toTime: toTime, }, - // mixed AND/OR + // mixed AND/OR/NOT { name: "message:request AND (level:1 OR level:3 OR level:5) AND trace_id:trace-2*", query: "message:request AND (level:1 OR level:3 OR level:5) AND trace_id:trace-2*", @@ -1323,6 +1322,30 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { fromTime: fromTime, toTime: toTime, }, + { + name: "service:gateway AND NOT (message:request OR message:timed OR level:[0 to 3])", + query: "service:gateway AND NOT (message:request OR message:timed OR level:[0 to 3])", + filter: func(doc *testDoc) bool { + return doc.service == "gateway" && + !(strings.Contains(doc.message, "request") || + strings.Contains(doc.message, "timed") || + (doc.level >= 0 && doc.level <= 3)) + }, + fromTime: fromTime, + toTime: midTime, + }, + { + name: "service:proxy AND NOT level:5 AND NOT pod:pod-2* AND NOT client_ip:ip_range(192.168.19.0,192.168.19.255)", + query: "service:proxy AND NOT level:5 AND NOT pod:pod-2* AND NOT client_ip:ip_range(192.168.19.0,192.168.19.255)", + filter: func(doc *testDoc) bool { + return doc.service == "proxy" && + doc.level != 5 && + !strings.Contains(doc.pod, "pod-2") && + !strings.Contains(doc.clientIp, "192.168.19") + }, + fromTime: fromTime, + toTime: midTime, + }, // other queries { diff --git a/node/cmp_lid_test.go b/node/cmp_lid_test.go index f612a1da..0d0b436c 100644 --- a/node/cmp_lid_test.go +++ b/node/cmp_lid_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCmpLID_ConvertionDesc(t *testing.T) { +func TestCmpLID_Unpack_Desc(t *testing.T) { x := NewCmpLIDOrderDesc(5) assert.Equal(t, uint32(5), x.Unpack()) @@ -22,7 +22,7 @@ func TestCmpLID_ConvertionDesc(t *testing.T) { assert.True(t, x.IsNull()) } -func TestCmpLID_ConvertionAsc(t *testing.T) { +func TestCmpLID_Unpack_Asc(t *testing.T) { x := NewCmpLIDOrderAsc(5) assert.Equal(t, uint32(5), x.Unpack()) @@ -36,3 +36,28 @@ func TestCmpLID_ConvertionAsc(t *testing.T) { assert.True(t, x.IsNull()) } + +func TestCmpLID_Eq(t *testing.T) { + assert.Equal(t, NewCmpLIDOrderDesc(6), NewCmpLIDOrderDesc(6)) + assert.Equal(t, NewCmpLIDOrderDesc(math.MaxUint32), NewCmpLIDOrderDesc(math.MaxUint32)) + + assert.Equal(t, NewCmpLIDOrderAsc(6), NewCmpLIDOrderAsc(6)) + assert.Equal(t, NewCmpLIDOrderAsc(0), NewCmpLIDOrderAsc(0)) +} + +func TestCmpLID_Less_Desc(t *testing.T) { + assert.False(t, NewCmpLIDOrderDesc(6).Less(NewCmpLIDOrderDesc(6))) + assert.True(t, NewCmpLIDOrderDesc(6).Less(NewCmpLIDOrderDesc(7))) + assert.True(t, NewCmpLIDOrderDesc(0).Less(NewCmpLIDOrderDesc(5))) + + assert.True(t, NewCmpLIDOrderDesc(56000).Less(NullCmpLID())) +} + +func TestCmpLID_Less_Asc(t *testing.T) { + // for asc sort order larger values go first (order is reversed), i.e. greater values are "less" than lower values + assert.False(t, NewCmpLIDOrderAsc(6).Less(NewCmpLIDOrderAsc(6))) + assert.True(t, NewCmpLIDOrderAsc(10).Less(NewCmpLIDOrderAsc(1))) + assert.True(t, NewCmpLIDOrderAsc(5).Less(NewCmpLIDOrderAsc(0))) + + assert.True(t, NewCmpLIDOrderAsc(56000).Less(NullCmpLID())) +} diff --git a/node/node_nand.go b/node/node_nand.go index f1fd5b39..0acdb3e0 100644 --- a/node/node_nand.go +++ b/node/node_nand.go @@ -6,8 +6,8 @@ type nodeNAnd struct { neg Node reg Node - negCmp CmpLID - regCmp CmpLID + negID CmpLID + regID CmpLID } func (n *nodeNAnd) String() string { @@ -22,20 +22,20 @@ func NewNAnd(negative, regular Node) *nodeNAnd { } func (n *nodeNAnd) readNeg() { - n.negCmp = n.neg.Next() + n.negID = n.neg.Next() } func (n *nodeNAnd) readReg() { - n.regCmp = n.reg.Next() + n.regID = n.reg.Next() } func (n *nodeNAnd) Next() CmpLID { - for !n.regCmp.IsNull() { - for !n.negCmp.IsNull() && n.negCmp.Less(n.regCmp) { + for !n.regID.IsNull() { + for !n.negID.IsNull() && n.negID.Less(n.regID) { n.readNeg() } - if n.negCmp.IsNull() || !n.negCmp.Eq(n.regCmp) { - cur := n.regCmp + if n.negID.IsNull() || !n.negID.Eq(n.regID) { + cur := n.regID n.readReg() return cur } diff --git a/node/node_range.go b/node/node_range.go index 52498450..6b914107 100644 --- a/node/node_range.go +++ b/node/node_range.go @@ -27,9 +27,9 @@ func NewRange(minVal, maxVal uint32, reverse bool) *nodeRange { } func (n *nodeRange) Next() CmpLID { - curCmp := NewCmpLID(uint32(n.cur), n.reverse) - maxCmp := NewCmpLID(n.maxVal, n.reverse) - if maxCmp.Less(curCmp) { + curID := NewCmpLID(uint32(n.cur), n.reverse) + maxID := NewCmpLID(n.maxVal, n.reverse) + if maxID.Less(curID) { return NullCmpLID() } cur := uint32(n.cur) From ad98152f97e11a957389352ef2077f57dd1a9b0d Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Tue, 17 Feb 2026 17:07:26 +0400 Subject: [PATCH 03/20] remove reverse flag from node_range.go, delete unneeded null checks --- frac/fraction_test.go | 14 +++++++++++ frac/processor/eval_tree.go | 11 +++++++- node/bench_test.go | 4 +-- node/cmp_lid.go | 8 ++++++ node/node_and.go | 6 ++--- node/node_not.go | 4 +-- node/node_or.go | 6 ++--- node/node_range.go | 30 +++++++--------------- node/node_test.go | 50 ++++++++++++++++++++++++++++++++++++- 9 files changed, 100 insertions(+), 33 deletions(-) diff --git a/frac/fraction_test.go b/frac/fraction_test.go index a29faccf..b7725377 100644 --- a/frac/fraction_test.go +++ b/frac/fraction_test.go @@ -1200,6 +1200,20 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { fromTime: fromTime, toTime: toTime, }, + { + name: "NOT service:bus", + query: "NOT service:bus", + filter: func(doc *testDoc) bool { return doc.service != "bus" }, + fromTime: fromTime, + toTime: toTime, + }, + { + name: "NOT service:bus (time range)", + query: "NOT service:bus", + filter: func(doc *testDoc) bool { return doc.service != "bus" }, + fromTime: fromTime, + toTime: midTime, + }, { name: "service:proxy (time range)", query: "service:proxy", diff --git a/frac/processor/eval_tree.go b/frac/processor/eval_tree.go index 8c9ca326..b75d465b 100644 --- a/frac/processor/eval_tree.go +++ b/frac/processor/eval_tree.go @@ -43,7 +43,16 @@ func buildEvalTree(root *parser.ASTNode, minVal, maxVal uint32, stats *searchSta return node.NewNAnd(children[0], children[1]), nil case parser.LogicalNot: stats.NodesTotal++ - return node.NewNot(children[0], minVal, maxVal, reverse), nil + var minCmpLID node.CmpLID + var maxCmpLID node.CmpLID + if reverse { + minCmpLID = node.NewCmpLIDOrderAsc(maxVal) + maxCmpLID = node.NewCmpLIDOrderAsc(minVal) + } else { + minCmpLID = node.NewCmpLIDOrderDesc(minVal) + maxCmpLID = node.NewCmpLIDOrderDesc(maxVal) + } + return node.NewNot(children[0], minCmpLID, maxCmpLID), nil } } return nil, fmt.Errorf("unknown token type") diff --git a/node/bench_test.go b/node/bench_test.go index 8ed51a05..9f15db1f 100644 --- a/node/bench_test.go +++ b/node/bench_test.go @@ -30,7 +30,7 @@ func BenchmarkNot(b *testing.B) { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { v, last := Generate(s) res := make([]uint32, 0, last+1) - n := NewNot(NewStatic(v, false), 1, last, false) + n := NewNot(NewStatic(v, false), NewCmpLIDOrderDesc(1), NewCmpLIDOrderDesc(last)) for b.Loop() { res = readAllInto(n, res) @@ -47,7 +47,7 @@ func BenchmarkNotEmpty(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n := NewNot(NewStatic(nil, false), 1, uint32(s), false) + n := NewNot(NewStatic(nil, false), NewCmpLIDOrderDesc(1), NewCmpLIDOrderDesc(uint32(s))) for b.Loop() { res = readAllInto(n, res) diff --git a/node/cmp_lid.go b/node/cmp_lid.go index fd381a2d..20bd46e7 100644 --- a/node/cmp_lid.go +++ b/node/cmp_lid.go @@ -42,10 +42,18 @@ func NewCmpLID(lid uint32, reverse bool) CmpLID { } } +// Less compares two values. It also does an implicit null check, since we store math.MaxUint32 for null values. +// Which means if we call x.Less(y), then we now for sure that x is not null. Therefore, this Less call can work +// as both "null check + less" combo. func (c CmpLID) Less(other CmpLID) bool { return c.lid < other.lid } +func (c CmpLID) Inc() CmpLID { + c.lid++ + return c +} + func (c CmpLID) Eq(other CmpLID) bool { return c.lid == other.lid } diff --git a/node/node_and.go b/node/node_and.go index 842965d9..1ecc454b 100644 --- a/node/node_and.go +++ b/node/node_and.go @@ -32,11 +32,11 @@ func (n *nodeAnd) readRight() { } func (n *nodeAnd) Next() CmpLID { - for !n.leftID.IsNull() && !n.rightID.IsNull() && n.leftID.Unpack() != n.rightID.Unpack() { - for !n.leftID.IsNull() && !n.rightID.IsNull() && n.leftID.Less(n.rightID) { + for !n.leftID.IsNull() && !n.rightID.IsNull() && !n.leftID.Eq(n.rightID) { + for !n.rightID.IsNull() && n.leftID.Less(n.rightID) { n.readLeft() } - for !n.leftID.IsNull() && !n.rightID.IsNull() && n.rightID.Less(n.leftID) { + for !n.rightID.IsNull() && n.rightID.Less(n.leftID) { n.readRight() } } diff --git a/node/node_not.go b/node/node_not.go index 17cb87a1..17415c90 100644 --- a/node/node_not.go +++ b/node/node_not.go @@ -10,8 +10,8 @@ func (n *nodeNot) String() string { return fmt.Sprintf("(NOT %s)", n.neg.String()) } -func NewNot(child Node, minVal, maxVal uint32, reverse bool) *nodeNot { - nodeRange := NewRange(minVal, maxVal, reverse) +func NewNot(child Node, minID, maxID CmpLID) *nodeNot { + nodeRange := NewRange(minID, maxID) nodeNAnd := NewNAnd(child, nodeRange) return &nodeNot{nodeNAnd: *(nodeNAnd)} } diff --git a/node/node_or.go b/node/node_or.go index 4f79a932..31285d3b 100644 --- a/node/node_or.go +++ b/node/node_or.go @@ -34,12 +34,12 @@ func (n *nodeOr) Next() CmpLID { return n.leftID } - if !n.leftID.IsNull() && (n.rightID.IsNull() || n.leftID.Less(n.rightID)) { + if n.leftID.Less(n.rightID) { cur := n.leftID n.readLeft() return cur } - if !n.rightID.IsNull() && (n.leftID.IsNull() || n.rightID.Less(n.leftID)) { + if n.rightID.Less(n.leftID) { cur := n.rightID n.readRight() return cur @@ -86,7 +86,7 @@ func (n *nodeOrAgg) NextSourced() (CmpLID, uint32) { if n.leftID.IsNull() && n.rightID.IsNull() { return n.leftID, 0 } - if !n.leftID.IsNull() && (n.rightID.IsNull() || n.leftID.Less(n.rightID)) { + if n.leftID.Less(n.rightID) { cur := n.leftID curSource := n.leftSource n.readLeft() diff --git a/node/node_range.go b/node/node_range.go index 6b914107..3a4b70dd 100644 --- a/node/node_range.go +++ b/node/node_range.go @@ -1,38 +1,26 @@ package node type nodeRange struct { - reverse bool - - maxVal uint32 - cur int - step int + maxID CmpLID + curID CmpLID } func (n *nodeRange) String() string { return "(RANGE)" } -func NewRange(minVal, maxVal uint32, reverse bool) *nodeRange { - step := 1 - if reverse { - step = -1 - minVal, maxVal = maxVal, minVal - } +func NewRange(minVal, maxVal CmpLID) *nodeRange { return &nodeRange{ - reverse: reverse, - cur: int(minVal), - maxVal: maxVal, - step: step, + curID: minVal, + maxID: maxVal, } } func (n *nodeRange) Next() CmpLID { - curID := NewCmpLID(uint32(n.cur), n.reverse) - maxID := NewCmpLID(n.maxVal, n.reverse) - if maxID.Less(curID) { + if n.maxID.Less(n.curID) { return NullCmpLID() } - cur := uint32(n.cur) - n.cur += n.step - return NewCmpLID(cur, n.reverse) + result := n.curID + n.curID = n.curID.Inc() + return result } diff --git a/node/node_test.go b/node/node_test.go index e72a03a4..bd0a2f8f 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -49,9 +49,57 @@ func TestNodeNAnd(t *testing.T) { assert.Equal(t, expect, readAll(nand)) } +func TestNodeAndReverse(t *testing.T) { + expect := []uint32{13, 6, 5} + and := NewAnd(NewStatic(data[0], true), NewStatic(data[1], true)) + assert.Equal(t, expect, readAll(and)) +} + +func TestNodeOrReverse(t *testing.T) { + expect := []uint32{14, 13, 9, 8, 7, 6, 5, 3, 2, 1} + or := NewOr(NewStatic(data[0], true), NewStatic(data[1], true)) + assert.Equal(t, expect, readAll(or)) +} + +func TestNodeNAndReverse(t *testing.T) { + expect := []uint32{14, 3, 2} + nand := NewNAnd(NewStatic(data[0], true), NewStatic(data[1], true)) + assert.Equal(t, expect, readAll(nand)) +} + +func TestNodeNotReverse(t *testing.T) { + expect := []uint32{15, 12, 11, 10, 9, 8, 7, 4, 1} + not := NewNot(NewStatic(data[1], true), NewCmpLIDOrderAsc(15), NewCmpLIDOrderAsc(1)) + assert.Equal(t, expect, readAll(not)) +} + +func TestNodeRange(t *testing.T) { + expect := []uint32{3, 4, 5, 6, 7, 8, 9, 10} + not := NewRange(NewCmpLIDOrderDesc(3), NewCmpLIDOrderDesc(10)) + assert.Equal(t, expect, readAll(not)) +} + +func TestNodeRangeReverse(t *testing.T) { + expect := []uint32{10, 9, 8, 7, 6, 5, 4, 3} + not := NewRange(NewCmpLIDOrderAsc(10), NewCmpLIDOrderAsc(3)) + assert.Equal(t, expect, readAll(not)) +} + +func TestNodeNotPartialRange(t *testing.T) { + expect := []uint32{4, 7, 8, 9, 10} + not := NewNot(NewStatic(data[1], false), NewCmpLIDOrderDesc(3), NewCmpLIDOrderDesc(10)) + assert.Equal(t, expect, readAll(not)) +} + +func TestNodeNotPartialRangeReverse(t *testing.T) { + expect := []uint32{10, 9, 8, 7, 4} + not := NewNot(NewStatic(data[1], true), NewCmpLIDOrderAsc(10), NewCmpLIDOrderAsc(3)) + assert.Equal(t, expect, readAll(not)) +} + func TestNodeNot(t *testing.T) { expect := []uint32{1, 4, 7, 8, 9, 10, 11, 12, 15} - nand := NewNot(NewStatic(data[1], false), 1, 15, false) + nand := NewNot(NewStatic(data[1], false), NewCmpLIDOrderDesc(1), NewCmpLIDOrderDesc(15)) assert.Equal(t, expect, readAll(nand)) } From e9401ba7268ea32e2f2aae8c95a2b4ef3e977c5f Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:14:16 +0400 Subject: [PATCH 04/20] rebase onto fast cmp --- frac/processor/aggregator.go | 2 +- frac/processor/aggregator_test.go | 16 ++ frac/sealed/lids/iterator_asc.go | 31 ++++ frac/sealed/lids/iterator_desc.go | 32 ++++ node/bench_test.go | 81 ++++++++-- node/cmp_lid.go | 20 +++ node/node.go | 5 + node/node_and.go | 31 +++- node/node_and_test.go | 65 ++++++++ node/node_nand.go | 4 + node/node_or.go | 78 ++++++++- node/node_or_test.go | 257 ++++++++++++++++++++++++++++++ node/node_range.go | 4 + node/node_static.go | 42 ++++- node/node_static_test.go | 69 ++++++++ node/node_test.go | 9 ++ node/sourced_node_wrapper.go | 5 + util/algorithms.go | 49 ++++++ util/algorithms_test.go | 254 +++++++++++++++++++++++++++++ 19 files changed, 1033 insertions(+), 21 deletions(-) create mode 100644 node/node_and_test.go create mode 100644 node/node_or_test.go create mode 100644 node/node_static_test.go create mode 100644 util/algorithms.go create mode 100644 util/algorithms_test.go diff --git a/frac/processor/aggregator.go b/frac/processor/aggregator.go index ec5b0e94..317e91b9 100644 --- a/frac/processor/aggregator.go +++ b/frac/processor/aggregator.go @@ -404,7 +404,7 @@ func NewSourcedNodeIterator(sourced node.Sourced, ti tokenIndex, tids []uint32, func (s *SourcedNodeIterator) ConsumeTokenSource(lid node.CmpLID) (uint32, bool, error) { for !s.lastID.IsNull() && s.lastID.Less(lid) { - s.lastID, s.lastSource = s.sourcedNode.NextSourced() + s.lastID, s.lastSource = s.sourcedNode.NextSourcedGeq(lid) } exists := !s.lastID.IsNull() && s.lastID == lid diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index 6d7f375a..bd5e453e 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -166,6 +166,22 @@ func (m *MockNode) String() string { return reflect.TypeOf(m).String() } +/*func (m *MockNode) NextSourced() (uint32, uint32, bool) { + return m.NextSourcedGeq(0) +} + +func (m *MockNode) NextSourcedGeq(minLID uint32) (uint32, uint32, bool) { + for len(m.Pairs) > 0 && m.Pairs[0].LID < minLID { + m.Pairs = m.Pairs[1:] + } + if len(m.Pairs) == 0 { + return 0, 0, false + } + first := m.Pairs[0] + m.Pairs = m.Pairs[1:] + return first.LID, first.Source, true +}*/ + func (m *MockNode) NextSourced() (node.CmpLID, uint32) { if len(m.Pairs) == 0 { return node.NullCmpLID(), 0 diff --git a/frac/sealed/lids/iterator_asc.go b/frac/sealed/lids/iterator_asc.go index 368adc65..44bfbab2 100644 --- a/frac/sealed/lids/iterator_asc.go +++ b/frac/sealed/lids/iterator_asc.go @@ -7,6 +7,7 @@ import ( "github.com/ozontech/seq-db/logger" "github.com/ozontech/seq-db/node" + "github.com/ozontech/seq-db/util" ) type IteratorAsc Cursor @@ -72,3 +73,33 @@ func (it *IteratorAsc) Next() node.CmpLID { it.lids = it.lids[:i] return node.NewCmpLIDOrderAsc(lid) } + +// NextGeq returns the next (in reverse iteration order) LID that is <= maxLID. +func (it *IteratorAsc) NextGeq(nextID node.CmpLID) node.CmpLID { + for { + for len(it.lids) == 0 { + if !it.tryNextBlock { + return node.NewCmpLIDOrderAsc(0) + } + + it.loadNextLIDsBlock() + it.lids, it.tryNextBlock = it.narrowLIDsRange(it.lids, it.tryNextBlock) + it.counter.AddLIDsCount(len(it.lids)) + } + + // fast path: smallest remaining > nextID => skip entire block + if it.lids[0] > nextID.Unpack() { + it.lids = it.lids[:0] + continue + } + + idx, found := util.GallopSearchLeq(it.lids, nextID.Unpack()) + if found { + lid := it.lids[idx] + it.lids = it.lids[:idx] + return node.NewCmpLIDOrderAsc(lid) + } + + it.lids = it.lids[:0] + } +} diff --git a/frac/sealed/lids/iterator_desc.go b/frac/sealed/lids/iterator_desc.go index 20ea1552..8bef3913 100644 --- a/frac/sealed/lids/iterator_desc.go +++ b/frac/sealed/lids/iterator_desc.go @@ -8,6 +8,7 @@ import ( "github.com/ozontech/seq-db/logger" "github.com/ozontech/seq-db/node" + "github.com/ozontech/seq-db/util" ) type IteratorDesc Cursor @@ -72,3 +73,34 @@ func (it *IteratorDesc) Next() node.CmpLID { it.lids = it.lids[1:] return node.NewCmpLIDOrderDesc(lid) } + +// NextGeq finds next greater or equal +func (it *IteratorDesc) NextGeq(nextID node.CmpLID) node.CmpLID { + for { + for len(it.lids) == 0 { + if !it.tryNextBlock { + return node.NewCmpLIDOrderDesc(math.MaxUint32) + } + + it.loadNextLIDsBlock() // last chunk in block but not last for tid; need load next block + it.lids, it.tryNextBlock = it.narrowLIDsRange(it.lids, it.tryNextBlock) + it.counter.AddLIDsCount(len(it.lids)) // inc loaded LIDs count + } + + // fast path: last LID < nextID => skip the entire block + if nextID.Unpack() > it.lids[len(it.lids)-1] { + it.lids = it.lids[:0] + continue + } + + idx, found := util.GallopSearchGeq(it.lids, nextID.Unpack()) + if found { + it.lids = it.lids[idx:] + lid := it.lids[0] + it.lids = it.lids[1:] + return node.NewCmpLIDOrderDesc(lid) + } + + it.lids = it.lids[:0] + } +} diff --git a/node/bench_test.go b/node/bench_test.go index 9f15db1f..8006a15c 100644 --- a/node/bench_test.go +++ b/node/bench_test.go @@ -8,11 +8,16 @@ import ( "github.com/stretchr/testify/assert" ) -func newNodeStaticSize(size int) *staticAsc { +func newNodeStaticSizeRand(size int) *staticAsc { data, _ := Generate(size) return &staticAsc{staticCursor: staticCursor{data: data}} } +func newNodeStaticSizeFixedDelta(size int, start int, delta int) *staticAsc { + data, _ := GenerateFixedDelta(size, start, delta) + return &staticAsc{staticCursor: staticCursor{data: data}} +} + func Generate(n int) ([]uint32, uint32) { v := make([]uint32, n) last := uint32(1) @@ -23,6 +28,16 @@ func Generate(n int) ([]uint32, uint32) { return v, last } +func GenerateFixedDelta(n, start, step int) ([]uint32, uint32) { + v := make([]uint32, n) + last := uint32(start) + for i := 0; i < len(v); i++ { + v[i] = last + last += uint32(step) + } + return v, last +} + func BenchmarkNot(b *testing.B) { sizes := []int{1000, 10_000, 1_000_000} @@ -65,7 +80,7 @@ func BenchmarkOr(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) for b.Loop() { res = readAllInto(n, res) @@ -82,7 +97,7 @@ func BenchmarkAnd(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s) - n := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) for b.Loop() { res = readAllInto(n, res) @@ -99,7 +114,7 @@ func BenchmarkNAnd(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s) - n := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n := NewNAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) for b.Loop() { res = readAllInto(n, res) @@ -115,10 +130,10 @@ func BenchmarkAndTree(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) - n2 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) - n3 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) - n4 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n1 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n2 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n3 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n4 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) n12 := NewAnd(n1, n2) n34 := NewAnd(n3, n4) n := NewAnd(n12, n34) @@ -138,10 +153,10 @@ func BenchmarkOrTree(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - n1 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) - n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) - n3 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) - n4 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n1 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n2 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n3 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n4 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) n12 := NewOr(n1, n2) n34 := NewOr(n3, n4) n := NewOr(n12, n34) @@ -157,15 +172,51 @@ func BenchmarkOrTree(b *testing.B) { } } +// BenchmarkOrTreeNextGeq checks the performance of NextGeq vs Next when no skipping occur and all node +// yield distinct values (no intersection between nodes) +func BenchmarkOrTreeNextGeq(b *testing.B) { + sizes := []int{1000, 10_000, 1_000_000} + // step is equal to total number of nodes, so that every node produces distinct values + step := 8 + + for _, s := range sizes { + b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { + n1 := NewOr( + newNodeStaticSizeFixedDelta(s, 1, step), + newNodeStaticSizeFixedDelta(s, 5, step)) + n2 := NewOr( + newNodeStaticSizeFixedDelta(s, 2, step), + newNodeStaticSizeFixedDelta(s, 6, step)) + n3 := NewOr( + newNodeStaticSizeFixedDelta(s, 3, step), + newNodeStaticSizeFixedDelta(s, 8, step)) + n4 := NewOr( + newNodeStaticSizeFixedDelta(s, 4, step), + newNodeStaticSizeFixedDelta(s, 7, step)) + n12 := NewOr(n1, n2) + n34 := NewOr(n3, n4) + n := NewOr(n12, n34) + res := make([]uint32, 0, s*8) + + for b.Loop() { + res = readAllIntoGeq(n, res) + } + + assert.Equal(b, cap(res), s*8) + + }) + } +} + func BenchmarkComplex(b *testing.B) { sizes := []int{1000, 10_000, 1_000_000} for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) - n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) - n3 := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n1 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n2 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n3 := NewNAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) n12 := NewOr(n1, n2) n := NewAnd(n12, n3) diff --git a/node/cmp_lid.go b/node/cmp_lid.go index 20bd46e7..f27c8314 100644 --- a/node/cmp_lid.go +++ b/node/cmp_lid.go @@ -49,6 +49,10 @@ func (c CmpLID) Less(other CmpLID) bool { return c.lid < other.lid } +func (c CmpLID) LessOrEq(other CmpLID) bool { + return c.lid <= other.lid +} + func (c CmpLID) Inc() CmpLID { c.lid++ return c @@ -58,6 +62,22 @@ func (c CmpLID) Eq(other CmpLID) bool { return c.lid == other.lid } +func Max(left CmpLID, right CmpLID) CmpLID { + if left.lid > right.lid { + return left + } else { + return right + } +} + +func Min(left CmpLID, right CmpLID) CmpLID { + if left.lid < right.lid { + return left + } else { + return right + } +} + func (c CmpLID) Unpack() uint32 { return c.lid ^ c.mask } diff --git a/node/node.go b/node/node.go index 4f5bc0b2..934cf4bb 100644 --- a/node/node.go +++ b/node/node.go @@ -7,10 +7,15 @@ import ( type Node interface { fmt.Stringer // for testing Next() CmpLID + // NextGeq returns next greater or equal (GEQ) lid. Currently, some nodes do not support it + // so the caller must check the output and be ready call it again if needed, like when using Next. + // Therefore, nextID is more like a hint. + NextGeq(nextID CmpLID) CmpLID } type Sourced interface { fmt.Stringer // for testing // aggregation need source NextSourced() (id CmpLID, source uint32) + NextSourcedGeq(nextLID CmpLID) (id CmpLID, source uint32) } diff --git a/node/node_and.go b/node/node_and.go index 1ecc454b..4088c4b5 100644 --- a/node/node_and.go +++ b/node/node_and.go @@ -31,13 +31,40 @@ func (n *nodeAnd) readRight() { n.rightID = n.right.Next() } +func (n *nodeAnd) readLeftGeq(nextID CmpLID) { + n.leftID = n.left.NextGeq(nextID) +} + +func (n *nodeAnd) readRightGeq(nextID CmpLID) { + n.rightID = n.right.NextGeq(nextID) +} + func (n *nodeAnd) Next() CmpLID { for !n.leftID.IsNull() && !n.rightID.IsNull() && !n.leftID.Eq(n.rightID) { for !n.rightID.IsNull() && n.leftID.Less(n.rightID) { - n.readLeft() + n.readLeftGeq(n.rightID) + } + for !n.rightID.IsNull() && n.rightID.Less(n.leftID) { + n.readRightGeq(n.leftID) + } + } + if n.leftID.IsNull() || n.rightID.IsNull() { + return NullCmpLID() + } + cur := n.leftID + n.readLeft() + n.readRight() + return cur +} + +func (n *nodeAnd) NextGeq(nextID CmpLID) CmpLID { + // TODO first skip not interesting values, then call Next() + for !n.leftID.IsNull() && !n.rightID.IsNull() && !n.leftID.Eq(n.rightID) { + for !n.rightID.IsNull() && n.leftID.Less(n.rightID) { + n.readLeftGeq(Max(n.rightID, nextID)) } for !n.rightID.IsNull() && n.rightID.Less(n.leftID) { - n.readRight() + n.readRightGeq(Max(n.leftID, nextID)) } } if n.leftID.IsNull() || n.rightID.IsNull() { diff --git a/node/node_and_test.go b/node/node_and_test.go new file mode 100644 index 00000000..7a737263 --- /dev/null +++ b/node/node_and_test.go @@ -0,0 +1,65 @@ +package node + +import ( + "math" + "math/rand/v2" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNodeAnd_NextGeqAscending(t *testing.T) { + left := NewStatic([]uint32{1, 2, 7, 10, 20, 25, 26, 30, 50, 80, 90, 100}, false) + right := NewStatic([]uint32{1, 3, 4, 7, 9, 30, 40, 45, 60, 80, 110}, false) + + node := NewAnd(left, right) + + // Currently, nodes instantiate their state on creation, which will be fixed later. + // Thus, the first LID returned is the first from left and right + id := node.NextGeq(NewCmpLIDOrderDesc(7)) + assert.Equal(t, uint32(1), id.Unpack()) + + id = node.NextGeq(NewCmpLIDOrderDesc(7)) + assert.Equal(t, uint32(7), id.Unpack()) + + id = node.NextGeq(NewCmpLIDOrderDesc(50)) + assert.Equal(t, uint32(80), id.Unpack()) + + id = node.NextGeq(NewCmpLIDOrderDesc(50)) + assert.True(t, id.IsNull()) +} + +// TestNodeAnd_NextGeqCompatibility tests that just calling NextGeq with 0 passed as argument is equivalent to +// calling Next +func TestNodeAnd_NextGeqCompatibility(t *testing.T) { + for _, rev := range []bool{true, false} { + left := []uint32{rand.Uint32N(10)} + right := []uint32{rand.Uint32N(10)} + + for i := 1; i < 1000; i++ { + left = append(left, left[i-1]+rand.Uint32N(10)) + right = append(right, right[i-1]+rand.Uint32N(10)) + } + + node := NewAnd(NewStatic(left, rev), NewStatic(right, rev)) + nodeGeq := NewAnd(NewStatic(left, rev), NewStatic(right, rev)) + + var zero uint32 + if rev { + zero = math.MaxUint32 + } else { + zero = 0 + } + + for { + lid := node.Next() + lidGeq := nodeGeq.NextGeq(NewCmpLID(zero, rev)) + + assert.Equal(t, lid, lidGeq) + + if lid.IsNull() { + break + } + } + } +} diff --git a/node/node_nand.go b/node/node_nand.go index 0acdb3e0..5bcf0345 100644 --- a/node/node_nand.go +++ b/node/node_nand.go @@ -43,3 +43,7 @@ func (n *nodeNAnd) Next() CmpLID { } return NullCmpLID() } + +func (n *nodeNAnd) NextGeq(nextID CmpLID) CmpLID { + return n.Next() +} diff --git a/node/node_or.go b/node/node_or.go index 31285d3b..4b08f139 100644 --- a/node/node_or.go +++ b/node/node_or.go @@ -1,6 +1,8 @@ package node -import "fmt" +import ( + "fmt" +) type nodeOr struct { left Node @@ -29,6 +31,14 @@ func (n *nodeOr) readRight() { n.rightID = n.right.Next() } +func (n *nodeOr) readLeftGeq(nextID CmpLID) { + n.leftID = n.left.NextGeq(nextID) +} + +func (n *nodeOr) readRightGeq(nextID CmpLID) { + n.rightID = n.right.NextGeq(nextID) +} + func (n *nodeOr) Next() CmpLID { if n.leftID.IsNull() && n.rightID.IsNull() { return n.leftID @@ -50,6 +60,36 @@ func (n *nodeOr) Next() CmpLID { return cur } +func (n *nodeOr) NextGeq(nextID CmpLID) CmpLID { + // fast path: if we have both branches and nothing to skip, then choose lowest and return + if !n.leftID.IsNull() && !n.rightID.IsNull() && nextID.Less(Min(n.leftID, n.rightID)) { + if n.leftID.Less(n.rightID) { + cur := n.leftID + n.readLeft() + return cur + } else if n.rightID.Less(n.leftID) { + cur := n.rightID + n.readRight() + return cur + } + + cur := n.leftID + n.readLeft() + n.readRight() + return cur + } + + // skip past nextID + if n.leftID.Less(nextID) { + n.readLeftGeq(nextID) + } + if n.rightID.Less(nextID) { + n.readRightGeq(nextID) + } + + return n.Next() +} + type nodeOrAgg struct { reverse bool @@ -82,6 +122,14 @@ func (n *nodeOrAgg) readRight() { n.rightID, n.rightSource = n.right.NextSourced() } +func (n *nodeOrAgg) readLeftGeq(nextID CmpLID) { + n.leftID, n.leftSource = n.left.NextSourcedGeq(nextID) +} + +func (n *nodeOrAgg) readRightGeq(nextID CmpLID) { + n.rightID, n.rightSource = n.right.NextSourcedGeq(nextID) +} + func (n *nodeOrAgg) NextSourced() (CmpLID, uint32) { if n.leftID.IsNull() && n.rightID.IsNull() { return n.leftID, 0 @@ -97,3 +145,31 @@ func (n *nodeOrAgg) NextSourced() (CmpLID, uint32) { n.readRight() return cur, curSource } + +func (n *nodeOrAgg) NextSourcedGeq(nextID CmpLID) (CmpLID, uint32) { + // Fast path: if we at least left or right and there is nothing to skip, then choose lowest and return. + minID := Min(n.leftID, n.rightID) + if nextID.LessOrEq(minID) { + if n.leftID.Less(n.rightID) { + cur := n.leftID + curSource := n.leftSource + n.readLeft() + return cur, curSource + } else { + // we don't need deduplication + cur := n.rightID + curSource := n.rightSource + n.readRight() + return cur, curSource + } + } + + if n.leftID.Less(nextID) { + n.readLeftGeq(nextID) + } + if n.rightID.Less(nextID) { + n.readRightGeq(nextID) + } + + return n.NextSourced() +} diff --git a/node/node_or_test.go b/node/node_or_test.go new file mode 100644 index 00000000..f5bf1fd3 --- /dev/null +++ b/node/node_or_test.go @@ -0,0 +1,257 @@ +package node + +import ( + "math" + "math/rand/v2" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNodeOr_NextGeqAscending(t *testing.T) { + left := NewStatic([]uint32{2, 7, 10, 20, 25, 26, 30, 50}, false) + right := NewStatic([]uint32{1, 3, 4, 7, 9, 30, 40}, false) + + node := NewOr(left, right) + + id := node.NextGeq(NewCmpLIDOrderDesc(7)) + assert.Equal(t, uint32(7), id.Unpack()) + + id = node.NextGeq(NewCmpLIDOrderDesc(7)) + assert.Equal(t, uint32(9), id.Unpack()) + + id = node.NextGeq(NewCmpLIDOrderDesc(24)) + assert.Equal(t, uint32(25), id.Unpack()) + + id = node.NextGeq(NewCmpLIDOrderDesc(30)) + assert.Equal(t, uint32(30), id.Unpack()) + + id = node.NextGeq(NewCmpLIDOrderDesc(51)) + assert.True(t, id.IsNull()) +} + +// TestNodeOr_NextGeqCompatibility tests that just calling NextGeq with LID zero value passed as argument is equivalent to +// calling Next +func TestNodeOr_NextGeqCompatibility(t *testing.T) { + for _, rev := range []bool{true, false} { + left := []uint32{rand.Uint32N(10)} + right := []uint32{rand.Uint32N(10)} + + for i := 1; i < 1000; i++ { + left = append(left, left[i-1]+rand.Uint32N(10)) + right = append(right, right[i-1]+rand.Uint32N(10)) + } + + node := NewOr(NewStatic(left, rev), NewStatic(right, rev)) + nodeGeq := NewOr(NewStatic(left, rev), NewStatic(right, rev)) + + var zero uint32 + if rev { + zero = math.MaxUint32 + } else { + zero = 0 + } + + for { + lid := node.Next() + lidGeq := nodeGeq.NextGeq(NewCmpLID(zero, rev)) + + assert.Equal(t, lid, lidGeq) + + if lid.IsNull() { + break + } + } + } +} + +// TestNodeOrAgg_NoDedup tests that nodeOrAgg yields values both from left and right for same lid. +func TestNodeOrAgg_NoDedup(t *testing.T) { + left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 5, 7}, false), 1) + right := NewSourcedNodeWrapper(NewStatic([]uint32{5, 8}, false), 2) + + orAgg := NewNodeOrAgg(left, right, false) + pairs := readAllSourced(orAgg) + + // expected sources for lid=5 + var sources []uint32 + + for _, p := range pairs { + id, src := p[0], p[1] + if id == 5 { + sources = append(sources, src) + } + } + + require.Len(t, sources, 2, "expected id 5 to be returned twice from both children") + assert.ElementsMatch(t, []uint32{1, 2}, sources, "expected id 5 from both left and right sources") +} + +func TestNodeOrAgg_MergeAscending(t *testing.T) { + left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 3, 5}, false), 0) + right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 4, 6}, false), 1) + + orAgg := NewNodeOrAgg(left, right, false) + got := readAllSourced(orAgg) + + want := [][2]uint32{ + {1, 0}, + {2, 1}, + {3, 0}, + {4, 1}, + {5, 0}, + {6, 1}, + } + + assert.Equal(t, want, got) +} + +func TestNodeOrAgg_MergeAscendingWithDups(t *testing.T) { + left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 2, 3, 5, 8}, false), 0) + right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 3, 4, 6, 8}, false), 1) + + orAgg := NewNodeOrAgg(left, right, false) + got := readAllSourced(orAgg) + + want := [][2]uint32{ + {1, 0}, + {2, 1}, + {2, 0}, + {3, 1}, + {3, 0}, + {4, 1}, + {5, 0}, + {6, 1}, + {8, 1}, + {8, 0}, + } + + assert.Equal(t, want, got) +} + +// TestNodeOrAgg_NextSourcedGeq tests we can navigate to a lid with NextGeq and do not skip it from +// both left and right sides (no deduplication like in ordinary OR tree) +func TestNodeOrAgg_NextSourcedGeq(t *testing.T) { + left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 2, 3, 5, 8, 15, 19}, false), 0) + right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 3, 4, 6, 8, 14, 20}, false), 1) + + orAgg := NewNodeOrAgg(left, right, false) + + id, source := orAgg.NextSourcedGeq(NewCmpLIDOrderDesc(3)) + assert.Equal(t, uint32(3), id.Unpack()) + assert.Equal(t, uint32(1), source) + + // 3 returned again, but with different source - no deduplication + id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderDesc(3)) + assert.Equal(t, uint32(3), id.Unpack()) + assert.Equal(t, uint32(0), source) + + id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderDesc(6)) + assert.Equal(t, uint32(6), id.Unpack()) + assert.Equal(t, uint32(1), source) + + id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderDesc(17)) + assert.Equal(t, uint32(19), id.Unpack()) + assert.Equal(t, uint32(0), source) +} + +// TestNodeOrAgg_NextSourcedGeq tests we can navigate to a lid with NextGeq in reverse way and do not skip it from +// both left and right sides (no deduplication like in ordinary OR tree) +func TestNodeOrAgg_NextSourcedGeq_Reverse(t *testing.T) { + left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 2, 3, 5, 8, 15, 19}, true), 0) + right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 3, 4, 6, 8, 14, 20}, true), 1) + + orAgg := NewNodeOrAgg(left, right, true) + + id, source := orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(8)) + assert.Equal(t, uint32(8), id.Unpack()) + assert.Equal(t, uint32(1), source) + + // 8 returned again, but with different source - no deduplication + id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(8)) + assert.Equal(t, uint32(8), id.Unpack()) + assert.Equal(t, uint32(0), source) + + id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(4)) + assert.Equal(t, uint32(4), id.Unpack()) + assert.Equal(t, uint32(1), source) + + id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(1)) + assert.Equal(t, uint32(1), id.Unpack()) + assert.Equal(t, uint32(0), source) + + id, _ = orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(1)) + assert.True(t, id.IsNull()) +} + +func TestNodeOrAgg_MergeDescending(t *testing.T) { + left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 3, 5}, true), 0) + right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 4, 6}, true), 1) + + orAgg := NewNodeOrAgg(left, right, true) + got := readAllSourced(orAgg) + + want := [][2]uint32{ + {6, 1}, + {5, 0}, + {4, 1}, + {3, 0}, + {2, 1}, + {1, 0}, + } + + assert.Equal(t, want, got) +} + +func TestNodeOrAgg_EmptySide(t *testing.T) { + t.Run("empty_left", func(t *testing.T) { + left := NewSourcedNodeWrapper(NewStatic(nil, false), 0) + right := NewSourcedNodeWrapper(NewStatic([]uint32{10, 20}, false), 1) + + orAgg := NewNodeOrAgg(left, right, false) + got := readAllSourced(orAgg) + + want := [][2]uint32{ + {10, 1}, + {20, 1}, + } + + assert.Equal(t, want, got) + }) + + t.Run("empty_right", func(t *testing.T) { + left := NewSourcedNodeWrapper(NewStatic([]uint32{10, 20}, false), 0) + right := NewSourcedNodeWrapper(NewStatic(nil, false), 1) + + orAgg := NewNodeOrAgg(left, right, false) + got := readAllSourced(orAgg) + + want := [][2]uint32{ + {10, 0}, + {20, 0}, + } + + assert.Equal(t, want, got) + }) + + t.Run("both_empty", func(t *testing.T) { + left := NewSourcedNodeWrapper(NewStatic(nil, false), 0) + right := NewSourcedNodeWrapper(NewStatic(nil, false), 1) + + orAgg := NewNodeOrAgg(left, right, false) + id, _ := orAgg.NextSourced() + + assert.True(t, id.IsNull()) + }) +} + +func readAllSourced(n Sourced) [][2]uint32 { + var res [][2]uint32 + id, src := n.NextSourced() + for !id.IsNull() { + res = append(res, [2]uint32{id.Unpack(), src}) + id, src = n.NextSourced() + } + return res +} diff --git a/node/node_range.go b/node/node_range.go index 3a4b70dd..09335636 100644 --- a/node/node_range.go +++ b/node/node_range.go @@ -24,3 +24,7 @@ func (n *nodeRange) Next() CmpLID { n.curID = n.curID.Inc() return result } + +func (n *nodeRange) NextGeq(nextID CmpLID) CmpLID { + return n.Next() +} diff --git a/node/node_static.go b/node/node_static.go index a6dabaf4..3960a94d 100644 --- a/node/node_static.go +++ b/node/node_static.go @@ -1,16 +1,21 @@ package node -import "math" +import "github.com/ozontech/seq-db/util" +import ( + "math" +) type staticCursor struct { ptr int data []uint32 } +// staticAsc stores lids in data slice in ascending order, and iterates in increasing order type staticAsc struct { staticCursor } +// staticAsc stores lids in data slice in ascending order, but iterates from the end (in descending order) type staticDesc struct { staticCursor } @@ -43,6 +48,24 @@ func (n *staticAsc) Next() CmpLID { return NewCmpLIDOrderDesc(cur) } +// NextGeq finds next greater or equals since iteration is in ascending order +func (n *staticAsc) NextGeq(nextID CmpLID) CmpLID { + if n.ptr >= len(n.data) { + return NewCmpLIDOrderDesc(math.MaxUint32) + } + + from := n.ptr + idx, found := util.GallopSearchGeq(n.data[from:], nextID.Unpack()) + if !found { + return NewCmpLIDOrderDesc(math.MaxUint32) + } + + i := from + idx + cur := n.data[i] + n.ptr = i + 1 + return NewCmpLIDOrderDesc(cur) +} + func (n *staticDesc) Next() CmpLID { // staticDesc is used in docs order asc, hence we return CmpLID with asc order if n.ptr < 0 { @@ -53,7 +76,22 @@ func (n *staticDesc) Next() CmpLID { return NewCmpLIDOrderAsc(cur) } -// MakeStaticNodes is currently used only for tests +// NextGeq finds next less or equals since iteration is in descending order +func (n *staticDesc) NextGeq(nextID CmpLID) CmpLID { + if n.ptr < 0 { + return NewCmpLIDOrderAsc(0) + } + idx, found := util.GallopSearchLeq(n.data[:n.ptr+1], nextID.Unpack()) + if !found { + return NewCmpLIDOrderAsc(0) + } + + cur := n.data[idx] + n.ptr = idx - 1 + return NewCmpLIDOrderAsc(cur) +} + +// MakeStaticNodes is currently used only for tests func MakeStaticNodes(data [][]uint32) []Node { nodes := make([]Node, len(data)) for i, values := range data { diff --git a/node/node_static_test.go b/node/node_static_test.go new file mode 100644 index 00000000..56d2a0b2 --- /dev/null +++ b/node/node_static_test.go @@ -0,0 +1,69 @@ +package node + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStaticAscNextGeq(t *testing.T) { + lids := []uint32{1, 3, 5, 7, 9} + n := NewStatic(lids, false).(*staticAsc) + + id := n.NextGeq(NewCmpLIDOrderDesc(0)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(1), id.Unpack()) + + id = n.NextGeq(NewCmpLIDOrderDesc(4)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(5), id.Unpack()) + + // 5 has already been returned, so the next value >= 5 is 7. + id = n.NextGeq(NewCmpLIDOrderDesc(5)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(7), id.Unpack()) + + id = n.NextGeq(NewCmpLIDOrderDesc(10)) + assert.True(t, id.IsNull()) +} + +func TestStaticDescNextGeq(t *testing.T) { + lids := []uint32{1, 3, 5, 7, 9} + n := NewStatic(lids, true).(*staticDesc) + + id := n.NextGeq(NewCmpLIDOrderDesc(10)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(9), id.Unpack()) + + id = n.NextGeq(NewCmpLIDOrderDesc(10)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(7), id.Unpack()) + + id = n.NextGeq(NewCmpLIDOrderDesc(10)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(5), id.Unpack()) +} + +func TestStaticDescNextGeq_WithThreshold(t *testing.T) { + lids := []uint32{1, 3, 5, 7, 9} + n := NewStatic(lids, true).(*staticDesc) + + id := n.NextGeq(NewCmpLIDOrderDesc(8)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(7), id.Unpack()) + + id = n.NextGeq(NewCmpLIDOrderDesc(8)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(5), id.Unpack()) + + id = n.NextGeq(NewCmpLIDOrderDesc(8)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(3), id.Unpack()) + + id = n.NextGeq(NewCmpLIDOrderDesc(8)) + assert.False(t, id.IsNull()) + assert.Equal(t, uint32(1), id.Unpack()) + + id = n.NextGeq(NewCmpLIDOrderDesc(8)) + assert.True(t, id.IsNull()) +} diff --git a/node/node_test.go b/node/node_test.go index bd0a2f8f..ecbf4865 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -14,6 +14,15 @@ func readAllInto(node Node, ids []uint32) []uint32 { return ids } +func readAllIntoGeq(node Node, ids []uint32) []uint32 { + id := node.Next() + for !id.IsNull() { + ids = append(ids, id.Unpack()) + id = node.NextGeq(id) + } + return ids +} + func readAll(node Node) []uint32 { return readAllInto(node, nil) } diff --git a/node/sourced_node_wrapper.go b/node/sourced_node_wrapper.go index 0023f4ac..45b351ef 100644 --- a/node/sourced_node_wrapper.go +++ b/node/sourced_node_wrapper.go @@ -14,6 +14,11 @@ func (w *sourcedNodeWrapper) NextSourced() (CmpLID, uint32) { return cmp, w.source } +func (w *sourcedNodeWrapper) NextSourcedGeq(nextID CmpLID) (CmpLID, uint32) { + id := w.node.NextGeq(nextID) + return id, w.source +} + func NewSourcedNodeWrapper(d Node, source int) Sourced { return &sourcedNodeWrapper{node: d, source: uint32(source)} } diff --git a/util/algorithms.go b/util/algorithms.go new file mode 100644 index 00000000..ce5d9801 --- /dev/null +++ b/util/algorithms.go @@ -0,0 +1,49 @@ +package util + +import "sort" + +// GallopSearchGeq returns the smallest index i in ascending sorted vals such that vals[i] >= geq +func GallopSearchGeq(vals []uint32, x uint32) (idx int, found bool) { + n := len(vals) + if n == 0 { + return 0, false + } + if vals[0] >= x { + return 0, true + } + hi := 1 + for hi < n && vals[hi] < x { + hi *= 2 + } + searchLen := min(n, hi+1) + idx = sort.Search(searchLen, func(i int) bool { return vals[i] >= x }) + if idx >= searchLen { + return 0, false + } + return idx, true +} + +// GallopSearchLeq returns the largest index i in ascending sorted vals such that vals[i] <= geq +func GallopSearchLeq(vals []uint32, x uint32) (idx int, found bool) { + n := len(vals) + if n == 0 { + return 0, false + } + if vals[n-1] <= x { + return n - 1, true + } + left := n - 1 + step := 1 + for left >= 0 && vals[left] > x { + left -= step + step *= 2 + } + + left = max(0, left) + searchLen := n - left + j := sort.Search(searchLen, func(j int) bool { return vals[left+j] > x }) + if j == 0 { + return 0, false + } + return left + j - 1, true +} diff --git a/util/algorithms_test.go b/util/algorithms_test.go new file mode 100644 index 00000000..a7c27cef --- /dev/null +++ b/util/algorithms_test.go @@ -0,0 +1,254 @@ +package util + +import ( + "math/rand" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGallopSearchGeq(t *testing.T) { + tests := []struct { + name string + vals []uint32 + geq uint32 + expectedIdx int + expectedFound bool + }{ + { + name: "empty", + vals: nil, + geq: 1, + expectedIdx: 0, + expectedFound: false, + }, + { + name: "single_found", + vals: []uint32{5}, + geq: 3, + expectedIdx: 0, + expectedFound: true, + }, + { + name: "single_not_found", + vals: []uint32{2}, + geq: 5, + expectedIdx: 0, + expectedFound: false, + }, + { + name: "first_element_greater", + vals: []uint32{1, 3, 5, 7, 9}, + geq: 0, + expectedIdx: 0, + expectedFound: true, + }, + { + name: "first_element_equals", + vals: []uint32{1, 3, 5, 7, 9}, + geq: 1, + expectedIdx: 0, + expectedFound: true, + }, + { + name: "middle_found_greater", + vals: []uint32{1, 3, 5, 7, 9}, + geq: 4, + expectedIdx: 2, + expectedFound: true, + }, + { + name: "mid_found_exact", + vals: []uint32{1, 3, 5, 7, 9}, + geq: 5, + expectedIdx: 2, + expectedFound: true, + }, + { + name: "last_found", + vals: []uint32{1, 3, 5, 7, 9}, + geq: 9, + expectedIdx: 4, + expectedFound: true, + }, + { + name: "last_not_found", + vals: []uint32{1, 3, 5, 7, 9}, + geq: 10, + expectedIdx: 0, + expectedFound: false, + }, + { + name: "gallop_then_binary_search", + vals: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, + geq: 17, + expectedIdx: 16, + expectedFound: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + idx, ok := GallopSearchGeq(tt.vals, tt.geq) + assert.Equal(t, tt.expectedFound, ok, "found") + if tt.expectedFound { + require.Less(t, idx, len(tt.vals), "index in range") + assert.GreaterOrEqual(t, tt.vals[idx], tt.geq, "vals[idx] >= geq") + if idx > 0 { + assert.Less(t, tt.vals[idx-1], tt.geq, "element before is < geq") + } + } + assert.Equal(t, tt.expectedIdx, idx) + }) + } +} + +func TestGallopSearchLeq(t *testing.T) { + tests := []struct { + name string + vals []uint32 + leq uint32 + expectedIdx int + expectedFound bool + }{ + { + name: "empty", + vals: nil, + leq: 5, + expectedIdx: 0, + expectedFound: false, + }, + { + name: "single_found", + vals: []uint32{5}, + leq: 10, + expectedIdx: 0, + expectedFound: true, + }, + { + name: "single_not_found", + vals: []uint32{5}, + leq: 3, + expectedIdx: 0, + expectedFound: false, + }, + { + name: "last_element_less", + vals: []uint32{1, 3, 5, 7, 9}, + leq: 10, + expectedIdx: 4, + expectedFound: true, + }, + { + name: "first_element_equal", + vals: []uint32{1, 3, 5, 7, 9}, + leq: 1, + expectedIdx: 0, + expectedFound: true, + }, + { + name: "mid_less", + vals: []uint32{1, 3, 5, 7, 9}, + leq: 6, + expectedIdx: 2, + expectedFound: true, + }, + { + name: "mid_equal", + vals: []uint32{1, 3, 5, 7, 9}, + leq: 5, + expectedIdx: 2, + expectedFound: true, + }, + { + name: "below_first", + vals: []uint32{1, 3, 5, 7, 9}, + leq: 0, + expectedIdx: 0, + expectedFound: false, + }, + { + name: "gallop_from_right_large", + vals: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, + leq: 17, + expectedIdx: 16, + expectedFound: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + idx, ok := GallopSearchLeq(tt.vals, tt.leq) + assert.Equal(t, tt.expectedFound, ok, "found") + if tt.expectedFound { + require.Less(t, idx, len(tt.vals), "index in range") + assert.LessOrEqual(t, tt.vals[idx], tt.leq, "vals[idx] <= geq") + if idx < len(tt.vals)-1 { + assert.Greater(t, tt.vals[idx+1], tt.leq, "element after is > geq") + } + } + assert.Equal(t, tt.expectedIdx, idx, "index") + }) + } +} + +func pickRandom(from, to uint32) uint32 { + if from == to { + return from + } + span := to - from + 1 + if span == 0 { + return rand.Uint32() + } + return from + rand.Uint32()%span +} + +// TestGallopSearchGeqVsSortSearch uses both gallop search and ordinary bin search to find a random number in a slice, then compares +func TestGallopSearchGeqVsSortSearch(t *testing.T) { + const size = 100 + const numSearches = 50 + + vals := make([]uint32, size) + for i := range vals { + vals[i] = rand.Uint32() + } + sort.Slice(vals, func(i, j int) bool { return vals[i] < vals[j] }) + + from, to := vals[0], vals[size-1] + for i := 0; i < numSearches; i++ { + x := pickRandom(from, to) + expectedIdx := sort.Search(size, func(i int) bool { return vals[i] >= x }) + expectedFound := expectedIdx < size + + idx, found := GallopSearchGeq(vals, x) + assert.Equal(t, expectedFound, found) + if expectedFound { + assert.Equal(t, expectedIdx, idx) + } + } +} + +// TestGallopSearchLeqVsSortSearch uses both gallop search and ordinary bin search to find a random number in a slice, then compares +func TestGallopSearchLeqVsSortSearch(t *testing.T) { + const size = 100 + const numSearches = 50 + + vals := make([]uint32, size) + for i := range vals { + vals[i] = rand.Uint32() + } + sort.Slice(vals, func(i, j int) bool { return vals[i] < vals[j] }) + + from, to := vals[0], vals[size-1] + for i := 0; i < numSearches; i++ { + x := pickRandom(from, to) + refIdx := sort.Search(size, func(i int) bool { return vals[i] > x }) - 1 + refFound := refIdx >= 0 + + idx, found := GallopSearchLeq(vals, x) + assert.Equal(t, refFound, found, "x=%d", x) + if refFound { + assert.Equal(t, refIdx, idx, "x=%d", x) + } + } +} From 4a8237e25787e39eed1dc7e78cba2b2b29f15f53 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:17:06 +0400 Subject: [PATCH 05/20] fixes --- frac/processor/aggregator_test.go | 20 ++++++++------------ node/bench_test.go | 30 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index bd5e453e..908b9398 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -166,23 +166,19 @@ func (m *MockNode) String() string { return reflect.TypeOf(m).String() } -/*func (m *MockNode) NextSourced() (uint32, uint32, bool) { - return m.NextSourcedGeq(0) -} - -func (m *MockNode) NextSourcedGeq(minLID uint32) (uint32, uint32, bool) { - for len(m.Pairs) > 0 && m.Pairs[0].LID < minLID { - m.Pairs = m.Pairs[1:] - } +func (m *MockNode) NextSourced() (node.CmpLID, uint32) { if len(m.Pairs) == 0 { - return 0, 0, false + return node.NullCmpLID(), 0 } first := m.Pairs[0] m.Pairs = m.Pairs[1:] - return first.LID, first.Source, true -}*/ + return first.LID, first.Source +} -func (m *MockNode) NextSourced() (node.CmpLID, uint32) { +func (m *MockNode) NextSourcedGeq(minLID node.CmpLID) (node.CmpLID, uint32) { + for len(m.Pairs) > 0 && m.Pairs[0].LID.Less(minLID) { + m.Pairs = m.Pairs[1:] + } if len(m.Pairs) == 0 { return node.NullCmpLID(), 0 } diff --git a/node/bench_test.go b/node/bench_test.go index 8006a15c..d4e14e7a 100644 --- a/node/bench_test.go +++ b/node/bench_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -func newNodeStaticSizeRand(size int) *staticAsc { +func newNodeStaticSize(size int) *staticAsc { data, _ := Generate(size) return &staticAsc{staticCursor: staticCursor{data: data}} } @@ -80,7 +80,7 @@ func BenchmarkOr(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) for b.Loop() { res = readAllInto(n, res) @@ -97,7 +97,7 @@ func BenchmarkAnd(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s) - n := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) for b.Loop() { res = readAllInto(n, res) @@ -114,7 +114,7 @@ func BenchmarkNAnd(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s) - n := NewNAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s)) for b.Loop() { res = readAllInto(n, res) @@ -130,10 +130,10 @@ func BenchmarkAndTree(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - n1 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) - n2 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) - n3 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) - n4 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n2 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n3 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n4 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) n12 := NewAnd(n1, n2) n34 := NewAnd(n3, n4) n := NewAnd(n12, n34) @@ -153,10 +153,10 @@ func BenchmarkOrTree(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { - n1 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) - n2 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) - n3 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) - n4 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n1 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n3 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n4 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) n12 := NewOr(n1, n2) n34 := NewOr(n3, n4) n := NewOr(n12, n34) @@ -214,9 +214,9 @@ func BenchmarkComplex(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n1 := NewAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) - n2 := NewOr(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) - n3 := NewNAnd(newNodeStaticSizeRand(s), newNodeStaticSizeRand(s)) + n1 := NewAnd(newNodeStaticSize(s), newNodeStaticSize(s)) + n2 := NewOr(newNodeStaticSize(s), newNodeStaticSize(s)) + n3 := NewNAnd(newNodeStaticSize(s), newNodeStaticSize(s)) n12 := NewOr(n1, n2) n := NewAnd(n12, n3) From e2e89f800a15a2ea5cc94de7e9c88bdf419572cc Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:20:30 +0400 Subject: [PATCH 06/20] implement String --- node/cmp_lid.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/node/cmp_lid.go b/node/cmp_lid.go index f27c8314..ba382aac 100644 --- a/node/cmp_lid.go +++ b/node/cmp_lid.go @@ -1,6 +1,14 @@ package node -import "math" +import ( + "fmt" + "math" +) + +const ( + DescMask = uint32(0) + AscMask = uint32(0xFFFFFFFF) +) // CmpLID is an encoded representation of LID and reverse flag made specifically for fast compare operations. // @@ -21,16 +29,15 @@ func NullCmpLID() CmpLID { func NewCmpLIDOrderDesc(lid uint32) CmpLID { return CmpLID{ lid: lid, - mask: uint32(0), + mask: DescMask, } } // NewCmpLIDOrderAsc returns LIDs for asc sort order func NewCmpLIDOrderAsc(lid uint32) CmpLID { - mask := uint32(0xFFFFFFFF) return CmpLID{ - lid: lid ^ mask, - mask: mask, + lid: lid ^ AscMask, + mask: AscMask, } } @@ -85,3 +92,7 @@ func (c CmpLID) Unpack() uint32 { func (c CmpLID) IsNull() bool { return c.lid == math.MaxUint32 } + +func (c CmpLID) String() string { + return fmt.Sprintf("%d, reverse=%t", c.Unpack(), c.mask == AscMask) +} From 7de326349740035bd756be04c3bdbcf88150a5a0 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:11:33 +0400 Subject: [PATCH 07/20] linter fixes --- frac/fraction_concurrency_test.go | 15 ++++++++++++--- frac/fraction_test.go | 24 ++++++++++++------------ frac/processor/aggregator.go | 5 +---- frac/processor/aggregator_test.go | 24 ++++++++++++------------ frac/processor/eval_tree.go | 4 ++-- node/builder.go | 4 ++-- node/cmp_lid.go | 12 ++---------- node/node_or.go | 6 ++---- node/node_test.go | 12 ++++++------ 9 files changed, 51 insertions(+), 55 deletions(-) diff --git a/frac/fraction_concurrency_test.go b/frac/fraction_concurrency_test.go index 38f02634..602ee9a7 100644 --- a/frac/fraction_concurrency_test.go +++ b/frac/fraction_concurrency_test.go @@ -147,6 +147,15 @@ func TestConcurrentAppendAndQuery(t *testing.T) { readTest(t, sealed, numReaders, numQueries, docs, fromTime, toTime, mapping) } +const ( + scheduler = "scheduler" + database = "database" + bus = "bus" + proxy = "proxy" + gateway = "gateway" + kafka = "kafka" +) + func readTest(t *testing.T, fraction Fraction, numReaders, numQueries int, docs []*testDoc, fromTime, toTime time.Time, mapping seq.Mapping) { readersGroup, ctx := errgroup.WithContext(t.Context()) @@ -173,7 +182,7 @@ func readTest(t *testing.T, fraction Fraction, numReaders, numQueries int, docs case 1: query = "service:gateway" filter = func(doc *testDoc) bool { - return doc.service == "gateway" + return doc.service == gateway } case 2: query = "level:2" @@ -198,7 +207,7 @@ func readTest(t *testing.T, fraction Fraction, numReaders, numQueries int, docs case 6: query = "service:gateway AND level:3" filter = func(doc *testDoc) bool { - return doc.service == "gateway" && doc.level == 3 + return doc.service == gateway && doc.level == 3 } } @@ -264,7 +273,7 @@ type testDoc = struct { } func generatesMessages(numMessages, bulkSize int) ([]*testDoc, [][]string, time.Time, time.Time) { - services := []string{"gateway", "proxy", "scheduler", "database", "bus", "kafka"} + services := []string{gateway, proxy, scheduler, database, bus, kafka} messages := []string{ "request started", "request completed", "processing timed out", "processing data", "processing failed", "processing retry", diff --git a/frac/fraction_test.go b/frac/fraction_test.go index cae6cfeb..c0ffd999 100644 --- a/frac/fraction_test.go +++ b/frac/fraction_test.go @@ -739,14 +739,14 @@ func (s *FractionTestSuite) TestBasicAggregation() { "message:*", withAggQuery(processor.AggQuery{GroupBy: aggField("service")})), []map[string]uint64{ - {"gateway": 3, "proxy": 2, "scheduler": 1}, + {gateway: 3, proxy: 2, scheduler: 1}, }) assertAggSearch( s.query( "message:good", withAggQuery(processor.AggQuery{GroupBy: aggField("service")})), []map[string]uint64{ - {"gateway": 2, "proxy": 1}, + {gateway: 2, proxy: 1}, }) assertAggSearch( s.query( @@ -761,7 +761,7 @@ func (s *FractionTestSuite) TestBasicAggregation() { withAggQuery(processor.AggQuery{GroupBy: aggField("service")}), withAggQuery(processor.AggQuery{GroupBy: aggField("level")})), []map[string]uint64{ - {"gateway": 3, "proxy": 2, "scheduler": 1}, + {gateway: 3, proxy: 2, "scheduler": 1}, {"1": 4, "2": 1, "3": 1}, }) } @@ -1224,28 +1224,28 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { { name: "NOT service:bus", query: "NOT service:bus", - filter: func(doc *testDoc) bool { return doc.service != "bus" }, + filter: func(doc *testDoc) bool { return doc.service != bus }, fromTime: fromTime, toTime: toTime, }, { name: "NOT service:bus (time range)", query: "NOT service:bus", - filter: func(doc *testDoc) bool { return doc.service != "bus" }, + filter: func(doc *testDoc) bool { return doc.service != bus }, fromTime: fromTime, toTime: midTime, }, { name: "service:proxy (time range)", query: "service:proxy", - filter: func(doc *testDoc) bool { return doc.service == "proxy" }, + filter: func(doc *testDoc) bool { return doc.service == proxy }, fromTime: fromTime, toTime: midTime, }, { name: "service:scheduler (time range + limit)", query: "service:scheduler", - filter: func(doc *testDoc) bool { return doc.service == "scheduler" }, + filter: func(doc *testDoc) bool { return doc.service == scheduler }, fromTime: fromTime, toTime: midTime, limit: 100, @@ -1313,7 +1313,7 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { name: "service:gateway AND message:processing AND message:retry AND level:5", query: "service:gateway AND message:processing AND message:retry AND level:5", filter: func(doc *testDoc) bool { - return doc.service == "gateway" && strings.Contains(doc.message, "processing") && + return doc.service == gateway && strings.Contains(doc.message, "processing") && strings.Contains(doc.message, "retry") && doc.level == 5 }, fromTime: fromTime, @@ -1350,7 +1350,7 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { query: "(service:gateway OR service:proxy OR service:scheduler) AND " + "(message:request OR message:failed) AND level:[1 to 3]", filter: func(doc *testDoc) bool { - return (doc.service == "gateway" || doc.service == "proxy" || doc.service == "scheduler") && + return (doc.service == gateway || doc.service == proxy || doc.service == "scheduler") && (strings.Contains(doc.message, "request") || strings.Contains(doc.message, "failed")) && (doc.level >= 1 && doc.level <= 3) }, @@ -1361,7 +1361,7 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { name: "service:gateway AND NOT (message:request OR message:timed OR level:[0 to 3])", query: "service:gateway AND NOT (message:request OR message:timed OR level:[0 to 3])", filter: func(doc *testDoc) bool { - return doc.service == "gateway" && + return doc.service == gateway && !(strings.Contains(doc.message, "request") || strings.Contains(doc.message, "timed") || (doc.level >= 0 && doc.level <= 3)) @@ -1373,7 +1373,7 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { name: "service:proxy AND NOT level:5 AND NOT pod:pod-2* AND NOT client_ip:ip_range(192.168.19.0,192.168.19.255)", query: "service:proxy AND NOT level:5 AND NOT pod:pod-2* AND NOT client_ip:ip_range(192.168.19.0,192.168.19.255)", filter: func(doc *testDoc) bool { - return doc.service == "proxy" && + return doc.service == proxy && doc.level != 5 && !strings.Contains(doc.pod, "pod-2") && !strings.Contains(doc.clientIp, "192.168.19") @@ -1430,7 +1430,7 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { for _, ord := range orders { ips := make(map[string]map[string]struct{}) for _, doc := range testDocs { - if doc.service != "kafka" { + if doc.service != kafka { continue } if ips[doc.pod] == nil { diff --git a/frac/processor/aggregator.go b/frac/processor/aggregator.go index ec5b0e94..21c32290 100644 --- a/frac/processor/aggregator.go +++ b/frac/processor/aggregator.go @@ -383,11 +383,9 @@ type SourcedNodeIterator struct { lastID node.CmpLID lastSource uint32 - - reverse bool } -func NewSourcedNodeIterator(sourced node.Sourced, ti tokenIndex, tids []uint32, limit iteratorLimit, reverse bool) *SourcedNodeIterator { +func NewSourcedNodeIterator(sourced node.Sourced, ti tokenIndex, tids []uint32, limit iteratorLimit) *SourcedNodeIterator { lastID, lastSource := sourced.NextSourced() return &SourcedNodeIterator{ sourcedNode: sourced, @@ -398,7 +396,6 @@ func NewSourcedNodeIterator(sourced node.Sourced, ti tokenIndex, tids []uint32, countBySource: make(map[uint32]int), lastID: lastID, lastSource: lastSource, - reverse: reverse, } } diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index 6d7f375a..ec8a3767 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -28,8 +28,8 @@ func TestSingleSourceCountAggregator(t *testing.T) { {1, 2, 4, 5, 8, 11, 12}, } - source := node.BuildORTreeAgg(node.MakeStaticNodes(sources), false) - iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) + source := node.BuildORTreeAgg(node.MakeStaticNodes(sources)) + iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) agg := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) for _, id := range searchDocs { if err := agg.Next(node.NewCmpLIDOrderDesc(id)); err != nil { @@ -56,8 +56,8 @@ func TestSingleSourceCountAggregatorWithInterval(t *testing.T) { {1, 2, 4, 5, 8, 11, 12}, } - source := node.BuildORTreeAgg(node.MakeStaticNodes(sources), false) - iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) + source := node.BuildORTreeAgg(node.MakeStaticNodes(sources)) + iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) agg := NewSingleSourceCountAggregator(iter, func(l seq.LID) seq.MID { return seq.MID(l) % 3 @@ -96,7 +96,7 @@ func BenchmarkAggDeep(b *testing.B) { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { v, _ := Generate(s) src := node.NewSourcedNodeWrapper(node.NewStatic(v, false), 0) - iter := NewSourcedNodeIterator(src, nil, make([]uint32, 1), iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) + iter := NewSourcedNodeIterator(src, nil, make([]uint32, 1), iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) n := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) vals, _ := Generate(s) @@ -127,9 +127,9 @@ func BenchmarkAggWide(b *testing.B) { slices.Sort(wide[i]) } - source := node.BuildORTreeAgg(node.MakeStaticNodes(wide), false) + source := node.BuildORTreeAgg(node.MakeStaticNodes(wide)) - iter := NewSourcedNodeIterator(source, nil, make([]uint32, len(wide)), iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) + iter := NewSourcedNodeIterator(source, nil, make([]uint32, len(wide)), iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) n := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) vals, _ := Generate(s) @@ -195,8 +195,8 @@ func TestTwoSourceAggregator(t *testing.T) { fieldTIDs := []uint32{42, 73} groupByTIDs := []uint32{1, 2} - groupIterator := NewSourcedNodeIterator(groupBy, dp, groupByTIDs, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) - fieldIterator := NewSourcedNodeIterator(field, dp, fieldTIDs, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) + groupIterator := NewSourcedNodeIterator(groupBy, dp, groupByTIDs, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) + fieldIterator := NewSourcedNodeIterator(field, dp, fieldTIDs, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) limits := AggLimits{} aggregator := NewGroupAndFieldAggregator( fieldIterator, groupIterator, provideExtractTimeFunc(nil, nil, 0), true, false, limits, @@ -248,7 +248,7 @@ func TestSingleTreeCountAggregator(t *testing.T) { }, } - iter := NewSourcedNodeIterator(field, dp, []uint32{0}, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}, false) + iter := NewSourcedNodeIterator(field, dp, []uint32{0}, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) aggregator := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) r.NoError(aggregator.Next(node.NewCmpLIDOrderDesc(1))) @@ -285,8 +285,8 @@ func TestAggregatorLimitExceeded(t *testing.T) { const limit = 1 for _, expectedErr := range []error{consts.ErrTooManyGroupTokens, consts.ErrTooManyFieldTokens} { - source := node.BuildORTreeAgg(node.MakeStaticNodes(sources), false) - iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: limit, err: expectedErr}, false) + source := node.BuildORTreeAgg(node.MakeStaticNodes(sources)) + iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: limit, err: expectedErr}) agg := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) var limitErr error diff --git a/frac/processor/eval_tree.go b/frac/processor/eval_tree.go index 4b6cbcbe..0fc17484 100644 --- a/frac/processor/eval_tree.go +++ b/frac/processor/eval_tree.go @@ -217,6 +217,6 @@ func iteratorFromLiteral( stats.AggNodesTotal += len(lidsTids)*2 - 1 } - sourcedNode := node.BuildORTreeAgg(lidsTids, order.IsReverse()) - return NewSourcedNodeIterator(sourcedNode, ti, tids, iteratorLimit, order.IsReverse()), nil + sourcedNode := node.BuildORTreeAgg(lidsTids) + return NewSourcedNodeIterator(sourcedNode, ti, tids, iteratorLimit), nil } diff --git a/node/builder.go b/node/builder.go index 69c4cb6e..80ab62d3 100644 --- a/node/builder.go +++ b/node/builder.go @@ -13,9 +13,9 @@ func BuildORTree(nodes []Node) Node { ) } -func BuildORTreeAgg(nodes []Node, reverse bool) Sourced { +func BuildORTreeAgg(nodes []Node) Sourced { return TreeFold( - func(l, r Sourced) Sourced { return NewNodeOrAgg(l, r, reverse) }, + NewNodeOrAgg, emptyNodeSourced, WrapWithSource(nodes), ) diff --git a/node/cmp_lid.go b/node/cmp_lid.go index 20bd46e7..8dfac516 100644 --- a/node/cmp_lid.go +++ b/node/cmp_lid.go @@ -14,7 +14,7 @@ type CmpLID struct { func NullCmpLID() CmpLID { // reverse flag does not matter, as null values are never unpacked - return NewCmpLID(math.MaxUint32, false) + return NewCmpLIDOrderDesc(math.MaxUint32) } // NewCmpLIDOrderDesc returns LIDs for desc sort order @@ -34,15 +34,7 @@ func NewCmpLIDOrderAsc(lid uint32) CmpLID { } } -func NewCmpLID(lid uint32, reverse bool) CmpLID { - if reverse { - return NewCmpLIDOrderAsc(lid) - } else { - return NewCmpLIDOrderDesc(lid) - } -} - -// Less compares two values. It also does an implicit null check, since we store math.MaxUint32 for null values. +// Less compares two values. It also does an implicit null check, since we store math.MaxUint32 in lid field for null values. // Which means if we call x.Less(y), then we now for sure that x is not null. Therefore, this Less call can work // as both "null check + less" combo. func (c CmpLID) Less(other CmpLID) bool { diff --git a/node/node_or.go b/node/node_or.go index 31285d3b..c0423433 100644 --- a/node/node_or.go +++ b/node/node_or.go @@ -51,8 +51,6 @@ func (n *nodeOr) Next() CmpLID { } type nodeOrAgg struct { - reverse bool - left Sourced right Sourced @@ -67,8 +65,8 @@ func (n *nodeOrAgg) String() string { return fmt.Sprintf("(%s OR %s)", n.left.String(), n.right.String()) } -func NewNodeOrAgg(left, right Sourced, reverse bool) Sourced { - n := &nodeOrAgg{reverse: reverse, left: left, right: right} +func NewNodeOrAgg(left, right Sourced) Sourced { + n := &nodeOrAgg{left: left, right: right} n.readLeft() n.readRight() return n diff --git a/node/node_test.go b/node/node_test.go index bd0a2f8f..671b1a7a 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -139,32 +139,32 @@ func TestNodeTreeBuilding(t *testing.T) { t.Run("size_0", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 0)) assert.True(t, isEmptyNode(BuildORTree(dn)), "expected empty node") - assert.True(t, isEmptyNode(BuildORTreeAgg(dn, false)), "expected empty node") + assert.True(t, isEmptyNode(BuildORTreeAgg(dn)), "expected empty node") }) t.Run("size_1", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 1)) assert.Equal(t, "STATIC", BuildORTree(dn).String()) - assert.Equal(t, "SOURCED", BuildORTreeAgg(dn, false).String()) + assert.Equal(t, "SOURCED", BuildORTreeAgg(dn).String()) }) t.Run("size_2", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 2)) assert.Equal(t, "(STATIC OR STATIC)", BuildORTree(dn).String()) - assert.Equal(t, "(SOURCED OR SOURCED)", BuildORTreeAgg(dn, false).String()) + assert.Equal(t, "(SOURCED OR SOURCED)", BuildORTreeAgg(dn).String()) }) t.Run("size_3", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 3)) assert.Equal(t, "(STATIC OR (STATIC OR STATIC))", BuildORTree(dn).String()) - assert.Equal(t, "(SOURCED OR (SOURCED OR SOURCED))", BuildORTreeAgg(dn, false).String()) + assert.Equal(t, "(SOURCED OR (SOURCED OR SOURCED))", BuildORTreeAgg(dn).String()) }) t.Run("size_4", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 4)) assert.Equal(t, "((STATIC OR STATIC) OR (STATIC OR STATIC))", BuildORTree(dn).String()) - assert.Equal(t, "((SOURCED OR SOURCED) OR (SOURCED OR SOURCED))", BuildORTreeAgg(dn, false).String()) + assert.Equal(t, "((SOURCED OR SOURCED) OR (SOURCED OR SOURCED))", BuildORTreeAgg(dn).String()) }) t.Run("size_5", func(t *testing.T) { dn := MakeStaticNodes(make([][]uint32, 5)) assert.Equal(t, "((STATIC OR STATIC) OR (STATIC OR (STATIC OR STATIC)))", BuildORTree(dn).String()) - assert.Equal(t, "((SOURCED OR SOURCED) OR (SOURCED OR (SOURCED OR SOURCED)))", BuildORTreeAgg(dn, false).String()) + assert.Equal(t, "((SOURCED OR SOURCED) OR (SOURCED OR (SOURCED OR SOURCED)))", BuildORTreeAgg(dn).String()) }) t.Run("size_6", func(t *testing.T) { labels := BuildORTree(MakeStaticNodes(make([][]uint32, 6))).String() From 1e942d0fce2b976d2a6cd23d8047f069fd602bf0 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:23:33 +0400 Subject: [PATCH 08/20] fix --- node/node_or.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node/node_or.go b/node/node_or.go index 4b08f139..ef70b763 100644 --- a/node/node_or.go +++ b/node/node_or.go @@ -61,8 +61,9 @@ func (n *nodeOr) Next() CmpLID { } func (n *nodeOr) NextGeq(nextID CmpLID) CmpLID { - // fast path: if we have both branches and nothing to skip, then choose lowest and return - if !n.leftID.IsNull() && !n.rightID.IsNull() && nextID.Less(Min(n.leftID, n.rightID)) { + // Fast path: if we at least left or right and there is nothing to skip, then choose lowest and return. + minID := Min(n.leftID, n.rightID) + if nextID.LessOrEq(minID) { if n.leftID.Less(n.rightID) { cur := n.leftID n.readLeft() @@ -79,7 +80,6 @@ func (n *nodeOr) NextGeq(nextID CmpLID) CmpLID { return cur } - // skip past nextID if n.leftID.Less(nextID) { n.readLeftGeq(nextID) } From 368d6c4fd78c6c62c2a27768ecc184434c8cebe2 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:48:17 +0400 Subject: [PATCH 09/20] const masks --- node/cmp_lid.go | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/node/cmp_lid.go b/node/cmp_lid.go index 8dfac516..1bf9c840 100644 --- a/node/cmp_lid.go +++ b/node/cmp_lid.go @@ -1,6 +1,14 @@ package node -import "math" +import ( + "fmt" + "math" +) + +const ( + DescMask = uint32(0) + AscMask = uint32(0xFFFFFFFF) +) // CmpLID is an encoded representation of LID and reverse flag made specifically for fast compare operations. // @@ -14,33 +22,44 @@ type CmpLID struct { func NullCmpLID() CmpLID { // reverse flag does not matter, as null values are never unpacked - return NewCmpLIDOrderDesc(math.MaxUint32) + return NewCmpLID(math.MaxUint32, false) } // NewCmpLIDOrderDesc returns LIDs for desc sort order func NewCmpLIDOrderDesc(lid uint32) CmpLID { return CmpLID{ lid: lid, - mask: uint32(0), + mask: DescMask, } } // NewCmpLIDOrderAsc returns LIDs for asc sort order func NewCmpLIDOrderAsc(lid uint32) CmpLID { - mask := uint32(0xFFFFFFFF) return CmpLID{ - lid: lid ^ mask, - mask: mask, + lid: lid ^ AscMask, + mask: AscMask, + } +} + +func NewCmpLID(lid uint32, reverse bool) CmpLID { + if reverse { + return NewCmpLIDOrderAsc(lid) + } else { + return NewCmpLIDOrderDesc(lid) } } -// Less compares two values. It also does an implicit null check, since we store math.MaxUint32 in lid field for null values. +// Less compares two values. It also does an implicit null check, since we store math.MaxUint32 for null values. // Which means if we call x.Less(y), then we now for sure that x is not null. Therefore, this Less call can work // as both "null check + less" combo. func (c CmpLID) Less(other CmpLID) bool { return c.lid < other.lid } +func (c CmpLID) LessOrEq(other CmpLID) bool { + return c.lid <= other.lid +} + func (c CmpLID) Inc() CmpLID { c.lid++ return c @@ -57,3 +76,7 @@ func (c CmpLID) Unpack() uint32 { func (c CmpLID) IsNull() bool { return c.lid == math.MaxUint32 } + +func (c CmpLID) String() string { + return fmt.Sprintf("%d, reverse=%t", c.Unpack(), c.mask == AscMask) +} From 08efdb7052b9587b51390cc4c1652d1fd47d5699 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Mon, 2 Mar 2026 08:35:15 +0300 Subject: [PATCH 10/20] rename node.CmpLID => node.LID --- frac/processor/aggregator.go | 12 ++++++------ frac/processor/aggregator_test.go | 4 ++-- frac/processor/eval_tree.go | 6 +++--- frac/processor/search.go | 12 ++++++------ frac/sealed/lids/iterator_asc.go | 2 +- frac/sealed/lids/iterator_desc.go | 2 +- node/cmp_lid.go | 30 +++++++++++++++--------------- node/node.go | 4 ++-- node/node_and.go | 6 +++--- node/node_nand.go | 6 +++--- node/node_not.go | 2 +- node/node_or.go | 12 ++++++------ node/node_range.go | 8 ++++---- node/node_static.go | 8 ++++---- node/sourced_node_wrapper.go | 2 +- 15 files changed, 58 insertions(+), 58 deletions(-) diff --git a/frac/processor/aggregator.go b/frac/processor/aggregator.go index 21c32290..beb1e6bd 100644 --- a/frac/processor/aggregator.go +++ b/frac/processor/aggregator.go @@ -70,7 +70,7 @@ func NewGroupAndFieldAggregator( } // Next iterates over groupBy and field iterators (actually trees) to count occurrence. -func (n *TwoSourceAggregator) Next(lid node.CmpLID) error { +func (n *TwoSourceAggregator) Next(lid node.LID) error { groupBySource, hasGroupBy, err := n.groupBy.ConsumeTokenSource(lid) if err != nil { return err @@ -204,7 +204,7 @@ func NewSingleSourceCountAggregator( } // Next iterates over groupBy tree to count occurrence. -func (n *SingleSourceCountAggregator) Next(lid node.CmpLID) error { +func (n *SingleSourceCountAggregator) Next(lid node.LID) error { source, has, err := n.group.ConsumeTokenSource(lid) if err != nil { return err @@ -273,7 +273,7 @@ func NewSingleSourceUniqueAggregator(iterator *SourcedNodeIterator) *SingleSourc } // Next iterates over groupBy tree to count occurrence. -func (n *SingleSourceUniqueAggregator) Next(lid node.CmpLID) error { +func (n *SingleSourceUniqueAggregator) Next(lid node.LID) error { source, has, err := n.group.ConsumeTokenSource(lid) if err != nil { return err @@ -325,7 +325,7 @@ func NewSingleSourceHistogramAggregator( } } -func (n *SingleSourceHistogramAggregator) Next(lid node.CmpLID) error { +func (n *SingleSourceHistogramAggregator) Next(lid node.LID) error { source, has, err := n.field.ConsumeTokenSource(lid) if err != nil { return err @@ -381,7 +381,7 @@ type SourcedNodeIterator struct { uniqSourcesLimit iteratorLimit countBySource map[uint32]int - lastID node.CmpLID + lastID node.LID lastSource uint32 } @@ -399,7 +399,7 @@ func NewSourcedNodeIterator(sourced node.Sourced, ti tokenIndex, tids []uint32, } } -func (s *SourcedNodeIterator) ConsumeTokenSource(lid node.CmpLID) (uint32, bool, error) { +func (s *SourcedNodeIterator) ConsumeTokenSource(lid node.LID) (uint32, bool, error) { for !s.lastID.IsNull() && s.lastID.Less(lid) { s.lastID, s.lastSource = s.sourcedNode.NextSourced() } diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index ec8a3767..b2e2594a 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -153,7 +153,7 @@ func (m *MockTokenIndex) GetValByTID(tid uint32) []byte { } type IDSourcePair struct { - LID node.CmpLID + LID node.LID Source uint32 } @@ -166,7 +166,7 @@ func (m *MockNode) String() string { return reflect.TypeOf(m).String() } -func (m *MockNode) NextSourced() (node.CmpLID, uint32) { +func (m *MockNode) NextSourced() (node.LID, uint32) { if len(m.Pairs) == 0 { return node.NullCmpLID(), 0 } diff --git a/frac/processor/eval_tree.go b/frac/processor/eval_tree.go index 0fc17484..aa54af67 100644 --- a/frac/processor/eval_tree.go +++ b/frac/processor/eval_tree.go @@ -45,8 +45,8 @@ func buildEvalTree(root *parser.ASTNode, minVal, maxVal uint32, stats *searchSta return node.NewNAnd(children[0], children[1]), nil case parser.LogicalNot: stats.NodesTotal++ - var minCmpLID node.CmpLID - var maxCmpLID node.CmpLID + var minCmpLID node.LID + var maxCmpLID node.LID if reverse { minCmpLID = node.NewCmpLIDOrderAsc(maxVal) maxCmpLID = node.NewCmpLIDOrderAsc(minVal) @@ -90,7 +90,7 @@ func evalLeaf( type Aggregator interface { // Next iterates to count the next lid. - Next(lid node.CmpLID) error + Next(lid node.LID) error // Aggregate processes and returns the final aggregation result. Aggregate() (seq.AggregatableSamples, error) } diff --git a/frac/processor/search.go b/frac/processor/search.go index b84b5b99..0bd9fdf3 100644 --- a/frac/processor/search.go +++ b/frac/processor/search.go @@ -189,17 +189,17 @@ func iterateEvalTree( } timerEval.Start() - cmpLid := evalTree.Next() + lid := evalTree.Next() timerEval.Stop() - if cmpLid.IsNull() { + if lid.IsNull() { break } - lid := cmpLid.Unpack() + rawLid := lid.Unpack() if needMore || hasHist { timerMID.Start() - mid := idsIndex.GetMID(seq.LID(lid)) + mid := idsIndex.GetMID(seq.LID(rawLid)) timerMID.Stop() if hasHist { @@ -216,7 +216,7 @@ func iterateEvalTree( if needMore { timerRID.Start() - rid := idsIndex.GetRID(seq.LID(lid)) + rid := idsIndex.GetRID(seq.LID(rawLid)) timerRID.Stop() id := seq.ID{MID: mid, RID: rid} @@ -233,7 +233,7 @@ func iterateEvalTree( if len(aggs) > 0 { timerAgg.Start() for i := range aggs { - if err := aggs[i].Next(cmpLid); err != nil { + if err := aggs[i].Next(lid); err != nil { timerAgg.Stop() return total, ids, histogram, err } diff --git a/frac/sealed/lids/iterator_asc.go b/frac/sealed/lids/iterator_asc.go index 368adc65..9674cb7e 100644 --- a/frac/sealed/lids/iterator_asc.go +++ b/frac/sealed/lids/iterator_asc.go @@ -56,7 +56,7 @@ func (it *IteratorAsc) loadNextLIDsBlock() { it.blockIndex-- } -func (it *IteratorAsc) Next() node.CmpLID { +func (it *IteratorAsc) Next() node.LID { for len(it.lids) == 0 { if !it.tryNextBlock { return node.NewCmpLIDOrderAsc(0) diff --git a/frac/sealed/lids/iterator_desc.go b/frac/sealed/lids/iterator_desc.go index 20ea1552..bcd51865 100644 --- a/frac/sealed/lids/iterator_desc.go +++ b/frac/sealed/lids/iterator_desc.go @@ -57,7 +57,7 @@ func (it *IteratorDesc) loadNextLIDsBlock() { it.blockIndex++ } -func (it *IteratorDesc) Next() node.CmpLID { +func (it *IteratorDesc) Next() node.LID { for len(it.lids) == 0 { if !it.tryNextBlock { return node.NewCmpLIDOrderDesc(math.MaxUint32) diff --git a/node/cmp_lid.go b/node/cmp_lid.go index 1bf9c840..ff7210c1 100644 --- a/node/cmp_lid.go +++ b/node/cmp_lid.go @@ -10,38 +10,38 @@ const ( AscMask = uint32(0xFFFFFFFF) ) -// CmpLID is an encoded representation of LID and reverse flag made specifically for fast compare operations. +// LID is an encoded representation of LID and reverse flag made specifically for fast compare operations. // // For reverse order LID is inverted as follows: "MaxUint32 - LID" formula using XOR mask. Terminal LID value is 0 instead // of MaxUint32 in reverse order, but 0 is XORed to MaxUint32. Which means, null value will always have lid field set to // 0xFFFFFFFF (math.MaxUint32) regardless of reverse (order) flag. -type CmpLID struct { +type LID struct { lid uint32 // do not read this field, use Unpack instead mask uint32 } -func NullCmpLID() CmpLID { +func NullCmpLID() LID { // reverse flag does not matter, as null values are never unpacked return NewCmpLID(math.MaxUint32, false) } // NewCmpLIDOrderDesc returns LIDs for desc sort order -func NewCmpLIDOrderDesc(lid uint32) CmpLID { - return CmpLID{ +func NewCmpLIDOrderDesc(lid uint32) LID { + return LID{ lid: lid, mask: DescMask, } } // NewCmpLIDOrderAsc returns LIDs for asc sort order -func NewCmpLIDOrderAsc(lid uint32) CmpLID { - return CmpLID{ +func NewCmpLIDOrderAsc(lid uint32) LID { + return LID{ lid: lid ^ AscMask, mask: AscMask, } } -func NewCmpLID(lid uint32, reverse bool) CmpLID { +func NewCmpLID(lid uint32, reverse bool) LID { if reverse { return NewCmpLIDOrderAsc(lid) } else { @@ -52,31 +52,31 @@ func NewCmpLID(lid uint32, reverse bool) CmpLID { // Less compares two values. It also does an implicit null check, since we store math.MaxUint32 for null values. // Which means if we call x.Less(y), then we now for sure that x is not null. Therefore, this Less call can work // as both "null check + less" combo. -func (c CmpLID) Less(other CmpLID) bool { +func (c LID) Less(other LID) bool { return c.lid < other.lid } -func (c CmpLID) LessOrEq(other CmpLID) bool { +func (c LID) LessOrEq(other LID) bool { return c.lid <= other.lid } -func (c CmpLID) Inc() CmpLID { +func (c LID) Inc() LID { c.lid++ return c } -func (c CmpLID) Eq(other CmpLID) bool { +func (c LID) Eq(other LID) bool { return c.lid == other.lid } -func (c CmpLID) Unpack() uint32 { +func (c LID) Unpack() uint32 { return c.lid ^ c.mask } -func (c CmpLID) IsNull() bool { +func (c LID) IsNull() bool { return c.lid == math.MaxUint32 } -func (c CmpLID) String() string { +func (c LID) String() string { return fmt.Sprintf("%d, reverse=%t", c.Unpack(), c.mask == AscMask) } diff --git a/node/node.go b/node/node.go index 4f5bc0b2..6f87e9c3 100644 --- a/node/node.go +++ b/node/node.go @@ -6,11 +6,11 @@ import ( type Node interface { fmt.Stringer // for testing - Next() CmpLID + Next() LID } type Sourced interface { fmt.Stringer // for testing // aggregation need source - NextSourced() (id CmpLID, source uint32) + NextSourced() (id LID, source uint32) } diff --git a/node/node_and.go b/node/node_and.go index 1ecc454b..d5af74f4 100644 --- a/node/node_and.go +++ b/node/node_and.go @@ -8,8 +8,8 @@ type nodeAnd struct { left Node right Node - leftID CmpLID - rightID CmpLID + leftID LID + rightID LID } func (n *nodeAnd) String() string { @@ -31,7 +31,7 @@ func (n *nodeAnd) readRight() { n.rightID = n.right.Next() } -func (n *nodeAnd) Next() CmpLID { +func (n *nodeAnd) Next() LID { for !n.leftID.IsNull() && !n.rightID.IsNull() && !n.leftID.Eq(n.rightID) { for !n.rightID.IsNull() && n.leftID.Less(n.rightID) { n.readLeft() diff --git a/node/node_nand.go b/node/node_nand.go index 0acdb3e0..f4d59c35 100644 --- a/node/node_nand.go +++ b/node/node_nand.go @@ -6,8 +6,8 @@ type nodeNAnd struct { neg Node reg Node - negID CmpLID - regID CmpLID + negID LID + regID LID } func (n *nodeNAnd) String() string { @@ -29,7 +29,7 @@ func (n *nodeNAnd) readReg() { n.regID = n.reg.Next() } -func (n *nodeNAnd) Next() CmpLID { +func (n *nodeNAnd) Next() LID { for !n.regID.IsNull() { for !n.negID.IsNull() && n.negID.Less(n.regID) { n.readNeg() diff --git a/node/node_not.go b/node/node_not.go index 17415c90..1aa9c098 100644 --- a/node/node_not.go +++ b/node/node_not.go @@ -10,7 +10,7 @@ func (n *nodeNot) String() string { return fmt.Sprintf("(NOT %s)", n.neg.String()) } -func NewNot(child Node, minID, maxID CmpLID) *nodeNot { +func NewNot(child Node, minID, maxID LID) *nodeNot { nodeRange := NewRange(minID, maxID) nodeNAnd := NewNAnd(child, nodeRange) return &nodeNot{nodeNAnd: *(nodeNAnd)} diff --git a/node/node_or.go b/node/node_or.go index c0423433..a31773a3 100644 --- a/node/node_or.go +++ b/node/node_or.go @@ -6,8 +6,8 @@ type nodeOr struct { left Node right Node - leftID CmpLID - rightID CmpLID + leftID LID + rightID LID } func (n *nodeOr) String() string { @@ -29,7 +29,7 @@ func (n *nodeOr) readRight() { n.rightID = n.right.Next() } -func (n *nodeOr) Next() CmpLID { +func (n *nodeOr) Next() LID { if n.leftID.IsNull() && n.rightID.IsNull() { return n.leftID } @@ -54,10 +54,10 @@ type nodeOrAgg struct { left Sourced right Sourced - leftID CmpLID + leftID LID leftSource uint32 - rightID CmpLID + rightID LID rightSource uint32 } @@ -80,7 +80,7 @@ func (n *nodeOrAgg) readRight() { n.rightID, n.rightSource = n.right.NextSourced() } -func (n *nodeOrAgg) NextSourced() (CmpLID, uint32) { +func (n *nodeOrAgg) NextSourced() (LID, uint32) { if n.leftID.IsNull() && n.rightID.IsNull() { return n.leftID, 0 } diff --git a/node/node_range.go b/node/node_range.go index 3a4b70dd..f215c38a 100644 --- a/node/node_range.go +++ b/node/node_range.go @@ -1,22 +1,22 @@ package node type nodeRange struct { - maxID CmpLID - curID CmpLID + maxID LID + curID LID } func (n *nodeRange) String() string { return "(RANGE)" } -func NewRange(minVal, maxVal CmpLID) *nodeRange { +func NewRange(minVal, maxVal LID) *nodeRange { return &nodeRange{ curID: minVal, maxID: maxVal, } } -func (n *nodeRange) Next() CmpLID { +func (n *nodeRange) Next() LID { if n.maxID.Less(n.curID) { return NullCmpLID() } diff --git a/node/node_static.go b/node/node_static.go index a6dabaf4..c6a842da 100644 --- a/node/node_static.go +++ b/node/node_static.go @@ -33,8 +33,8 @@ func NewStatic(data []uint32, reverse bool) Node { }} } -func (n *staticAsc) Next() CmpLID { - // staticAsc is used in docs order desc, hence we return CmpLID with desc order +func (n *staticAsc) Next() LID { + // staticAsc is used in docs order desc, hence we return LID with desc order if n.ptr >= len(n.data) { return NewCmpLIDOrderDesc(math.MaxUint32) } @@ -43,8 +43,8 @@ func (n *staticAsc) Next() CmpLID { return NewCmpLIDOrderDesc(cur) } -func (n *staticDesc) Next() CmpLID { - // staticDesc is used in docs order asc, hence we return CmpLID with asc order +func (n *staticDesc) Next() LID { + // staticDesc is used in docs order asc, hence we return LID with asc order if n.ptr < 0 { return NewCmpLIDOrderAsc(0) } diff --git a/node/sourced_node_wrapper.go b/node/sourced_node_wrapper.go index 0023f4ac..82b52449 100644 --- a/node/sourced_node_wrapper.go +++ b/node/sourced_node_wrapper.go @@ -9,7 +9,7 @@ func (*sourcedNodeWrapper) String() string { return "SOURCED" } -func (w *sourcedNodeWrapper) NextSourced() (CmpLID, uint32) { +func (w *sourcedNodeWrapper) NextSourced() (LID, uint32) { cmp := w.node.Next() return cmp, w.source } From f62e2abbcfb2b127ce5b155d9d4b461f7888b54f Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Mon, 2 Mar 2026 08:42:02 +0300 Subject: [PATCH 11/20] remove TODO --- node/node_and.go | 1 - 1 file changed, 1 deletion(-) diff --git a/node/node_and.go b/node/node_and.go index 332f8353..0b42aac3 100644 --- a/node/node_and.go +++ b/node/node_and.go @@ -58,7 +58,6 @@ func (n *nodeAnd) Next() LID { } func (n *nodeAnd) NextGeq(nextID LID) LID { - // TODO first skip not interesting values, then call Next() for !n.leftID.IsNull() && !n.rightID.IsNull() && !n.leftID.Eq(n.rightID) { for !n.rightID.IsNull() && n.leftID.Less(n.rightID) { n.readLeftGeq(Max(n.rightID, nextID)) From 6035a9c4656849103337666bacae3376e99c3c8e Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Mon, 2 Mar 2026 08:50:50 +0300 Subject: [PATCH 12/20] rename node.CmpLID => node.LID --- frac/processor/aggregator_test.go | 28 ++++++++--------- frac/processor/eval_tree.go | 14 ++++----- frac/sealed/lids/iterator_asc.go | 4 +-- frac/sealed/lids/iterator_desc.go | 4 +-- node/bench_test.go | 4 +-- node/cmp_lid.go | 22 +++++--------- node/cmp_lid_test.go | 50 +++++++++++++++---------------- node/node_and.go | 2 +- node/node_nand.go | 2 +- node/node_range.go | 2 +- node/node_static.go | 8 ++--- node/node_test.go | 12 ++++---- 12 files changed, 72 insertions(+), 80 deletions(-) diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index b2e2594a..fb805350 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -32,7 +32,7 @@ func TestSingleSourceCountAggregator(t *testing.T) { iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) agg := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) for _, id := range searchDocs { - if err := agg.Next(node.NewCmpLIDOrderDesc(id)); err != nil { + if err := agg.Next(node.NewLIDOrderDesc(id)); err != nil { t.Fatal(err) } } @@ -64,7 +64,7 @@ func TestSingleSourceCountAggregatorWithInterval(t *testing.T) { }) for _, id := range searchDocs { - if err := agg.Next(node.NewCmpLIDOrderDesc(id)); err != nil { + if err := agg.Next(node.NewLIDOrderDesc(id)); err != nil { t.Fatal(err) } } @@ -102,7 +102,7 @@ func BenchmarkAggDeep(b *testing.B) { for b.Loop() { for _, v := range vals { - if err := n.Next(node.NewCmpLIDOrderDesc(v)); err != nil { + if err := n.Next(node.NewLIDOrderDesc(v)); err != nil { b.Fatal(err) } } @@ -135,7 +135,7 @@ func BenchmarkAggWide(b *testing.B) { for b.Loop() { for _, v := range vals { - if err := n.Next(node.NewCmpLIDOrderDesc(v)); err != nil { + if err := n.Next(node.NewLIDOrderDesc(v)); err != nil { b.Fatal(err) } } @@ -168,7 +168,7 @@ func (m *MockNode) String() string { func (m *MockNode) NextSourced() (node.LID, uint32) { if len(m.Pairs) == 0 { - return node.NullCmpLID(), 0 + return node.NullLID(), 0 } first := m.Pairs[0] m.Pairs = m.Pairs[1:] @@ -182,14 +182,14 @@ func TestTwoSourceAggregator(t *testing.T) { dp := &MockTokenIndex{} field := &MockNode{ Pairs: []IDSourcePair{ - {LID: node.NewCmpLIDOrderDesc(1), Source: 0}, - {LID: node.NewCmpLIDOrderDesc(2), Source: 1}, + {LID: node.NewLIDOrderDesc(1), Source: 0}, + {LID: node.NewLIDOrderDesc(2), Source: 1}, }, } groupBy := &MockNode{ Pairs: []IDSourcePair{ - {LID: node.NewCmpLIDOrderDesc(1), Source: 0}, - {LID: node.NewCmpLIDOrderDesc(2), Source: 1}, + {LID: node.NewLIDOrderDesc(1), Source: 0}, + {LID: node.NewLIDOrderDesc(2), Source: 1}, }, } @@ -203,8 +203,8 @@ func TestTwoSourceAggregator(t *testing.T) { ) // Call Next for two data points. - r.NoError(aggregator.Next(node.NewCmpLIDOrderDesc(1))) - r.NoError(aggregator.Next(node.NewCmpLIDOrderDesc(2))) + r.NoError(aggregator.Next(node.NewLIDOrderDesc(1))) + r.NoError(aggregator.Next(node.NewLIDOrderDesc(2))) // Verify countBySource map. expectedCountBySource := map[twoSources]int64{ @@ -244,14 +244,14 @@ func TestSingleTreeCountAggregator(t *testing.T) { dp := &MockTokenIndex{} field := &MockNode{ Pairs: []IDSourcePair{ - {LID: node.NewCmpLIDOrderDesc(1), Source: 0}, + {LID: node.NewLIDOrderDesc(1), Source: 0}, }, } iter := NewSourcedNodeIterator(field, dp, []uint32{0}, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) aggregator := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) - r.NoError(aggregator.Next(node.NewCmpLIDOrderDesc(1))) + r.NoError(aggregator.Next(node.NewLIDOrderDesc(1))) result, err := aggregator.Aggregate() if err != nil { @@ -293,7 +293,7 @@ func TestAggregatorLimitExceeded(t *testing.T) { var limitIteration int for i, id := range searchDocs { - if err := agg.Next(node.NewCmpLIDOrderDesc(id)); err != nil { + if err := agg.Next(node.NewLIDOrderDesc(id)); err != nil { limitErr = err limitIteration = i break diff --git a/frac/processor/eval_tree.go b/frac/processor/eval_tree.go index aa54af67..bb80db66 100644 --- a/frac/processor/eval_tree.go +++ b/frac/processor/eval_tree.go @@ -45,16 +45,16 @@ func buildEvalTree(root *parser.ASTNode, minVal, maxVal uint32, stats *searchSta return node.NewNAnd(children[0], children[1]), nil case parser.LogicalNot: stats.NodesTotal++ - var minCmpLID node.LID - var maxCmpLID node.LID + var minLID node.LID + var maxLID node.LID if reverse { - minCmpLID = node.NewCmpLIDOrderAsc(maxVal) - maxCmpLID = node.NewCmpLIDOrderAsc(minVal) + minLID = node.NewLIDOrderAsc(maxVal) + maxLID = node.NewLIDOrderAsc(minVal) } else { - minCmpLID = node.NewCmpLIDOrderDesc(minVal) - maxCmpLID = node.NewCmpLIDOrderDesc(maxVal) + minLID = node.NewLIDOrderDesc(minVal) + maxLID = node.NewLIDOrderDesc(maxVal) } - return node.NewNot(children[0], minCmpLID, maxCmpLID), nil + return node.NewNot(children[0], minLID, maxLID), nil } } return nil, fmt.Errorf("unknown token type") diff --git a/frac/sealed/lids/iterator_asc.go b/frac/sealed/lids/iterator_asc.go index 9674cb7e..4c65a094 100644 --- a/frac/sealed/lids/iterator_asc.go +++ b/frac/sealed/lids/iterator_asc.go @@ -59,7 +59,7 @@ func (it *IteratorAsc) loadNextLIDsBlock() { func (it *IteratorAsc) Next() node.LID { for len(it.lids) == 0 { if !it.tryNextBlock { - return node.NewCmpLIDOrderAsc(0) + return node.NewLIDOrderAsc(0) } it.loadNextLIDsBlock() // last chunk in block but not last for tid; need load next block @@ -70,5 +70,5 @@ func (it *IteratorAsc) Next() node.LID { i := len(it.lids) - 1 lid := it.lids[i] it.lids = it.lids[:i] - return node.NewCmpLIDOrderAsc(lid) + return node.NewLIDOrderAsc(lid) } diff --git a/frac/sealed/lids/iterator_desc.go b/frac/sealed/lids/iterator_desc.go index bcd51865..234c46db 100644 --- a/frac/sealed/lids/iterator_desc.go +++ b/frac/sealed/lids/iterator_desc.go @@ -60,7 +60,7 @@ func (it *IteratorDesc) loadNextLIDsBlock() { func (it *IteratorDesc) Next() node.LID { for len(it.lids) == 0 { if !it.tryNextBlock { - return node.NewCmpLIDOrderDesc(math.MaxUint32) + return node.NewLIDOrderDesc(math.MaxUint32) } it.loadNextLIDsBlock() // last chunk in block but not last for tid; need load next block @@ -70,5 +70,5 @@ func (it *IteratorDesc) Next() node.LID { lid := it.lids[0] it.lids = it.lids[1:] - return node.NewCmpLIDOrderDesc(lid) + return node.NewLIDOrderDesc(lid) } diff --git a/node/bench_test.go b/node/bench_test.go index 9f15db1f..0dc917e0 100644 --- a/node/bench_test.go +++ b/node/bench_test.go @@ -30,7 +30,7 @@ func BenchmarkNot(b *testing.B) { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { v, last := Generate(s) res := make([]uint32, 0, last+1) - n := NewNot(NewStatic(v, false), NewCmpLIDOrderDesc(1), NewCmpLIDOrderDesc(last)) + n := NewNot(NewStatic(v, false), NewLIDOrderDesc(1), NewLIDOrderDesc(last)) for b.Loop() { res = readAllInto(n, res) @@ -47,7 +47,7 @@ func BenchmarkNotEmpty(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n := NewNot(NewStatic(nil, false), NewCmpLIDOrderDesc(1), NewCmpLIDOrderDesc(uint32(s))) + n := NewNot(NewStatic(nil, false), NewLIDOrderDesc(1), NewLIDOrderDesc(uint32(s))) for b.Loop() { res = readAllInto(n, res) diff --git a/node/cmp_lid.go b/node/cmp_lid.go index ff7210c1..a4e83f1c 100644 --- a/node/cmp_lid.go +++ b/node/cmp_lid.go @@ -20,35 +20,27 @@ type LID struct { mask uint32 } -func NullCmpLID() LID { - // reverse flag does not matter, as null values are never unpacked - return NewCmpLID(math.MaxUint32, false) +func NullLID() LID { + // order does not matter, as null values are never unpacked + return NewLIDOrderDesc(math.MaxUint32) } -// NewCmpLIDOrderDesc returns LIDs for desc sort order -func NewCmpLIDOrderDesc(lid uint32) LID { +// NewLIDOrderDesc returns LIDs for desc sort order +func NewLIDOrderDesc(lid uint32) LID { return LID{ lid: lid, mask: DescMask, } } -// NewCmpLIDOrderAsc returns LIDs for asc sort order -func NewCmpLIDOrderAsc(lid uint32) LID { +// NewLIDOrderAsc returns LIDs for asc sort order +func NewLIDOrderAsc(lid uint32) LID { return LID{ lid: lid ^ AscMask, mask: AscMask, } } -func NewCmpLID(lid uint32, reverse bool) LID { - if reverse { - return NewCmpLIDOrderAsc(lid) - } else { - return NewCmpLIDOrderDesc(lid) - } -} - // Less compares two values. It also does an implicit null check, since we store math.MaxUint32 for null values. // Which means if we call x.Less(y), then we now for sure that x is not null. Therefore, this Less call can work // as both "null check + less" combo. diff --git a/node/cmp_lid_test.go b/node/cmp_lid_test.go index 0d0b436c..8051f363 100644 --- a/node/cmp_lid_test.go +++ b/node/cmp_lid_test.go @@ -7,57 +7,57 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCmpLID_Unpack_Desc(t *testing.T) { - x := NewCmpLIDOrderDesc(5) +func TestLID_Unpack_Desc(t *testing.T) { + x := NewLIDOrderDesc(5) assert.Equal(t, uint32(5), x.Unpack()) - x = NewCmpLIDOrderDesc(math.MaxUint32) + x = NewLIDOrderDesc(math.MaxUint32) assert.Equal(t, uint32(math.MaxUint32), x.Unpack()) - x = NewCmpLIDOrderDesc(0) + x = NewLIDOrderDesc(0) assert.Equal(t, uint32(0), x.Unpack()) - x = NullCmpLID() + x = NullLID() assert.True(t, x.IsNull()) } -func TestCmpLID_Unpack_Asc(t *testing.T) { - x := NewCmpLIDOrderAsc(5) +func TestLID_Unpack_Asc(t *testing.T) { + x := NewLIDOrderAsc(5) assert.Equal(t, uint32(5), x.Unpack()) - x = NewCmpLIDOrderAsc(0) + x = NewLIDOrderAsc(0) assert.Equal(t, uint32(0), x.Unpack()) - x = NewCmpLIDOrderAsc(math.MaxUint32) + x = NewLIDOrderAsc(math.MaxUint32) assert.Equal(t, uint32(math.MaxUint32), x.Unpack()) - x = NullCmpLID() + x = NullLID() assert.True(t, x.IsNull()) } -func TestCmpLID_Eq(t *testing.T) { - assert.Equal(t, NewCmpLIDOrderDesc(6), NewCmpLIDOrderDesc(6)) - assert.Equal(t, NewCmpLIDOrderDesc(math.MaxUint32), NewCmpLIDOrderDesc(math.MaxUint32)) +func TestLID_Eq(t *testing.T) { + assert.Equal(t, NewLIDOrderDesc(6), NewLIDOrderDesc(6)) + assert.Equal(t, NewLIDOrderDesc(math.MaxUint32), NewLIDOrderDesc(math.MaxUint32)) - assert.Equal(t, NewCmpLIDOrderAsc(6), NewCmpLIDOrderAsc(6)) - assert.Equal(t, NewCmpLIDOrderAsc(0), NewCmpLIDOrderAsc(0)) + assert.Equal(t, NewLIDOrderAsc(6), NewLIDOrderAsc(6)) + assert.Equal(t, NewLIDOrderAsc(0), NewLIDOrderAsc(0)) } -func TestCmpLID_Less_Desc(t *testing.T) { - assert.False(t, NewCmpLIDOrderDesc(6).Less(NewCmpLIDOrderDesc(6))) - assert.True(t, NewCmpLIDOrderDesc(6).Less(NewCmpLIDOrderDesc(7))) - assert.True(t, NewCmpLIDOrderDesc(0).Less(NewCmpLIDOrderDesc(5))) +func TestLID_Less_Desc(t *testing.T) { + assert.False(t, NewLIDOrderDesc(6).Less(NewLIDOrderDesc(6))) + assert.True(t, NewLIDOrderDesc(6).Less(NewLIDOrderDesc(7))) + assert.True(t, NewLIDOrderDesc(0).Less(NewLIDOrderDesc(5))) - assert.True(t, NewCmpLIDOrderDesc(56000).Less(NullCmpLID())) + assert.True(t, NewLIDOrderDesc(56000).Less(NullLID())) } -func TestCmpLID_Less_Asc(t *testing.T) { +func TestLID_Less_Asc(t *testing.T) { // for asc sort order larger values go first (order is reversed), i.e. greater values are "less" than lower values - assert.False(t, NewCmpLIDOrderAsc(6).Less(NewCmpLIDOrderAsc(6))) - assert.True(t, NewCmpLIDOrderAsc(10).Less(NewCmpLIDOrderAsc(1))) - assert.True(t, NewCmpLIDOrderAsc(5).Less(NewCmpLIDOrderAsc(0))) + assert.False(t, NewLIDOrderAsc(6).Less(NewLIDOrderAsc(6))) + assert.True(t, NewLIDOrderAsc(10).Less(NewLIDOrderAsc(1))) + assert.True(t, NewLIDOrderAsc(5).Less(NewLIDOrderAsc(0))) - assert.True(t, NewCmpLIDOrderAsc(56000).Less(NullCmpLID())) + assert.True(t, NewLIDOrderAsc(56000).Less(NullLID())) } diff --git a/node/node_and.go b/node/node_and.go index d5af74f4..c420410b 100644 --- a/node/node_and.go +++ b/node/node_and.go @@ -41,7 +41,7 @@ func (n *nodeAnd) Next() LID { } } if n.leftID.IsNull() || n.rightID.IsNull() { - return NullCmpLID() + return NullLID() } cur := n.leftID n.readLeft() diff --git a/node/node_nand.go b/node/node_nand.go index f4d59c35..69f6bdb8 100644 --- a/node/node_nand.go +++ b/node/node_nand.go @@ -41,5 +41,5 @@ func (n *nodeNAnd) Next() LID { } n.readReg() } - return NullCmpLID() + return NullLID() } diff --git a/node/node_range.go b/node/node_range.go index f215c38a..a6f75467 100644 --- a/node/node_range.go +++ b/node/node_range.go @@ -18,7 +18,7 @@ func NewRange(minVal, maxVal LID) *nodeRange { func (n *nodeRange) Next() LID { if n.maxID.Less(n.curID) { - return NullCmpLID() + return NullLID() } result := n.curID n.curID = n.curID.Inc() diff --git a/node/node_static.go b/node/node_static.go index c6a842da..81a8b5c8 100644 --- a/node/node_static.go +++ b/node/node_static.go @@ -36,21 +36,21 @@ func NewStatic(data []uint32, reverse bool) Node { func (n *staticAsc) Next() LID { // staticAsc is used in docs order desc, hence we return LID with desc order if n.ptr >= len(n.data) { - return NewCmpLIDOrderDesc(math.MaxUint32) + return NewLIDOrderDesc(math.MaxUint32) } cur := n.data[n.ptr] n.ptr++ - return NewCmpLIDOrderDesc(cur) + return NewLIDOrderDesc(cur) } func (n *staticDesc) Next() LID { // staticDesc is used in docs order asc, hence we return LID with asc order if n.ptr < 0 { - return NewCmpLIDOrderAsc(0) + return NewLIDOrderAsc(0) } cur := n.data[n.ptr] n.ptr-- - return NewCmpLIDOrderAsc(cur) + return NewLIDOrderAsc(cur) } // MakeStaticNodes is currently used only for tests diff --git a/node/node_test.go b/node/node_test.go index 671b1a7a..12ef3403 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -69,37 +69,37 @@ func TestNodeNAndReverse(t *testing.T) { func TestNodeNotReverse(t *testing.T) { expect := []uint32{15, 12, 11, 10, 9, 8, 7, 4, 1} - not := NewNot(NewStatic(data[1], true), NewCmpLIDOrderAsc(15), NewCmpLIDOrderAsc(1)) + not := NewNot(NewStatic(data[1], true), NewLIDOrderAsc(15), NewLIDOrderAsc(1)) assert.Equal(t, expect, readAll(not)) } func TestNodeRange(t *testing.T) { expect := []uint32{3, 4, 5, 6, 7, 8, 9, 10} - not := NewRange(NewCmpLIDOrderDesc(3), NewCmpLIDOrderDesc(10)) + not := NewRange(NewLIDOrderDesc(3), NewLIDOrderDesc(10)) assert.Equal(t, expect, readAll(not)) } func TestNodeRangeReverse(t *testing.T) { expect := []uint32{10, 9, 8, 7, 6, 5, 4, 3} - not := NewRange(NewCmpLIDOrderAsc(10), NewCmpLIDOrderAsc(3)) + not := NewRange(NewLIDOrderAsc(10), NewLIDOrderAsc(3)) assert.Equal(t, expect, readAll(not)) } func TestNodeNotPartialRange(t *testing.T) { expect := []uint32{4, 7, 8, 9, 10} - not := NewNot(NewStatic(data[1], false), NewCmpLIDOrderDesc(3), NewCmpLIDOrderDesc(10)) + not := NewNot(NewStatic(data[1], false), NewLIDOrderDesc(3), NewLIDOrderDesc(10)) assert.Equal(t, expect, readAll(not)) } func TestNodeNotPartialRangeReverse(t *testing.T) { expect := []uint32{10, 9, 8, 7, 4} - not := NewNot(NewStatic(data[1], true), NewCmpLIDOrderAsc(10), NewCmpLIDOrderAsc(3)) + not := NewNot(NewStatic(data[1], true), NewLIDOrderAsc(10), NewLIDOrderAsc(3)) assert.Equal(t, expect, readAll(not)) } func TestNodeNot(t *testing.T) { expect := []uint32{1, 4, 7, 8, 9, 10, 11, 12, 15} - nand := NewNot(NewStatic(data[1], false), NewCmpLIDOrderDesc(1), NewCmpLIDOrderDesc(15)) + nand := NewNot(NewStatic(data[1], false), NewLIDOrderDesc(1), NewLIDOrderDesc(15)) assert.Equal(t, expect, readAll(nand)) } From f6ac1eb47a5adf6e184e4f44bf2a32658dbed70b Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Mon, 2 Mar 2026 08:56:59 +0300 Subject: [PATCH 13/20] rename node.CmpLID => node.LID --- frac/processor/aggregator_test.go | 4 +-- frac/sealed/lids/iterator_asc.go | 6 ++-- frac/sealed/lids/iterator_desc.go | 6 ++-- node/cmp_lid.go | 8 ++++++ node/node_and_test.go | 10 +++---- node/node_nand.go | 2 +- node/node_or_test.go | 48 +++++++++++++++---------------- node/node_range.go | 2 +- node/node_static.go | 14 ++++----- node/node_static_test.go | 24 ++++++++-------- node/sourced_node_wrapper.go | 2 +- 11 files changed, 67 insertions(+), 59 deletions(-) diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index 0dbafef6..48099922 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -175,12 +175,12 @@ func (m *MockNode) NextSourced() (node.LID, uint32) { return first.LID, first.Source } -func (m *MockNode) NextSourcedGeq(minLID node.CmpLID) (node.CmpLID, uint32) { +func (m *MockNode) NextSourcedGeq(minLID node.LID) (node.LID, uint32) { for len(m.Pairs) > 0 && m.Pairs[0].LID.Less(minLID) { m.Pairs = m.Pairs[1:] } if len(m.Pairs) == 0 { - return node.NullCmpLID(), 0 + return node.NullLID(), 0 } first := m.Pairs[0] m.Pairs = m.Pairs[1:] diff --git a/frac/sealed/lids/iterator_asc.go b/frac/sealed/lids/iterator_asc.go index b509f41b..9ff6fa4f 100644 --- a/frac/sealed/lids/iterator_asc.go +++ b/frac/sealed/lids/iterator_asc.go @@ -75,11 +75,11 @@ func (it *IteratorAsc) Next() node.LID { } // NextGeq returns the next (in reverse iteration order) LID that is <= maxLID. -func (it *IteratorAsc) NextGeq(nextID node.CmpLID) node.CmpLID { +func (it *IteratorAsc) NextGeq(nextID node.LID) node.LID { for { for len(it.lids) == 0 { if !it.tryNextBlock { - return node.NewCmpLIDOrderAsc(0) + return node.NewLIDOrderAsc(0) } it.loadNextLIDsBlock() @@ -97,7 +97,7 @@ func (it *IteratorAsc) NextGeq(nextID node.CmpLID) node.CmpLID { if found { lid := it.lids[idx] it.lids = it.lids[:idx] - return node.NewCmpLIDOrderAsc(lid) + return node.NewLIDOrderAsc(lid) } it.lids = it.lids[:0] diff --git a/frac/sealed/lids/iterator_desc.go b/frac/sealed/lids/iterator_desc.go index 630a00c6..d9a1fc26 100644 --- a/frac/sealed/lids/iterator_desc.go +++ b/frac/sealed/lids/iterator_desc.go @@ -75,11 +75,11 @@ func (it *IteratorDesc) Next() node.LID { } // NextGeq finds next greater or equal -func (it *IteratorDesc) NextGeq(nextID node.CmpLID) node.CmpLID { +func (it *IteratorDesc) NextGeq(nextID node.LID) node.LID { for { for len(it.lids) == 0 { if !it.tryNextBlock { - return node.NewCmpLIDOrderDesc(math.MaxUint32) + return node.NewLIDOrderDesc(math.MaxUint32) } it.loadNextLIDsBlock() // last chunk in block but not last for tid; need load next block @@ -98,7 +98,7 @@ func (it *IteratorDesc) NextGeq(nextID node.CmpLID) node.CmpLID { it.lids = it.lids[idx:] lid := it.lids[0] it.lids = it.lids[1:] - return node.NewCmpLIDOrderDesc(lid) + return node.NewLIDOrderDesc(lid) } it.lids = it.lids[:0] diff --git a/node/cmp_lid.go b/node/cmp_lid.go index b68625e7..ffe094f9 100644 --- a/node/cmp_lid.go +++ b/node/cmp_lid.go @@ -41,6 +41,14 @@ func NewLIDOrderAsc(lid uint32) LID { } } +func NewLID(lid uint32, reverse bool) LID { + if reverse { + return NewLIDOrderAsc(lid) + } else { + return NewLIDOrderDesc(lid) + } +} + // Less compares two values. It also does an implicit null check, since we store math.MaxUint32 for null values. // Which means if we call x.Less(y), then we now for sure that x is not null. Therefore, this Less call can work // as both "null check + less" combo. diff --git a/node/node_and_test.go b/node/node_and_test.go index 7a737263..0d4f41f3 100644 --- a/node/node_and_test.go +++ b/node/node_and_test.go @@ -16,16 +16,16 @@ func TestNodeAnd_NextGeqAscending(t *testing.T) { // Currently, nodes instantiate their state on creation, which will be fixed later. // Thus, the first LID returned is the first from left and right - id := node.NextGeq(NewCmpLIDOrderDesc(7)) + id := node.NextGeq(NewLIDOrderDesc(7)) assert.Equal(t, uint32(1), id.Unpack()) - id = node.NextGeq(NewCmpLIDOrderDesc(7)) + id = node.NextGeq(NewLIDOrderDesc(7)) assert.Equal(t, uint32(7), id.Unpack()) - id = node.NextGeq(NewCmpLIDOrderDesc(50)) + id = node.NextGeq(NewLIDOrderDesc(50)) assert.Equal(t, uint32(80), id.Unpack()) - id = node.NextGeq(NewCmpLIDOrderDesc(50)) + id = node.NextGeq(NewLIDOrderDesc(50)) assert.True(t, id.IsNull()) } @@ -53,7 +53,7 @@ func TestNodeAnd_NextGeqCompatibility(t *testing.T) { for { lid := node.Next() - lidGeq := nodeGeq.NextGeq(NewCmpLID(zero, rev)) + lidGeq := nodeGeq.NextGeq(NewLID(zero, rev)) assert.Equal(t, lid, lidGeq) diff --git a/node/node_nand.go b/node/node_nand.go index a2de19d1..d4a9bd73 100644 --- a/node/node_nand.go +++ b/node/node_nand.go @@ -44,6 +44,6 @@ func (n *nodeNAnd) Next() LID { return NullLID() } -func (n *nodeNAnd) NextGeq(nextID CmpLID) CmpLID { +func (n *nodeNAnd) NextGeq(nextID LID) LID { return n.Next() } diff --git a/node/node_or_test.go b/node/node_or_test.go index f5bf1fd3..e37b032f 100644 --- a/node/node_or_test.go +++ b/node/node_or_test.go @@ -15,19 +15,19 @@ func TestNodeOr_NextGeqAscending(t *testing.T) { node := NewOr(left, right) - id := node.NextGeq(NewCmpLIDOrderDesc(7)) + id := node.NextGeq(NewLIDOrderDesc(7)) assert.Equal(t, uint32(7), id.Unpack()) - id = node.NextGeq(NewCmpLIDOrderDesc(7)) + id = node.NextGeq(NewLIDOrderDesc(7)) assert.Equal(t, uint32(9), id.Unpack()) - id = node.NextGeq(NewCmpLIDOrderDesc(24)) + id = node.NextGeq(NewLIDOrderDesc(24)) assert.Equal(t, uint32(25), id.Unpack()) - id = node.NextGeq(NewCmpLIDOrderDesc(30)) + id = node.NextGeq(NewLIDOrderDesc(30)) assert.Equal(t, uint32(30), id.Unpack()) - id = node.NextGeq(NewCmpLIDOrderDesc(51)) + id = node.NextGeq(NewLIDOrderDesc(51)) assert.True(t, id.IsNull()) } @@ -55,7 +55,7 @@ func TestNodeOr_NextGeqCompatibility(t *testing.T) { for { lid := node.Next() - lidGeq := nodeGeq.NextGeq(NewCmpLID(zero, rev)) + lidGeq := nodeGeq.NextGeq(NewLID(zero, rev)) assert.Equal(t, lid, lidGeq) @@ -71,7 +71,7 @@ func TestNodeOrAgg_NoDedup(t *testing.T) { left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 5, 7}, false), 1) right := NewSourcedNodeWrapper(NewStatic([]uint32{5, 8}, false), 2) - orAgg := NewNodeOrAgg(left, right, false) + orAgg := NewNodeOrAgg(left, right) pairs := readAllSourced(orAgg) // expected sources for lid=5 @@ -92,7 +92,7 @@ func TestNodeOrAgg_MergeAscending(t *testing.T) { left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 3, 5}, false), 0) right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 4, 6}, false), 1) - orAgg := NewNodeOrAgg(left, right, false) + orAgg := NewNodeOrAgg(left, right) got := readAllSourced(orAgg) want := [][2]uint32{ @@ -111,7 +111,7 @@ func TestNodeOrAgg_MergeAscendingWithDups(t *testing.T) { left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 2, 3, 5, 8}, false), 0) right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 3, 4, 6, 8}, false), 1) - orAgg := NewNodeOrAgg(left, right, false) + orAgg := NewNodeOrAgg(left, right) got := readAllSourced(orAgg) want := [][2]uint32{ @@ -136,22 +136,22 @@ func TestNodeOrAgg_NextSourcedGeq(t *testing.T) { left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 2, 3, 5, 8, 15, 19}, false), 0) right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 3, 4, 6, 8, 14, 20}, false), 1) - orAgg := NewNodeOrAgg(left, right, false) + orAgg := NewNodeOrAgg(left, right) - id, source := orAgg.NextSourcedGeq(NewCmpLIDOrderDesc(3)) + id, source := orAgg.NextSourcedGeq(NewLIDOrderDesc(3)) assert.Equal(t, uint32(3), id.Unpack()) assert.Equal(t, uint32(1), source) // 3 returned again, but with different source - no deduplication - id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderDesc(3)) + id, source = orAgg.NextSourcedGeq(NewLIDOrderDesc(3)) assert.Equal(t, uint32(3), id.Unpack()) assert.Equal(t, uint32(0), source) - id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderDesc(6)) + id, source = orAgg.NextSourcedGeq(NewLIDOrderDesc(6)) assert.Equal(t, uint32(6), id.Unpack()) assert.Equal(t, uint32(1), source) - id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderDesc(17)) + id, source = orAgg.NextSourcedGeq(NewLIDOrderDesc(17)) assert.Equal(t, uint32(19), id.Unpack()) assert.Equal(t, uint32(0), source) } @@ -162,26 +162,26 @@ func TestNodeOrAgg_NextSourcedGeq_Reverse(t *testing.T) { left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 2, 3, 5, 8, 15, 19}, true), 0) right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 3, 4, 6, 8, 14, 20}, true), 1) - orAgg := NewNodeOrAgg(left, right, true) + orAgg := NewNodeOrAgg(left, right) - id, source := orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(8)) + id, source := orAgg.NextSourcedGeq(NewLIDOrderAsc(8)) assert.Equal(t, uint32(8), id.Unpack()) assert.Equal(t, uint32(1), source) // 8 returned again, but with different source - no deduplication - id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(8)) + id, source = orAgg.NextSourcedGeq(NewLIDOrderAsc(8)) assert.Equal(t, uint32(8), id.Unpack()) assert.Equal(t, uint32(0), source) - id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(4)) + id, source = orAgg.NextSourcedGeq(NewLIDOrderAsc(4)) assert.Equal(t, uint32(4), id.Unpack()) assert.Equal(t, uint32(1), source) - id, source = orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(1)) + id, source = orAgg.NextSourcedGeq(NewLIDOrderAsc(1)) assert.Equal(t, uint32(1), id.Unpack()) assert.Equal(t, uint32(0), source) - id, _ = orAgg.NextSourcedGeq(NewCmpLIDOrderAsc(1)) + id, _ = orAgg.NextSourcedGeq(NewLIDOrderAsc(1)) assert.True(t, id.IsNull()) } @@ -189,7 +189,7 @@ func TestNodeOrAgg_MergeDescending(t *testing.T) { left := NewSourcedNodeWrapper(NewStatic([]uint32{1, 3, 5}, true), 0) right := NewSourcedNodeWrapper(NewStatic([]uint32{2, 4, 6}, true), 1) - orAgg := NewNodeOrAgg(left, right, true) + orAgg := NewNodeOrAgg(left, right) got := readAllSourced(orAgg) want := [][2]uint32{ @@ -209,7 +209,7 @@ func TestNodeOrAgg_EmptySide(t *testing.T) { left := NewSourcedNodeWrapper(NewStatic(nil, false), 0) right := NewSourcedNodeWrapper(NewStatic([]uint32{10, 20}, false), 1) - orAgg := NewNodeOrAgg(left, right, false) + orAgg := NewNodeOrAgg(left, right) got := readAllSourced(orAgg) want := [][2]uint32{ @@ -224,7 +224,7 @@ func TestNodeOrAgg_EmptySide(t *testing.T) { left := NewSourcedNodeWrapper(NewStatic([]uint32{10, 20}, false), 0) right := NewSourcedNodeWrapper(NewStatic(nil, false), 1) - orAgg := NewNodeOrAgg(left, right, false) + orAgg := NewNodeOrAgg(left, right) got := readAllSourced(orAgg) want := [][2]uint32{ @@ -239,7 +239,7 @@ func TestNodeOrAgg_EmptySide(t *testing.T) { left := NewSourcedNodeWrapper(NewStatic(nil, false), 0) right := NewSourcedNodeWrapper(NewStatic(nil, false), 1) - orAgg := NewNodeOrAgg(left, right, false) + orAgg := NewNodeOrAgg(left, right) id, _ := orAgg.NextSourced() assert.True(t, id.IsNull()) diff --git a/node/node_range.go b/node/node_range.go index ed5ce921..8ebaca3b 100644 --- a/node/node_range.go +++ b/node/node_range.go @@ -25,6 +25,6 @@ func (n *nodeRange) Next() LID { return result } -func (n *nodeRange) NextGeq(nextID CmpLID) CmpLID { +func (n *nodeRange) NextGeq(nextID LID) LID { return n.Next() } diff --git a/node/node_static.go b/node/node_static.go index bb79b38d..d4c61954 100644 --- a/node/node_static.go +++ b/node/node_static.go @@ -51,19 +51,19 @@ func (n *staticAsc) Next() LID { // NextGeq finds next greater or equals since iteration is in ascending order func (n *staticAsc) NextGeq(nextID LID) LID { if n.ptr >= len(n.data) { - return NewCmpLIDOrderDesc(math.MaxUint32) + return NewLIDOrderDesc(math.MaxUint32) } from := n.ptr idx, found := util.GallopSearchGeq(n.data[from:], nextID.Unpack()) if !found { - return NewCmpLIDOrderDesc(math.MaxUint32) + return NewLIDOrderDesc(math.MaxUint32) } i := from + idx cur := n.data[i] n.ptr = i + 1 - return NewCmpLIDOrderDesc(cur) + return NewLIDOrderDesc(cur) } func (n *staticDesc) Next() LID { @@ -77,18 +77,18 @@ func (n *staticDesc) Next() LID { } // NextGeq finds next less or equals since iteration is in descending order -func (n *staticDesc) NextGeq(nextID LID) CmpLID { +func (n *staticDesc) NextGeq(nextID LID) LID { if n.ptr < 0 { - return NewCmpLIDOrderAsc(0) + return NewLIDOrderAsc(0) } idx, found := util.GallopSearchLeq(n.data[:n.ptr+1], nextID.Unpack()) if !found { - return NewCmpLIDOrderAsc(0) + return NewLIDOrderAsc(0) } cur := n.data[idx] n.ptr = idx - 1 - return NewCmpLIDOrderAsc(cur) + return NewLIDOrderAsc(cur) } // MakeStaticNodes is currently used only for tests diff --git a/node/node_static_test.go b/node/node_static_test.go index 56d2a0b2..98ec8bfb 100644 --- a/node/node_static_test.go +++ b/node/node_static_test.go @@ -10,20 +10,20 @@ func TestStaticAscNextGeq(t *testing.T) { lids := []uint32{1, 3, 5, 7, 9} n := NewStatic(lids, false).(*staticAsc) - id := n.NextGeq(NewCmpLIDOrderDesc(0)) + id := n.NextGeq(NewLIDOrderDesc(0)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(1), id.Unpack()) - id = n.NextGeq(NewCmpLIDOrderDesc(4)) + id = n.NextGeq(NewLIDOrderDesc(4)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(5), id.Unpack()) // 5 has already been returned, so the next value >= 5 is 7. - id = n.NextGeq(NewCmpLIDOrderDesc(5)) + id = n.NextGeq(NewLIDOrderDesc(5)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(7), id.Unpack()) - id = n.NextGeq(NewCmpLIDOrderDesc(10)) + id = n.NextGeq(NewLIDOrderDesc(10)) assert.True(t, id.IsNull()) } @@ -31,15 +31,15 @@ func TestStaticDescNextGeq(t *testing.T) { lids := []uint32{1, 3, 5, 7, 9} n := NewStatic(lids, true).(*staticDesc) - id := n.NextGeq(NewCmpLIDOrderDesc(10)) + id := n.NextGeq(NewLIDOrderDesc(10)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(9), id.Unpack()) - id = n.NextGeq(NewCmpLIDOrderDesc(10)) + id = n.NextGeq(NewLIDOrderDesc(10)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(7), id.Unpack()) - id = n.NextGeq(NewCmpLIDOrderDesc(10)) + id = n.NextGeq(NewLIDOrderDesc(10)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(5), id.Unpack()) } @@ -48,22 +48,22 @@ func TestStaticDescNextGeq_WithThreshold(t *testing.T) { lids := []uint32{1, 3, 5, 7, 9} n := NewStatic(lids, true).(*staticDesc) - id := n.NextGeq(NewCmpLIDOrderDesc(8)) + id := n.NextGeq(NewLIDOrderDesc(8)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(7), id.Unpack()) - id = n.NextGeq(NewCmpLIDOrderDesc(8)) + id = n.NextGeq(NewLIDOrderDesc(8)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(5), id.Unpack()) - id = n.NextGeq(NewCmpLIDOrderDesc(8)) + id = n.NextGeq(NewLIDOrderDesc(8)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(3), id.Unpack()) - id = n.NextGeq(NewCmpLIDOrderDesc(8)) + id = n.NextGeq(NewLIDOrderDesc(8)) assert.False(t, id.IsNull()) assert.Equal(t, uint32(1), id.Unpack()) - id = n.NextGeq(NewCmpLIDOrderDesc(8)) + id = n.NextGeq(NewLIDOrderDesc(8)) assert.True(t, id.IsNull()) } diff --git a/node/sourced_node_wrapper.go b/node/sourced_node_wrapper.go index 34384a57..7cc60015 100644 --- a/node/sourced_node_wrapper.go +++ b/node/sourced_node_wrapper.go @@ -14,7 +14,7 @@ func (w *sourcedNodeWrapper) NextSourced() (LID, uint32) { return cmp, w.source } -func (w *sourcedNodeWrapper) NextSourcedGeq(nextID CmpLID) (CmpLID, uint32) { +func (w *sourcedNodeWrapper) NextSourcedGeq(nextID LID) (LID, uint32) { id := w.node.NextGeq(nextID) return id, w.source } From 8c12ab3d90364b72c28944428b8fa96536e07e05 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Mon, 2 Mar 2026 08:57:54 +0300 Subject: [PATCH 14/20] rename node.CmpLID => node.LID (filename) --- node/{cmp_lid.go => lid.go} | 0 node/{cmp_lid_test.go => lid_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename node/{cmp_lid.go => lid.go} (100%) rename node/{cmp_lid_test.go => lid_test.go} (100%) diff --git a/node/cmp_lid.go b/node/lid.go similarity index 100% rename from node/cmp_lid.go rename to node/lid.go diff --git a/node/cmp_lid_test.go b/node/lid_test.go similarity index 100% rename from node/cmp_lid_test.go rename to node/lid_test.go From 35219c8ce3454a554c4729f5fb19ffd3ea884443 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Mon, 2 Mar 2026 08:59:29 +0300 Subject: [PATCH 15/20] rename node.CmpLID => node.LID (filename) --- node/node_and.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/node_and.go b/node/node_and.go index 39fa02f0..7c7d4aa8 100644 --- a/node/node_and.go +++ b/node/node_and.go @@ -49,7 +49,7 @@ func (n *nodeAnd) Next() LID { } } if n.leftID.IsNull() || n.rightID.IsNull() { - return NullCmpLID() + return NullLID() } cur := n.leftID n.readLeft() From 68f1aa3e068302187696c9dd464f91e0d5289af0 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:30:35 +0300 Subject: [PATCH 16/20] review fixes --- frac/fraction_test.go | 2 +- node/lid.go | 2 +- node/node_and.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frac/fraction_test.go b/frac/fraction_test.go index c0ffd999..d5e57730 100644 --- a/frac/fraction_test.go +++ b/frac/fraction_test.go @@ -761,7 +761,7 @@ func (s *FractionTestSuite) TestBasicAggregation() { withAggQuery(processor.AggQuery{GroupBy: aggField("service")}), withAggQuery(processor.AggQuery{GroupBy: aggField("level")})), []map[string]uint64{ - {gateway: 3, proxy: 2, "scheduler": 1}, + {gateway: 3, proxy: 2, scheduler: 1}, {"1": 4, "2": 1, "3": 1}, }) } diff --git a/node/lid.go b/node/lid.go index a4e83f1c..0dd338ed 100644 --- a/node/lid.go +++ b/node/lid.go @@ -42,7 +42,7 @@ func NewLIDOrderAsc(lid uint32) LID { } // Less compares two values. It also does an implicit null check, since we store math.MaxUint32 for null values. -// Which means if we call x.Less(y), then we now for sure that x is not null. Therefore, this Less call can work +// Which means if we call x.Less(y), then we know for sure that x is not null. Therefore, this Less call can work // as both "null check + less" combo. func (c LID) Less(other LID) bool { return c.lid < other.lid diff --git a/node/node_and.go b/node/node_and.go index c420410b..afb01575 100644 --- a/node/node_and.go +++ b/node/node_and.go @@ -36,7 +36,7 @@ func (n *nodeAnd) Next() LID { for !n.rightID.IsNull() && n.leftID.Less(n.rightID) { n.readLeft() } - for !n.rightID.IsNull() && n.rightID.Less(n.leftID) { + for !n.leftID.IsNull() && n.rightID.Less(n.leftID) { n.readRight() } } From eadd7899d2189bec6cc12d9a474c73a12ff4cd4b Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:51:57 +0400 Subject: [PATCH 17/20] review fixes --- frac/processor/aggregator_test.go | 26 +++++++++++----------- frac/processor/eval_tree.go | 8 +++---- frac/sealed/lids/iterator_asc.go | 4 ++-- frac/sealed/lids/iterator_desc.go | 5 ++--- node/bench_test.go | 4 ++-- node/lid.go | 10 ++++----- node/lid_test.go | 36 +++++++++++++++---------------- node/node_and.go | 2 +- node/node_nand.go | 2 +- node/node_static.go | 8 +++---- node/node_test.go | 12 +++++------ 11 files changed, 58 insertions(+), 59 deletions(-) diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index 7f609c9c..712af89c 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -32,7 +32,7 @@ func TestSingleSourceCountAggregator(t *testing.T) { iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) agg := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) for _, id := range searchDocs { - if err := agg.Next(node.NewLIDOrderDesc(id)); err != nil { + if err := agg.Next(node.NewLIDDesc(id)); err != nil { t.Fatal(err) } } @@ -64,7 +64,7 @@ func TestSingleSourceCountAggregatorWithInterval(t *testing.T) { }) for _, id := range searchDocs { - if err := agg.Next(node.NewLIDOrderDesc(id)); err != nil { + if err := agg.Next(node.NewLIDDesc(id)); err != nil { t.Fatal(err) } } @@ -105,7 +105,7 @@ func BenchmarkAggDeep(b *testing.B) { for b.Loop() { for _, v := range vals { - if err := n.Next(node.NewLIDOrderDesc(v)); err != nil { + if err := n.Next(node.NewLIDDesc(v)); err != nil { b.Fatal(err) } } @@ -139,7 +139,7 @@ func BenchmarkAggWide(b *testing.B) { for b.Loop() { for _, v := range vals { - if err := n.Next(node.NewLIDOrderDesc(v)); err != nil { + if err := n.Next(node.NewLIDDesc(v)); err != nil { b.Fatal(err) } } @@ -186,14 +186,14 @@ func TestTwoSourceAggregator(t *testing.T) { dp := &MockTokenIndex{} field := &MockNode{ Pairs: []IDSourcePair{ - {LID: node.NewLIDOrderDesc(1), Source: 0}, - {LID: node.NewLIDOrderDesc(2), Source: 1}, + {LID: node.NewLIDDesc(1), Source: 0}, + {LID: node.NewLIDDesc(2), Source: 1}, }, } groupBy := &MockNode{ Pairs: []IDSourcePair{ - {LID: node.NewLIDOrderDesc(1), Source: 0}, - {LID: node.NewLIDOrderDesc(2), Source: 1}, + {LID: node.NewLIDDesc(1), Source: 0}, + {LID: node.NewLIDDesc(2), Source: 1}, }, } @@ -207,8 +207,8 @@ func TestTwoSourceAggregator(t *testing.T) { ) // Call Next for two data points. - r.NoError(aggregator.Next(node.NewLIDOrderDesc(1))) - r.NoError(aggregator.Next(node.NewLIDOrderDesc(2))) + r.NoError(aggregator.Next(node.NewLIDDesc(1))) + r.NoError(aggregator.Next(node.NewLIDDesc(2))) // Verify countBySource map. expectedCountBySource := map[twoSources]int64{ @@ -248,14 +248,14 @@ func TestSingleTreeCountAggregator(t *testing.T) { dp := &MockTokenIndex{} field := &MockNode{ Pairs: []IDSourcePair{ - {LID: node.NewLIDOrderDesc(1), Source: 0}, + {LID: node.NewLIDDesc(1), Source: 0}, }, } iter := NewSourcedNodeIterator(field, dp, []uint32{0}, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) aggregator := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) - r.NoError(aggregator.Next(node.NewLIDOrderDesc(1))) + r.NoError(aggregator.Next(node.NewLIDDesc(1))) result, err := aggregator.Aggregate() if err != nil { @@ -297,7 +297,7 @@ func TestAggregatorLimitExceeded(t *testing.T) { var limitIteration int for i, id := range searchDocs { - if err := agg.Next(node.NewLIDOrderDesc(id)); err != nil { + if err := agg.Next(node.NewLIDDesc(id)); err != nil { limitErr = err limitIteration = i break diff --git a/frac/processor/eval_tree.go b/frac/processor/eval_tree.go index bb80db66..e4aefcba 100644 --- a/frac/processor/eval_tree.go +++ b/frac/processor/eval_tree.go @@ -48,11 +48,11 @@ func buildEvalTree(root *parser.ASTNode, minVal, maxVal uint32, stats *searchSta var minLID node.LID var maxLID node.LID if reverse { - minLID = node.NewLIDOrderAsc(maxVal) - maxLID = node.NewLIDOrderAsc(minVal) + minLID = node.NewLIDAsc(maxVal) + maxLID = node.NewLIDAsc(minVal) } else { - minLID = node.NewLIDOrderDesc(minVal) - maxLID = node.NewLIDOrderDesc(maxVal) + minLID = node.NewLIDDesc(minVal) + maxLID = node.NewLIDDesc(maxVal) } return node.NewNot(children[0], minLID, maxLID), nil } diff --git a/frac/sealed/lids/iterator_asc.go b/frac/sealed/lids/iterator_asc.go index 4c65a094..af231cbb 100644 --- a/frac/sealed/lids/iterator_asc.go +++ b/frac/sealed/lids/iterator_asc.go @@ -59,7 +59,7 @@ func (it *IteratorAsc) loadNextLIDsBlock() { func (it *IteratorAsc) Next() node.LID { for len(it.lids) == 0 { if !it.tryNextBlock { - return node.NewLIDOrderAsc(0) + return node.NullLID() } it.loadNextLIDsBlock() // last chunk in block but not last for tid; need load next block @@ -70,5 +70,5 @@ func (it *IteratorAsc) Next() node.LID { i := len(it.lids) - 1 lid := it.lids[i] it.lids = it.lids[:i] - return node.NewLIDOrderAsc(lid) + return node.NewLIDAsc(lid) } diff --git a/frac/sealed/lids/iterator_desc.go b/frac/sealed/lids/iterator_desc.go index 234c46db..16600c24 100644 --- a/frac/sealed/lids/iterator_desc.go +++ b/frac/sealed/lids/iterator_desc.go @@ -1,7 +1,6 @@ package lids import ( - "math" "sort" "go.uber.org/zap" @@ -60,7 +59,7 @@ func (it *IteratorDesc) loadNextLIDsBlock() { func (it *IteratorDesc) Next() node.LID { for len(it.lids) == 0 { if !it.tryNextBlock { - return node.NewLIDOrderDesc(math.MaxUint32) + return node.NullLID() } it.loadNextLIDsBlock() // last chunk in block but not last for tid; need load next block @@ -70,5 +69,5 @@ func (it *IteratorDesc) Next() node.LID { lid := it.lids[0] it.lids = it.lids[1:] - return node.NewLIDOrderDesc(lid) + return node.NewLIDDesc(lid) } diff --git a/node/bench_test.go b/node/bench_test.go index 1fea9d72..497c5f61 100644 --- a/node/bench_test.go +++ b/node/bench_test.go @@ -33,7 +33,7 @@ func BenchmarkNot(b *testing.B) { r := rand.New(rand.NewSource(benchRandSeed)) v, last := Generate(r, s) res := make([]uint32, 0, last+1) - n := NewNot(NewStatic(v, false), NewLIDOrderDesc(1), NewLIDOrderDesc(last)) + n := NewNot(NewStatic(v, false), NewLIDDesc(1), NewLIDDesc(last)) for b.Loop() { res = readAllInto(n, res) @@ -50,7 +50,7 @@ func BenchmarkNotEmpty(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n := NewNot(NewStatic(nil, false), NewLIDOrderDesc(1), NewLIDOrderDesc(uint32(s))) + n := NewNot(NewStatic(nil, false), NewLIDDesc(1), NewLIDDesc(uint32(s))) for b.Loop() { res = readAllInto(n, res) diff --git a/node/lid.go b/node/lid.go index 0dd338ed..36463b8c 100644 --- a/node/lid.go +++ b/node/lid.go @@ -22,19 +22,19 @@ type LID struct { func NullLID() LID { // order does not matter, as null values are never unpacked - return NewLIDOrderDesc(math.MaxUint32) + return NewLIDDesc(math.MaxUint32) } -// NewLIDOrderDesc returns LIDs for desc sort order -func NewLIDOrderDesc(lid uint32) LID { +// NewLIDDesc returns LIDs for desc sort order +func NewLIDDesc(lid uint32) LID { return LID{ lid: lid, mask: DescMask, } } -// NewLIDOrderAsc returns LIDs for asc sort order -func NewLIDOrderAsc(lid uint32) LID { +// NewLIDAsc returns LIDs for asc sort order +func NewLIDAsc(lid uint32) LID { return LID{ lid: lid ^ AscMask, mask: AscMask, diff --git a/node/lid_test.go b/node/lid_test.go index 8051f363..7506e5dd 100644 --- a/node/lid_test.go +++ b/node/lid_test.go @@ -8,13 +8,13 @@ import ( ) func TestLID_Unpack_Desc(t *testing.T) { - x := NewLIDOrderDesc(5) + x := NewLIDDesc(5) assert.Equal(t, uint32(5), x.Unpack()) - x = NewLIDOrderDesc(math.MaxUint32) + x = NewLIDDesc(math.MaxUint32) assert.Equal(t, uint32(math.MaxUint32), x.Unpack()) - x = NewLIDOrderDesc(0) + x = NewLIDDesc(0) assert.Equal(t, uint32(0), x.Unpack()) x = NullLID() @@ -23,13 +23,13 @@ func TestLID_Unpack_Desc(t *testing.T) { } func TestLID_Unpack_Asc(t *testing.T) { - x := NewLIDOrderAsc(5) + x := NewLIDAsc(5) assert.Equal(t, uint32(5), x.Unpack()) - x = NewLIDOrderAsc(0) + x = NewLIDAsc(0) assert.Equal(t, uint32(0), x.Unpack()) - x = NewLIDOrderAsc(math.MaxUint32) + x = NewLIDAsc(math.MaxUint32) assert.Equal(t, uint32(math.MaxUint32), x.Unpack()) x = NullLID() @@ -38,26 +38,26 @@ func TestLID_Unpack_Asc(t *testing.T) { } func TestLID_Eq(t *testing.T) { - assert.Equal(t, NewLIDOrderDesc(6), NewLIDOrderDesc(6)) - assert.Equal(t, NewLIDOrderDesc(math.MaxUint32), NewLIDOrderDesc(math.MaxUint32)) + assert.Equal(t, NewLIDDesc(6), NewLIDDesc(6)) + assert.Equal(t, NewLIDDesc(math.MaxUint32), NewLIDDesc(math.MaxUint32)) - assert.Equal(t, NewLIDOrderAsc(6), NewLIDOrderAsc(6)) - assert.Equal(t, NewLIDOrderAsc(0), NewLIDOrderAsc(0)) + assert.Equal(t, NewLIDAsc(6), NewLIDAsc(6)) + assert.Equal(t, NewLIDAsc(0), NewLIDAsc(0)) } func TestLID_Less_Desc(t *testing.T) { - assert.False(t, NewLIDOrderDesc(6).Less(NewLIDOrderDesc(6))) - assert.True(t, NewLIDOrderDesc(6).Less(NewLIDOrderDesc(7))) - assert.True(t, NewLIDOrderDesc(0).Less(NewLIDOrderDesc(5))) + assert.False(t, NewLIDDesc(6).Less(NewLIDDesc(6))) + assert.True(t, NewLIDDesc(6).Less(NewLIDDesc(7))) + assert.True(t, NewLIDDesc(0).Less(NewLIDDesc(5))) - assert.True(t, NewLIDOrderDesc(56000).Less(NullLID())) + assert.True(t, NewLIDDesc(56000).Less(NullLID())) } func TestLID_Less_Asc(t *testing.T) { // for asc sort order larger values go first (order is reversed), i.e. greater values are "less" than lower values - assert.False(t, NewLIDOrderAsc(6).Less(NewLIDOrderAsc(6))) - assert.True(t, NewLIDOrderAsc(10).Less(NewLIDOrderAsc(1))) - assert.True(t, NewLIDOrderAsc(5).Less(NewLIDOrderAsc(0))) + assert.False(t, NewLIDAsc(6).Less(NewLIDAsc(6))) + assert.True(t, NewLIDAsc(10).Less(NewLIDAsc(1))) + assert.True(t, NewLIDAsc(5).Less(NewLIDAsc(0))) - assert.True(t, NewLIDOrderAsc(56000).Less(NullLID())) + assert.True(t, NewLIDAsc(56000).Less(NullLID())) } diff --git a/node/node_and.go b/node/node_and.go index afb01575..51918f46 100644 --- a/node/node_and.go +++ b/node/node_and.go @@ -32,7 +32,7 @@ func (n *nodeAnd) readRight() { } func (n *nodeAnd) Next() LID { - for !n.leftID.IsNull() && !n.rightID.IsNull() && !n.leftID.Eq(n.rightID) { + for !n.leftID.IsNull() && !n.rightID.IsNull() && n.leftID != n.rightID { for !n.rightID.IsNull() && n.leftID.Less(n.rightID) { n.readLeft() } diff --git a/node/node_nand.go b/node/node_nand.go index 69f6bdb8..a24cca20 100644 --- a/node/node_nand.go +++ b/node/node_nand.go @@ -34,7 +34,7 @@ func (n *nodeNAnd) Next() LID { for !n.negID.IsNull() && n.negID.Less(n.regID) { n.readNeg() } - if n.negID.IsNull() || !n.negID.Eq(n.regID) { + if n.negID.IsNull() || n.negID != n.regID { cur := n.regID n.readReg() return cur diff --git a/node/node_static.go b/node/node_static.go index 81a8b5c8..d017bab6 100644 --- a/node/node_static.go +++ b/node/node_static.go @@ -36,21 +36,21 @@ func NewStatic(data []uint32, reverse bool) Node { func (n *staticAsc) Next() LID { // staticAsc is used in docs order desc, hence we return LID with desc order if n.ptr >= len(n.data) { - return NewLIDOrderDesc(math.MaxUint32) + return NewLIDDesc(math.MaxUint32) } cur := n.data[n.ptr] n.ptr++ - return NewLIDOrderDesc(cur) + return NewLIDDesc(cur) } func (n *staticDesc) Next() LID { // staticDesc is used in docs order asc, hence we return LID with asc order if n.ptr < 0 { - return NewLIDOrderAsc(0) + return NewLIDAsc(0) } cur := n.data[n.ptr] n.ptr-- - return NewLIDOrderAsc(cur) + return NewLIDAsc(cur) } // MakeStaticNodes is currently used only for tests diff --git a/node/node_test.go b/node/node_test.go index 12ef3403..fb9eaa13 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -69,37 +69,37 @@ func TestNodeNAndReverse(t *testing.T) { func TestNodeNotReverse(t *testing.T) { expect := []uint32{15, 12, 11, 10, 9, 8, 7, 4, 1} - not := NewNot(NewStatic(data[1], true), NewLIDOrderAsc(15), NewLIDOrderAsc(1)) + not := NewNot(NewStatic(data[1], true), NewLIDAsc(15), NewLIDAsc(1)) assert.Equal(t, expect, readAll(not)) } func TestNodeRange(t *testing.T) { expect := []uint32{3, 4, 5, 6, 7, 8, 9, 10} - not := NewRange(NewLIDOrderDesc(3), NewLIDOrderDesc(10)) + not := NewRange(NewLIDDesc(3), NewLIDDesc(10)) assert.Equal(t, expect, readAll(not)) } func TestNodeRangeReverse(t *testing.T) { expect := []uint32{10, 9, 8, 7, 6, 5, 4, 3} - not := NewRange(NewLIDOrderAsc(10), NewLIDOrderAsc(3)) + not := NewRange(NewLIDAsc(10), NewLIDAsc(3)) assert.Equal(t, expect, readAll(not)) } func TestNodeNotPartialRange(t *testing.T) { expect := []uint32{4, 7, 8, 9, 10} - not := NewNot(NewStatic(data[1], false), NewLIDOrderDesc(3), NewLIDOrderDesc(10)) + not := NewNot(NewStatic(data[1], false), NewLIDDesc(3), NewLIDDesc(10)) assert.Equal(t, expect, readAll(not)) } func TestNodeNotPartialRangeReverse(t *testing.T) { expect := []uint32{10, 9, 8, 7, 4} - not := NewNot(NewStatic(data[1], true), NewLIDOrderAsc(10), NewLIDOrderAsc(3)) + not := NewNot(NewStatic(data[1], true), NewLIDAsc(10), NewLIDAsc(3)) assert.Equal(t, expect, readAll(not)) } func TestNodeNot(t *testing.T) { expect := []uint32{1, 4, 7, 8, 9, 10, 11, 12, 15} - nand := NewNot(NewStatic(data[1], false), NewLIDOrderDesc(1), NewLIDOrderDesc(15)) + nand := NewNot(NewStatic(data[1], false), NewLIDDesc(1), NewLIDDesc(15)) assert.Equal(t, expect, readAll(nand)) } From bcbf323c9120d92ed6463ec1e114e5c58128a84c Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Wed, 18 Mar 2026 17:28:15 +0400 Subject: [PATCH 18/20] review fixes --- frac/fraction_test.go | 7 +++++++ frac/processor/aggregator.go | 2 +- node/node_nand.go | 2 +- node/node_test.go | 12 ++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/frac/fraction_test.go b/frac/fraction_test.go index 2fd226dc..cc42bca0 100644 --- a/frac/fraction_test.go +++ b/frac/fraction_test.go @@ -1286,6 +1286,13 @@ func (s *FractionTestSuite) TestSearchLargeFrac() { fromTime: fromTime, toTime: midTime, }, + { + name: "NOT trace_id:trace-4999", + query: "NOT trace_id:trace-4999", + filter: func(doc *testDoc) bool { return doc.traceId != "trace-4999" }, + fromTime: fromTime, + toTime: toTime, + }, { name: "trace_id:trace-4999", query: "trace_id:trace-4999", diff --git a/frac/processor/aggregator.go b/frac/processor/aggregator.go index beb1e6bd..dc3f99f3 100644 --- a/frac/processor/aggregator.go +++ b/frac/processor/aggregator.go @@ -400,7 +400,7 @@ func NewSourcedNodeIterator(sourced node.Sourced, ti tokenIndex, tids []uint32, } func (s *SourcedNodeIterator) ConsumeTokenSource(lid node.LID) (uint32, bool, error) { - for !s.lastID.IsNull() && s.lastID.Less(lid) { + for s.lastID.Less(lid) { s.lastID, s.lastSource = s.sourcedNode.NextSourced() } diff --git a/node/node_nand.go b/node/node_nand.go index a24cca20..42c679ac 100644 --- a/node/node_nand.go +++ b/node/node_nand.go @@ -31,7 +31,7 @@ func (n *nodeNAnd) readReg() { func (n *nodeNAnd) Next() LID { for !n.regID.IsNull() { - for !n.negID.IsNull() && n.negID.Less(n.regID) { + for n.negID.Less(n.regID) { n.readNeg() } if n.negID.IsNull() || n.negID != n.regID { diff --git a/node/node_test.go b/node/node_test.go index fb9eaa13..d537b025 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -35,12 +35,20 @@ func TestNodeAnd(t *testing.T) { expect := []uint32{5, 6, 13} and := NewAnd(NewStatic(data[0], false), NewStatic(data[1], false)) assert.Equal(t, expect, readAll(and)) + + // commutativity test + and2 := NewAnd(NewStatic(data[1], false), NewStatic(data[0], false)) + assert.Equal(t, expect, readAll(and2)) } func TestNodeOr(t *testing.T) { expect := []uint32{1, 2, 3, 5, 6, 7, 8, 9, 13, 14} or := NewOr(NewStatic(data[0], false), NewStatic(data[1], false)) assert.Equal(t, expect, readAll(or)) + + // commutativity test + or2 := NewOr(NewStatic(data[1], false), NewStatic(data[0], false)) + assert.Equal(t, expect, readAll(or2)) } func TestNodeNAnd(t *testing.T) { @@ -59,6 +67,10 @@ func TestNodeOrReverse(t *testing.T) { expect := []uint32{14, 13, 9, 8, 7, 6, 5, 3, 2, 1} or := NewOr(NewStatic(data[0], true), NewStatic(data[1], true)) assert.Equal(t, expect, readAll(or)) + + // commutativity test + or2 := NewOr(NewStatic(data[0], true), NewStatic(data[1], true)) + assert.Equal(t, expect, readAll(or2)) } func TestNodeNAndReverse(t *testing.T) { From 3678a3a761095ae64d7f123e5f109e15746340c7 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Wed, 18 Mar 2026 17:31:28 +0400 Subject: [PATCH 19/20] review fixes: make masks private --- node/lid.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/node/lid.go b/node/lid.go index 36463b8c..51d0c356 100644 --- a/node/lid.go +++ b/node/lid.go @@ -6,8 +6,8 @@ import ( ) const ( - DescMask = uint32(0) - AscMask = uint32(0xFFFFFFFF) + descMask = uint32(0) + ascMask = uint32(0xFFFFFFFF) ) // LID is an encoded representation of LID and reverse flag made specifically for fast compare operations. @@ -29,15 +29,15 @@ func NullLID() LID { func NewLIDDesc(lid uint32) LID { return LID{ lid: lid, - mask: DescMask, + mask: descMask, } } // NewLIDAsc returns LIDs for asc sort order func NewLIDAsc(lid uint32) LID { return LID{ - lid: lid ^ AscMask, - mask: AscMask, + lid: lid ^ ascMask, + mask: ascMask, } } @@ -70,5 +70,5 @@ func (c LID) IsNull() bool { } func (c LID) String() string { - return fmt.Sprintf("%d, reverse=%t", c.Unpack(), c.mask == AscMask) + return fmt.Sprintf("%d, reverse=%t", c.Unpack(), c.mask == ascMask) } From 1e602f6ca3faca4ae59d94004c87b6445a2adc90 Mon Sep 17 00:00:00 2001 From: Andrei Cheboksarov <37665782+cheb0@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:22:57 +0400 Subject: [PATCH 20/20] rename --- frac/processor/aggregator_test.go | 26 +++++++++++----------- frac/processor/eval_tree.go | 8 +++---- frac/sealed/lids/iterator_asc.go | 2 +- frac/sealed/lids/iterator_desc.go | 2 +- node/bench_test.go | 4 ++-- node/lid.go | 10 ++++----- node/lid_test.go | 36 +++++++++++++++---------------- node/node_static.go | 8 +++---- node/node_test.go | 12 +++++------ 9 files changed, 54 insertions(+), 54 deletions(-) diff --git a/frac/processor/aggregator_test.go b/frac/processor/aggregator_test.go index 712af89c..b0a173d3 100644 --- a/frac/processor/aggregator_test.go +++ b/frac/processor/aggregator_test.go @@ -32,7 +32,7 @@ func TestSingleSourceCountAggregator(t *testing.T) { iter := NewSourcedNodeIterator(source, nil, nil, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) agg := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) for _, id := range searchDocs { - if err := agg.Next(node.NewLIDDesc(id)); err != nil { + if err := agg.Next(node.NewDescLID(id)); err != nil { t.Fatal(err) } } @@ -64,7 +64,7 @@ func TestSingleSourceCountAggregatorWithInterval(t *testing.T) { }) for _, id := range searchDocs { - if err := agg.Next(node.NewLIDDesc(id)); err != nil { + if err := agg.Next(node.NewDescLID(id)); err != nil { t.Fatal(err) } } @@ -105,7 +105,7 @@ func BenchmarkAggDeep(b *testing.B) { for b.Loop() { for _, v := range vals { - if err := n.Next(node.NewLIDDesc(v)); err != nil { + if err := n.Next(node.NewDescLID(v)); err != nil { b.Fatal(err) } } @@ -139,7 +139,7 @@ func BenchmarkAggWide(b *testing.B) { for b.Loop() { for _, v := range vals { - if err := n.Next(node.NewLIDDesc(v)); err != nil { + if err := n.Next(node.NewDescLID(v)); err != nil { b.Fatal(err) } } @@ -186,14 +186,14 @@ func TestTwoSourceAggregator(t *testing.T) { dp := &MockTokenIndex{} field := &MockNode{ Pairs: []IDSourcePair{ - {LID: node.NewLIDDesc(1), Source: 0}, - {LID: node.NewLIDDesc(2), Source: 1}, + {LID: node.NewDescLID(1), Source: 0}, + {LID: node.NewDescLID(2), Source: 1}, }, } groupBy := &MockNode{ Pairs: []IDSourcePair{ - {LID: node.NewLIDDesc(1), Source: 0}, - {LID: node.NewLIDDesc(2), Source: 1}, + {LID: node.NewDescLID(1), Source: 0}, + {LID: node.NewDescLID(2), Source: 1}, }, } @@ -207,8 +207,8 @@ func TestTwoSourceAggregator(t *testing.T) { ) // Call Next for two data points. - r.NoError(aggregator.Next(node.NewLIDDesc(1))) - r.NoError(aggregator.Next(node.NewLIDDesc(2))) + r.NoError(aggregator.Next(node.NewDescLID(1))) + r.NoError(aggregator.Next(node.NewDescLID(2))) // Verify countBySource map. expectedCountBySource := map[twoSources]int64{ @@ -248,14 +248,14 @@ func TestSingleTreeCountAggregator(t *testing.T) { dp := &MockTokenIndex{} field := &MockNode{ Pairs: []IDSourcePair{ - {LID: node.NewLIDDesc(1), Source: 0}, + {LID: node.NewDescLID(1), Source: 0}, }, } iter := NewSourcedNodeIterator(field, dp, []uint32{0}, iteratorLimit{limit: 0, err: consts.ErrTooManyGroupTokens}) aggregator := NewSingleSourceCountAggregator(iter, provideExtractTimeFunc(nil, nil, 0)) - r.NoError(aggregator.Next(node.NewLIDDesc(1))) + r.NoError(aggregator.Next(node.NewDescLID(1))) result, err := aggregator.Aggregate() if err != nil { @@ -297,7 +297,7 @@ func TestAggregatorLimitExceeded(t *testing.T) { var limitIteration int for i, id := range searchDocs { - if err := agg.Next(node.NewLIDDesc(id)); err != nil { + if err := agg.Next(node.NewDescLID(id)); err != nil { limitErr = err limitIteration = i break diff --git a/frac/processor/eval_tree.go b/frac/processor/eval_tree.go index e4aefcba..0f260053 100644 --- a/frac/processor/eval_tree.go +++ b/frac/processor/eval_tree.go @@ -48,11 +48,11 @@ func buildEvalTree(root *parser.ASTNode, minVal, maxVal uint32, stats *searchSta var minLID node.LID var maxLID node.LID if reverse { - minLID = node.NewLIDAsc(maxVal) - maxLID = node.NewLIDAsc(minVal) + minLID = node.NewAscLID(maxVal) + maxLID = node.NewAscLID(minVal) } else { - minLID = node.NewLIDDesc(minVal) - maxLID = node.NewLIDDesc(maxVal) + minLID = node.NewDescLID(minVal) + maxLID = node.NewDescLID(maxVal) } return node.NewNot(children[0], minLID, maxLID), nil } diff --git a/frac/sealed/lids/iterator_asc.go b/frac/sealed/lids/iterator_asc.go index af231cbb..783a3d1f 100644 --- a/frac/sealed/lids/iterator_asc.go +++ b/frac/sealed/lids/iterator_asc.go @@ -70,5 +70,5 @@ func (it *IteratorAsc) Next() node.LID { i := len(it.lids) - 1 lid := it.lids[i] it.lids = it.lids[:i] - return node.NewLIDAsc(lid) + return node.NewAscLID(lid) } diff --git a/frac/sealed/lids/iterator_desc.go b/frac/sealed/lids/iterator_desc.go index 16600c24..0485d41c 100644 --- a/frac/sealed/lids/iterator_desc.go +++ b/frac/sealed/lids/iterator_desc.go @@ -69,5 +69,5 @@ func (it *IteratorDesc) Next() node.LID { lid := it.lids[0] it.lids = it.lids[1:] - return node.NewLIDDesc(lid) + return node.NewDescLID(lid) } diff --git a/node/bench_test.go b/node/bench_test.go index 497c5f61..cf517686 100644 --- a/node/bench_test.go +++ b/node/bench_test.go @@ -33,7 +33,7 @@ func BenchmarkNot(b *testing.B) { r := rand.New(rand.NewSource(benchRandSeed)) v, last := Generate(r, s) res := make([]uint32, 0, last+1) - n := NewNot(NewStatic(v, false), NewLIDDesc(1), NewLIDDesc(last)) + n := NewNot(NewStatic(v, false), NewDescLID(1), NewDescLID(last)) for b.Loop() { res = readAllInto(n, res) @@ -50,7 +50,7 @@ func BenchmarkNotEmpty(b *testing.B) { for _, s := range sizes { b.Run(fmt.Sprintf("size=%d", s), func(b *testing.B) { res := make([]uint32, 0, s*2) - n := NewNot(NewStatic(nil, false), NewLIDDesc(1), NewLIDDesc(uint32(s))) + n := NewNot(NewStatic(nil, false), NewDescLID(1), NewDescLID(uint32(s))) for b.Loop() { res = readAllInto(n, res) diff --git a/node/lid.go b/node/lid.go index 51d0c356..620d4c1d 100644 --- a/node/lid.go +++ b/node/lid.go @@ -22,19 +22,19 @@ type LID struct { func NullLID() LID { // order does not matter, as null values are never unpacked - return NewLIDDesc(math.MaxUint32) + return NewDescLID(math.MaxUint32) } -// NewLIDDesc returns LIDs for desc sort order -func NewLIDDesc(lid uint32) LID { +// NewDescLID returns LIDs for desc sort order +func NewDescLID(lid uint32) LID { return LID{ lid: lid, mask: descMask, } } -// NewLIDAsc returns LIDs for asc sort order -func NewLIDAsc(lid uint32) LID { +// NewAscLID returns LIDs for asc sort order +func NewAscLID(lid uint32) LID { return LID{ lid: lid ^ ascMask, mask: ascMask, diff --git a/node/lid_test.go b/node/lid_test.go index 7506e5dd..44ad8004 100644 --- a/node/lid_test.go +++ b/node/lid_test.go @@ -8,13 +8,13 @@ import ( ) func TestLID_Unpack_Desc(t *testing.T) { - x := NewLIDDesc(5) + x := NewDescLID(5) assert.Equal(t, uint32(5), x.Unpack()) - x = NewLIDDesc(math.MaxUint32) + x = NewDescLID(math.MaxUint32) assert.Equal(t, uint32(math.MaxUint32), x.Unpack()) - x = NewLIDDesc(0) + x = NewDescLID(0) assert.Equal(t, uint32(0), x.Unpack()) x = NullLID() @@ -23,13 +23,13 @@ func TestLID_Unpack_Desc(t *testing.T) { } func TestLID_Unpack_Asc(t *testing.T) { - x := NewLIDAsc(5) + x := NewAscLID(5) assert.Equal(t, uint32(5), x.Unpack()) - x = NewLIDAsc(0) + x = NewAscLID(0) assert.Equal(t, uint32(0), x.Unpack()) - x = NewLIDAsc(math.MaxUint32) + x = NewAscLID(math.MaxUint32) assert.Equal(t, uint32(math.MaxUint32), x.Unpack()) x = NullLID() @@ -38,26 +38,26 @@ func TestLID_Unpack_Asc(t *testing.T) { } func TestLID_Eq(t *testing.T) { - assert.Equal(t, NewLIDDesc(6), NewLIDDesc(6)) - assert.Equal(t, NewLIDDesc(math.MaxUint32), NewLIDDesc(math.MaxUint32)) + assert.Equal(t, NewDescLID(6), NewDescLID(6)) + assert.Equal(t, NewDescLID(math.MaxUint32), NewDescLID(math.MaxUint32)) - assert.Equal(t, NewLIDAsc(6), NewLIDAsc(6)) - assert.Equal(t, NewLIDAsc(0), NewLIDAsc(0)) + assert.Equal(t, NewAscLID(6), NewAscLID(6)) + assert.Equal(t, NewAscLID(0), NewAscLID(0)) } func TestLID_Less_Desc(t *testing.T) { - assert.False(t, NewLIDDesc(6).Less(NewLIDDesc(6))) - assert.True(t, NewLIDDesc(6).Less(NewLIDDesc(7))) - assert.True(t, NewLIDDesc(0).Less(NewLIDDesc(5))) + assert.False(t, NewDescLID(6).Less(NewDescLID(6))) + assert.True(t, NewDescLID(6).Less(NewDescLID(7))) + assert.True(t, NewDescLID(0).Less(NewDescLID(5))) - assert.True(t, NewLIDDesc(56000).Less(NullLID())) + assert.True(t, NewDescLID(56000).Less(NullLID())) } func TestLID_Less_Asc(t *testing.T) { // for asc sort order larger values go first (order is reversed), i.e. greater values are "less" than lower values - assert.False(t, NewLIDAsc(6).Less(NewLIDAsc(6))) - assert.True(t, NewLIDAsc(10).Less(NewLIDAsc(1))) - assert.True(t, NewLIDAsc(5).Less(NewLIDAsc(0))) + assert.False(t, NewAscLID(6).Less(NewAscLID(6))) + assert.True(t, NewAscLID(10).Less(NewAscLID(1))) + assert.True(t, NewAscLID(5).Less(NewAscLID(0))) - assert.True(t, NewLIDAsc(56000).Less(NullLID())) + assert.True(t, NewAscLID(56000).Less(NullLID())) } diff --git a/node/node_static.go b/node/node_static.go index d017bab6..d0b17eab 100644 --- a/node/node_static.go +++ b/node/node_static.go @@ -36,21 +36,21 @@ func NewStatic(data []uint32, reverse bool) Node { func (n *staticAsc) Next() LID { // staticAsc is used in docs order desc, hence we return LID with desc order if n.ptr >= len(n.data) { - return NewLIDDesc(math.MaxUint32) + return NewDescLID(math.MaxUint32) } cur := n.data[n.ptr] n.ptr++ - return NewLIDDesc(cur) + return NewDescLID(cur) } func (n *staticDesc) Next() LID { // staticDesc is used in docs order asc, hence we return LID with asc order if n.ptr < 0 { - return NewLIDAsc(0) + return NewAscLID(0) } cur := n.data[n.ptr] n.ptr-- - return NewLIDAsc(cur) + return NewAscLID(cur) } // MakeStaticNodes is currently used only for tests diff --git a/node/node_test.go b/node/node_test.go index d537b025..b6730e21 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -81,37 +81,37 @@ func TestNodeNAndReverse(t *testing.T) { func TestNodeNotReverse(t *testing.T) { expect := []uint32{15, 12, 11, 10, 9, 8, 7, 4, 1} - not := NewNot(NewStatic(data[1], true), NewLIDAsc(15), NewLIDAsc(1)) + not := NewNot(NewStatic(data[1], true), NewAscLID(15), NewAscLID(1)) assert.Equal(t, expect, readAll(not)) } func TestNodeRange(t *testing.T) { expect := []uint32{3, 4, 5, 6, 7, 8, 9, 10} - not := NewRange(NewLIDDesc(3), NewLIDDesc(10)) + not := NewRange(NewDescLID(3), NewDescLID(10)) assert.Equal(t, expect, readAll(not)) } func TestNodeRangeReverse(t *testing.T) { expect := []uint32{10, 9, 8, 7, 6, 5, 4, 3} - not := NewRange(NewLIDAsc(10), NewLIDAsc(3)) + not := NewRange(NewAscLID(10), NewAscLID(3)) assert.Equal(t, expect, readAll(not)) } func TestNodeNotPartialRange(t *testing.T) { expect := []uint32{4, 7, 8, 9, 10} - not := NewNot(NewStatic(data[1], false), NewLIDDesc(3), NewLIDDesc(10)) + not := NewNot(NewStatic(data[1], false), NewDescLID(3), NewDescLID(10)) assert.Equal(t, expect, readAll(not)) } func TestNodeNotPartialRangeReverse(t *testing.T) { expect := []uint32{10, 9, 8, 7, 4} - not := NewNot(NewStatic(data[1], true), NewLIDAsc(10), NewLIDAsc(3)) + not := NewNot(NewStatic(data[1], true), NewAscLID(10), NewAscLID(3)) assert.Equal(t, expect, readAll(not)) } func TestNodeNot(t *testing.T) { expect := []uint32{1, 4, 7, 8, 9, 10, 11, 12, 15} - nand := NewNot(NewStatic(data[1], false), NewLIDDesc(1), NewLIDDesc(15)) + nand := NewNot(NewStatic(data[1], false), NewDescLID(1), NewDescLID(15)) assert.Equal(t, expect, readAll(nand)) }