Files
chaisql/internal/database/iteration.go
2024-01-20 18:47:14 +04:00

171 lines
3.9 KiB
Go

package database
import (
"math"
"github.com/chaisql/chai/internal/object"
"github.com/chaisql/chai/internal/tree"
"github.com/chaisql/chai/internal/types"
)
type Pivot []types.Value
type Range struct {
Min, Max Pivot
Exclusive bool
Exact bool
}
func (r *Range) ToTreeRange(constraints *FieldConstraints, paths []object.Path) (*tree.Range, error) {
var rng tree.Range
var err error
if len(r.Min) > 0 {
for i := range r.Min {
r.Min[i], err = r.Convert(constraints, r.Min[i], paths[i], true)
if err != nil {
return nil, err
}
}
rng.Min = tree.NewKey(r.Min...)
}
if len(r.Max) > 0 {
for i := range r.Max {
r.Max[i], err = r.Convert(constraints, r.Max[i], paths[i], false)
if err != nil {
return nil, err
}
}
rng.Max = tree.NewKey(r.Max...)
}
if r.Exclusive && r.Exact {
panic("exclusive and exact cannot both be true")
}
if r.Exact {
if rng.Max != nil {
panic("cannot use exact with a max range")
}
rng.Max = rng.Min
}
rng.Exclusive = r.Exclusive
return &rng, nil
}
func (r *Range) Convert(constraints *FieldConstraints, v types.Value, p object.Path, isMin bool) (types.Value, error) {
// ensure the operand satisfies all the constraints, index can work only on exact types.
// if a number is encountered, try to convert it to the right type if and only if the conversion
// is lossless.
// if a timestamp is encountered, ensure the field constraint is also a timestamp, otherwise convert it to text.
v, err := constraints.ConvertValueAtPath(p, v, func(v types.Value, path object.Path, targetType types.ValueType) (types.Value, error) {
if v.Type() == types.TypeInteger && targetType == types.TypeDouble {
return object.CastAsDouble(v)
}
if v.Type() == types.TypeDouble && targetType == types.TypeInteger {
f := types.As[float64](v)
if float64(int64(f)) == f {
return object.CastAsInteger(v)
}
if r.Exact {
return v, nil
}
// we want to convert a non rounded double to int in a way that preserves
// comparison logic with the index. ex:
// a > 1.1 -> a >= 2; exclusive -> false
// a >= 1.1 -> a >= 2; exclusive -> false
// a < 1.1 -> a < 2; exclusive -> true
// a <= 1.1 -> a < 2; exclusive -> true
// a BETWEEN 1.1 AND 2.2 -> a >= 2 AND a <= 3; exclusive -> false
// First, we need to ceil the number. Ex: 1.1 -> 2
v = types.NewIntegerValue(int64(math.Ceil(f)))
// Next, we need to convert the boundaries
if isMin {
// (a > 1.1) or (a >= 1.1) must be transformed to (a >= 2)
r.Exclusive = false
} else {
// (a < 1.1) or (a <= 1.1) must be transformed to (a < 2)
// But there is an exception: if we are dealing with both min
// and max boundaries, we are operating a BETWEEN operation,
// meaning that we need to convert a BETWEEN 1.1 AND 2.2 to a >= 2 AND a <= 3,
// and thus have to set exclusive to false.
r.Exclusive = r.Min == nil || len(r.Min) == 0
}
}
if v.Type() == types.TypeTimestamp && targetType == types.TypeText {
return object.CastAsText(v)
}
return v, nil
})
return v, err
}
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
}
for i := range r.Min {
eq, err := r.Min[i].EQ(other.Min[i])
if err != nil || !eq {
return false
}
}
for i := range r.Max {
eq, err := r.Max[i].EQ(other.Max[i])
if err != nil || !eq {
return false
}
}
return true
}
// type Ranges []Range
// // 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)
// }