mirror of
https://github.com/chaisql/chai.git
synced 2025-10-06 08:06:55 +08:00
227 lines
3.9 KiB
Go
227 lines
3.9 KiB
Go
package stream
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/chaisql/chai/internal/database"
|
|
"github.com/chaisql/chai/internal/environment"
|
|
"github.com/chaisql/chai/internal/expr"
|
|
)
|
|
|
|
// Range represents a range to select values after or before
|
|
// a given boundary.
|
|
type Range struct {
|
|
Min, Max expr.LiteralExprList
|
|
Columns []string
|
|
// Exclude Min and Max from the results.
|
|
// By default, min and max are inclusive.
|
|
// Exclusive and Exact cannot be set to true at the same time.
|
|
Exclusive bool
|
|
// Used to match an exact value equal to Min.
|
|
// If set to true, Max will be ignored for comparison
|
|
// and for determining the global upper bound.
|
|
Exact bool
|
|
}
|
|
|
|
func (r *Range) Clone() Range {
|
|
return Range{
|
|
Min: expr.Clone(r.Min).(expr.LiteralExprList),
|
|
Max: expr.Clone(r.Max).(expr.LiteralExprList),
|
|
// No need to clone the columns, they are immutable.
|
|
Columns: r.Columns,
|
|
Exclusive: r.Exclusive,
|
|
Exact: r.Exact,
|
|
}
|
|
}
|
|
|
|
func (r *Range) Eval(env *environment.Environment) (*database.Range, error) {
|
|
rng := database.Range{
|
|
Exclusive: r.Exclusive,
|
|
Exact: r.Exact,
|
|
}
|
|
var err error
|
|
|
|
if len(r.Min) > 0 {
|
|
rng.Min, err = r.Min.EvalAll(env)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if len(r.Max) > 0 {
|
|
rng.Max, err = r.Max.EvalAll(env)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &rng, nil
|
|
}
|
|
|
|
func (r *Range) String() string {
|
|
var sb strings.Builder
|
|
|
|
sb.WriteByte('{')
|
|
var needsComa bool
|
|
|
|
if len(r.Min) > 0 {
|
|
sb.WriteString(`"min": `)
|
|
sb.WriteString(r.Min.String())
|
|
needsComa = true
|
|
}
|
|
|
|
if len(r.Max) > 0 {
|
|
if needsComa {
|
|
sb.WriteString(", ")
|
|
}
|
|
sb.WriteString(`"max": `)
|
|
sb.WriteString(r.Max.String())
|
|
needsComa = true
|
|
}
|
|
|
|
if r.Exact {
|
|
if needsComa {
|
|
sb.WriteString(", ")
|
|
}
|
|
sb.WriteString(`"exact": true`)
|
|
needsComa = true
|
|
}
|
|
|
|
if r.Exclusive {
|
|
if needsComa {
|
|
sb.WriteString(", ")
|
|
}
|
|
sb.WriteString(`"exclusive": true`)
|
|
needsComa = true
|
|
}
|
|
|
|
sb.WriteByte('}')
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
func (r *Range) IsEqual(other *Range) bool {
|
|
if r.Exact != other.Exact {
|
|
return false
|
|
}
|
|
|
|
if r.Exclusive != other.Exclusive {
|
|
return false
|
|
}
|
|
|
|
if len(r.Min) != len(other.Min) {
|
|
return false
|
|
}
|
|
|
|
if len(r.Max) != len(other.Max) {
|
|
return false
|
|
}
|
|
|
|
if !r.Min.IsEqual(other.Min) {
|
|
return false
|
|
}
|
|
|
|
if !r.Max.IsEqual(other.Max) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
type Ranges []Range
|
|
|
|
func (r Ranges) Clone() Ranges {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
|
|
clone := make(Ranges, len(r))
|
|
for i := range r {
|
|
clone[i] = r[i].Clone()
|
|
}
|
|
|
|
return clone
|
|
}
|
|
|
|
// Encode each range using the given value encoder.
|
|
func (r Ranges) Eval(env *environment.Environment) ([]*database.Range, error) {
|
|
ranges := make([]*database.Range, 0, len(r))
|
|
|
|
for i := range r {
|
|
rng, err := r[i].Eval(env)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if rng != nil {
|
|
ranges = append(ranges, rng)
|
|
}
|
|
}
|
|
|
|
return ranges, nil
|
|
}
|
|
|
|
// Append rng to r and return the new slice.
|
|
// Duplicate ranges are ignored.
|
|
func (r Ranges) Append(rng Range) Ranges {
|
|
// ensure we don't keep duplicate ranges
|
|
isDuplicate := false
|
|
for _, e := range r {
|
|
if e.IsEqual(&rng) {
|
|
isDuplicate = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if isDuplicate {
|
|
return r
|
|
}
|
|
|
|
return append(r, rng)
|
|
}
|
|
|
|
func (r Ranges) String() string {
|
|
var sb strings.Builder
|
|
|
|
for i, rr := range r {
|
|
if i > 0 {
|
|
sb.WriteString(", ")
|
|
}
|
|
|
|
sb.WriteString(rr.String())
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// Cost is a best effort function to determine the cost of
|
|
// a range lookup.
|
|
func (r Ranges) Cost() int {
|
|
var cost int
|
|
|
|
for _, rng := range r {
|
|
// if we are looking for an exact value
|
|
// increment by 1
|
|
if rng.Exact {
|
|
cost++
|
|
continue
|
|
}
|
|
|
|
// if there are two boundaries, increment by 50
|
|
if len(rng.Min) > 0 && len(rng.Max) > 0 {
|
|
cost += 50
|
|
continue
|
|
}
|
|
|
|
// if there is only one boundary, increment by 100
|
|
if len(rng.Min) > 0 || len(rng.Max) > 0 {
|
|
cost += 100
|
|
continue
|
|
}
|
|
|
|
// if there are no boundaries, increment by 200
|
|
cost += 200
|
|
}
|
|
|
|
return cost
|
|
}
|