mukan-consensus/state/indexer/query_range.go
Mukan Erkin Törük c6a41110d1
Some checks are pending
docker-build-cometbft / vars (push) Waiting to run
docker-build-cometbft / build-images (amd64, ubuntu-24.04) (push) Blocked by required conditions
docker-build-cometbft / build-images (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
docker-build-cometbft / merge-images (push) Blocked by required conditions
docker-build-e2e-node / vars (push) Waiting to run
docker-build-e2e-node / build-images (amd64, ubuntu-24.04) (push) Blocked by required conditions
docker-build-e2e-node / build-images (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
docker-build-e2e-node / merge-images (push) Blocked by required conditions
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:20 +03:00

205 lines
4.7 KiB
Go

package indexer
import (
"math/big"
"time"
"git.cw.tr/mukan-network/mukan-consensus/libs/pubsub/query/syntax"
"git.cw.tr/mukan-network/mukan-consensus/types"
)
// QueryRanges defines a mapping between a composite event key and a QueryRange.
//
// e.g.account.number => queryRange{lowerBound: 1, upperBound: 5}
type QueryRanges map[string]QueryRange
// QueryRange defines a range within a query condition.
type QueryRange struct {
LowerBound interface{} // int || time.Time
UpperBound interface{} // int || time.Time
Key string
IncludeLowerBound bool
IncludeUpperBound bool
}
// AnyBound returns either the lower bound if non-nil, otherwise the upper bound.
func (qr QueryRange) AnyBound() interface{} {
if qr.LowerBound != nil {
return qr.LowerBound
}
return qr.UpperBound
}
// LowerBoundValue returns the value for the lower bound. If the lower bound is
// nil, nil will be returned.
func (qr QueryRange) LowerBoundValue() interface{} {
if qr.LowerBound == nil {
return nil
}
if qr.IncludeLowerBound {
return qr.LowerBound
}
switch t := qr.LowerBound.(type) {
case int64:
return t + 1
case *big.Int:
tmp := new(big.Int)
return tmp.Add(t, big.NewInt(1))
case *big.Float:
// For floats we cannot simply add one as the float to float
// comparison is more finegrained.
// When comparing to integers, adding one is also incorrect:
// example: x >100.2 ; x = 101 float increased to 101.2 and condition
// is not satisfied
return t
case time.Time:
return t.Unix() + 1
default:
panic("not implemented")
}
}
// UpperBoundValue returns the value for the upper bound. If the upper bound is
// nil, nil will be returned.
func (qr QueryRange) UpperBoundValue() interface{} {
if qr.UpperBound == nil {
return nil
}
if qr.IncludeUpperBound {
return qr.UpperBound
}
switch t := qr.UpperBound.(type) {
case int64:
return t - 1
case *big.Int:
tmp := new(big.Int)
return tmp.Sub(t, big.NewInt(1))
case *big.Float:
return t
case time.Time:
return t.Unix() - 1
default:
panic("not implemented")
}
}
// LookForRangesWithHeight returns a mapping of QueryRanges and the matching indexes in
// the provided query conditions.
func LookForRangesWithHeight(conditions []syntax.Condition) (queryRange QueryRanges, indexes []int, heightRange QueryRange) {
queryRange = make(QueryRanges)
for i, c := range conditions {
if IsRangeOperation(c.Op) {
heightKey := c.Tag == types.BlockHeightKey || c.Tag == types.TxHeightKey
r, ok := queryRange[c.Tag]
if !ok {
r = QueryRange{Key: c.Tag}
if c.Tag == types.BlockHeightKey || c.Tag == types.TxHeightKey {
heightRange = QueryRange{Key: c.Tag}
}
}
switch c.Op {
case syntax.TGt:
if heightKey {
heightRange.LowerBound = conditionArg(c)
}
r.LowerBound = conditionArg(c)
case syntax.TGeq:
r.IncludeLowerBound = true
r.LowerBound = conditionArg(c)
if heightKey {
heightRange.IncludeLowerBound = true
heightRange.LowerBound = conditionArg(c)
}
case syntax.TLt:
r.UpperBound = conditionArg(c)
if heightKey {
heightRange.UpperBound = conditionArg(c)
}
case syntax.TLeq:
r.IncludeUpperBound = true
r.UpperBound = conditionArg(c)
if heightKey {
heightRange.IncludeUpperBound = true
heightRange.UpperBound = conditionArg(c)
}
}
queryRange[c.Tag] = r
indexes = append(indexes, i)
}
}
return queryRange, indexes, heightRange
}
// Deprecated: This function is not used anymore and will be replaced with LookForRangesWithHeight
func LookForRanges(conditions []syntax.Condition) (ranges QueryRanges, indexes []int) {
ranges = make(QueryRanges)
for i, c := range conditions {
if IsRangeOperation(c.Op) {
r, ok := ranges[c.Tag]
if !ok {
r = QueryRange{Key: c.Tag}
}
switch c.Op {
case syntax.TGt:
r.LowerBound = conditionArg(c)
case syntax.TGeq:
r.IncludeLowerBound = true
r.LowerBound = conditionArg(c)
case syntax.TLt:
r.UpperBound = conditionArg(c)
case syntax.TLeq:
r.IncludeUpperBound = true
r.UpperBound = conditionArg(c)
}
ranges[c.Tag] = r
indexes = append(indexes, i)
}
}
return ranges, indexes
}
// IsRangeOperation returns a boolean signifying if a query Operator is a range
// operation or not.
func IsRangeOperation(op syntax.Token) bool {
switch op {
case syntax.TGt, syntax.TGeq, syntax.TLt, syntax.TLeq:
return true
default:
return false
}
}
func conditionArg(c syntax.Condition) interface{} {
if c.Arg == nil {
return nil
}
switch c.Arg.Type {
case syntax.TNumber:
return c.Arg.Number()
case syntax.TTime, syntax.TDate:
return c.Arg.Time()
default:
return c.Arg.Value() // string
}
}