blob: e14cee7e7502fc5f855c1afe7e89d9160a4f8213 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package query
import (
"errors"
"fmt"
"math/big"
"reflect"
"strconv"
"v.io/syncbase/v23/syncbase/nosql/internal/query/query_checker"
"v.io/syncbase/v23/syncbase/nosql/internal/query/query_parser"
)
func Eval(k string, v interface{}, e *query_parser.Expression) bool {
if query_checker.IsLogicalOperator(e.Operator) {
return evalLogicalOperators(k, v, e)
} else {
return evalComparisonOperators(k, v, e)
}
}
func evalLogicalOperators(k string, v interface{}, e *query_parser.Expression) bool {
switch e.Operator.Type {
case query_parser.And:
return Eval(k, v, e.Operand1.Expr) && Eval(k, v, e.Operand2.Expr)
case query_parser.Or:
return Eval(k, v, e.Operand1.Expr) || Eval(k, v, e.Operand2.Expr)
default:
// TODO(jkline): Log this logic error and all other similar cases.
return false
}
}
func evalComparisonOperators(k string, v interface{}, e *query_parser.Expression) bool {
lhsValue := resolveOperand(k, v, e.Operand1)
if lhsValue == nil {
return false
}
rhsValue := resolveOperand(k, v, e.Operand2)
if rhsValue == nil {
return false
}
// coerce operands so they are comparable
var err error
lhsValue, rhsValue, err = coerceValues(lhsValue, rhsValue)
if err != nil {
return false // If operands can't be coerced to compare, expr evals to false.
}
// Do the compare
switch lhsValue.Type {
case query_parser.TypBigInt:
return compareBigInts(lhsValue, rhsValue, e.Operator)
case query_parser.TypBigRat:
return compareBigRats(lhsValue, rhsValue, e.Operator)
case query_parser.TypBool:
return compareBools(lhsValue, rhsValue, e.Operator)
case query_parser.TypFloat:
return compareFloats(lhsValue, rhsValue, e.Operator)
case query_parser.TypInt:
return compareInts(lhsValue, rhsValue, e.Operator)
case query_parser.TypStr:
return compareStrings(lhsValue, rhsValue, e.Operator)
case query_parser.TypUint:
return compareUints(lhsValue, rhsValue, e.Operator)
case query_parser.TypObject:
return compareObjects(lhsValue, rhsValue, e.Operator)
}
return false
}
func coerceValues(lhsValue, rhsValue *query_parser.Operand) (*query_parser.Operand, *query_parser.Operand, error) {
var err error
// If either operand is a string, convert the other to a string.
if lhsValue.Type == query_parser.TypStr || rhsValue.Type == query_parser.TypStr {
if lhsValue, err = convertValueToString(lhsValue); err != nil {
return nil, nil, err
}
if rhsValue, err = convertValueToString(rhsValue); err != nil {
return nil, nil, err
}
}
// If either operand is a big rat, convert both to a big rat.
// Also, if one operand is a float and the other is a big int,
// convert both to big rats.
if lhsValue.Type == query_parser.TypBigRat || rhsValue.Type == query_parser.TypBigRat || (lhsValue.Type == query_parser.TypBigInt && rhsValue.Type == query_parser.TypFloat) || (lhsValue.Type == query_parser.TypFloat && rhsValue.Type == query_parser.TypBigInt) {
if lhsValue, err = convertValueToBigRat(lhsValue); err != nil {
return nil, nil, err
}
if rhsValue, err = convertValueToBigRat(rhsValue); err != nil {
return nil, nil, err
}
}
// If either operand is a float, convert the other to a float.
if lhsValue.Type == query_parser.TypFloat || rhsValue.Type == query_parser.TypFloat {
if lhsValue, err = convertValueToFloat(lhsValue); err != nil {
return nil, nil, err
}
if rhsValue, err = convertValueToFloat(rhsValue); err != nil {
return nil, nil, err
}
}
// If either operand is a big int, convert both to a big int.
// Also, if one operand is a uint64 and the other is an int64, convert both to big ints.
if lhsValue.Type == query_parser.TypBigInt || rhsValue.Type == query_parser.TypBigInt || (lhsValue.Type == query_parser.TypUint && rhsValue.Type == query_parser.TypInt) || (lhsValue.Type == query_parser.TypInt && rhsValue.Type == query_parser.TypUint) {
if lhsValue, err = convertValueToBigInt(lhsValue); err != nil {
return nil, nil, err
}
if rhsValue, err = convertValueToBigInt(rhsValue); err != nil {
return nil, nil, err
}
}
// If either operand is an int64, convert the other to int64.
if lhsValue.Type == query_parser.TypInt || rhsValue.Type == query_parser.TypInt {
if lhsValue, err = convertValueToInt(lhsValue); err != nil {
return nil, nil, err
}
if rhsValue, err = convertValueToInt(rhsValue); err != nil {
return nil, nil, err
}
}
// If either operand is an uint64, convert the other to uint64.
if lhsValue.Type == query_parser.TypUint || rhsValue.Type == query_parser.TypUint {
if lhsValue, err = convertValueToUint(lhsValue); err != nil {
return nil, nil, err
}
if rhsValue, err = convertValueToUint(rhsValue); err != nil {
return nil, nil, err
}
}
// Must be the same at this point.
if lhsValue.Type != rhsValue.Type {
return nil, nil, errors.New(fmt.Sprintf("Logic error: expected like types, got: %v, %v", lhsValue, rhsValue))
}
return lhsValue, rhsValue, nil
}
func convertValueToString(o *query_parser.Operand) (*query_parser.Operand, error) {
var c query_parser.Operand
c.Type = query_parser.TypStr
c.Off = o.Off
switch o.Type {
case query_parser.TypBigInt:
c.Str = o.BigInt.String()
case query_parser.TypBigRat:
c.Str = o.BigRat.String()
case query_parser.TypBool:
c.Str = strconv.FormatBool(o.Bool)
case query_parser.TypFloat:
c.Str = strconv.FormatFloat(o.Float, 'f', -1, 64)
case query_parser.TypInt:
c.Str = strconv.FormatInt(o.Int, 10)
case query_parser.TypStr:
c.Str = o.Str
c.HasAltStr = o.HasAltStr // true for type = expressions
c.AltStr = o.AltStr
c.Regex = o.Regex // non-empty for rhs of like expressions
c.CompRegex = o.CompRegex // non-nil for rhs of like expressions
case query_parser.TypUint:
c.Str = strconv.FormatUint(o.Uint, 10)
case query_parser.TypObject:
return nil, errors.New("Cannot convert object to string for comparison.")
default:
// TODO(jkline): Log this logic error and all other similar cases.
return nil, errors.New("Cannot convert operand to string for comparison.")
}
return &c, nil
}
func convertValueToBigRat(o *query_parser.Operand) (*query_parser.Operand, error) {
// operand cannot be string literal.
var c query_parser.Operand
c.Type = query_parser.TypBigRat
switch o.Type {
case query_parser.TypBigInt:
var b big.Rat
c.BigRat = b.SetInt(o.BigInt)
case query_parser.TypBigRat:
c.BigRat = o.BigRat
case query_parser.TypBool:
return nil, errors.New("Cannot convert bool to big.Rat for comparison.")
case query_parser.TypFloat:
var b big.Rat
c.BigRat = b.SetFloat64(o.Float)
case query_parser.TypInt:
c.BigRat = big.NewRat(o.Int, 1)
case query_parser.TypUint:
var bi big.Int
bi.SetUint64(o.Uint)
var br big.Rat
c.BigRat = br.SetInt(&bi)
case query_parser.TypObject:
return nil, errors.New("Cannot convert object to big.Rat for comparison.")
default:
// TODO(jkline): Log this logic error and all other similar cases.
return nil, errors.New("Cannot convert operand to big.Rat for comparison.")
}
return &c, nil
}
func convertValueToFloat(o *query_parser.Operand) (*query_parser.Operand, error) {
// Operand cannot be literal, big.Rat or big.Int
var c query_parser.Operand
c.Type = query_parser.TypFloat
switch o.Type {
case query_parser.TypBool:
return nil, errors.New("Cannot convert bool to float64 for comparison.")
case query_parser.TypFloat:
c.Float = o.Float
case query_parser.TypInt:
c.Float = float64(o.Int)
case query_parser.TypUint:
c.Float = float64(o.Uint)
case query_parser.TypObject:
return nil, errors.New("Cannot convert object to float64 for comparison.")
default:
// TODO(jkline): Log this logic error and all other similar cases.
return nil, errors.New("Cannot convert operand to float64 for comparison.")
}
return &c, nil
}
func convertValueToBigInt(o *query_parser.Operand) (*query_parser.Operand, error) {
// Operand cannot be literal, big.Rat or float.
var c query_parser.Operand
c.Type = query_parser.TypBigInt
switch o.Type {
case query_parser.TypBigInt:
c.BigInt = o.BigInt
case query_parser.TypBool:
return nil, errors.New("Cannot convert bool to big.Int for comparison.")
case query_parser.TypInt:
c.BigInt = big.NewInt(o.Int)
case query_parser.TypUint:
var b big.Int
b.SetUint64(o.Uint)
c.BigInt = &b
case query_parser.TypObject:
return nil, errors.New("Cannot convert object to big.Int for comparison.")
default:
// TODO(jkline): Log this logic error and all other similar cases.
return nil, errors.New("Cannot convert operand to big.Int for comparison.")
}
return &c, nil
}
func convertValueToInt(o *query_parser.Operand) (*query_parser.Operand, error) {
// Operand cannot be literal, big.Rat or float or uint64.
var c query_parser.Operand
c.Type = query_parser.TypInt
switch o.Type {
case query_parser.TypBool:
return nil, errors.New("Cannot convert bool to int64 for comparison.")
case query_parser.TypInt:
c.Int = o.Int
case query_parser.TypObject:
return nil, errors.New("Cannot convert object to int64 for comparison.")
default:
// TODO(jkline): Log this logic error and all other similar cases.
return nil, errors.New("Cannot convert operand to int64 for comparison.")
}
return &c, nil
}
func convertValueToUint(o *query_parser.Operand) (*query_parser.Operand, error) {
// Operand cannot be literal, big.Rat or float or int64.
var c query_parser.Operand
c.Type = query_parser.TypUint
switch o.Type {
case query_parser.TypBool:
return nil, errors.New("Cannot convert bool to int64 for comparison.")
case query_parser.TypUint:
c.Uint = o.Uint
case query_parser.TypObject:
return nil, errors.New("Cannot convert object to int64 for comparison.")
default:
// TODO(jkline): Log this logic error and all other similar cases.
return nil, errors.New("Cannot convert operand to int64 for comparison.")
}
return &c, nil
}
func compareBools(lhsValue, rhsValue *query_parser.Operand, oper *query_parser.BinaryOperator) bool {
switch oper.Type {
case query_parser.Equal:
return lhsValue.Bool == rhsValue.Bool
case query_parser.NotEqual:
return lhsValue.Bool != rhsValue.Bool
default:
// TODO(jkline): Log this logic error and all other similar cases.
return false
}
}
func compareBigInts(lhsValue, rhsValue *query_parser.Operand, oper *query_parser.BinaryOperator) bool {
switch oper.Type {
case query_parser.Equal:
return lhsValue.BigInt.Cmp(rhsValue.BigInt) == 0
case query_parser.NotEqual:
return lhsValue.BigInt.Cmp(rhsValue.BigInt) != 0
case query_parser.LessThan:
return lhsValue.BigInt.Cmp(rhsValue.BigInt) < 0
case query_parser.LessThanOrEqual:
return lhsValue.BigInt.Cmp(rhsValue.BigInt) <= 0
case query_parser.GreaterThan:
return lhsValue.BigInt.Cmp(rhsValue.BigInt) > 0
case query_parser.GreaterThanOrEqual:
return lhsValue.BigInt.Cmp(rhsValue.BigInt) >= 0
default:
// TODO(jkline): Log this logic error and all other similar cases.
return false
}
}
func compareBigRats(lhsValue, rhsValue *query_parser.Operand, oper *query_parser.BinaryOperator) bool {
switch oper.Type {
case query_parser.Equal:
return lhsValue.BigRat.Cmp(rhsValue.BigRat) == 0
case query_parser.NotEqual:
return lhsValue.BigRat.Cmp(rhsValue.BigRat) != 0
case query_parser.LessThan:
return lhsValue.BigRat.Cmp(rhsValue.BigRat) < 0
case query_parser.LessThanOrEqual:
return lhsValue.BigRat.Cmp(rhsValue.BigRat) <= 0
case query_parser.GreaterThan:
return lhsValue.BigRat.Cmp(rhsValue.BigRat) > 0
case query_parser.GreaterThanOrEqual:
return lhsValue.BigRat.Cmp(rhsValue.BigRat) >= 0
default:
// TODO(jkline): Log this logic error and all other similar cases.
return false
}
}
func compareFloats(lhsValue, rhsValue *query_parser.Operand, oper *query_parser.BinaryOperator) bool {
switch oper.Type {
case query_parser.Equal:
return lhsValue.Float == rhsValue.Float
case query_parser.NotEqual:
return lhsValue.Float != rhsValue.Float
case query_parser.LessThan:
return lhsValue.Float < rhsValue.Float
case query_parser.LessThanOrEqual:
return lhsValue.Float <= rhsValue.Float
case query_parser.GreaterThan:
return lhsValue.Float > rhsValue.Float
case query_parser.GreaterThanOrEqual:
return lhsValue.Float >= rhsValue.Float
default:
// TODO(jkline): Log this logic error and all other similar cases.
return false
}
}
func compareInts(lhsValue, rhsValue *query_parser.Operand, oper *query_parser.BinaryOperator) bool {
switch oper.Type {
case query_parser.Equal:
return lhsValue.Int == rhsValue.Int
case query_parser.NotEqual:
return lhsValue.Int != rhsValue.Int
case query_parser.LessThan:
return lhsValue.Int < rhsValue.Int
case query_parser.LessThanOrEqual:
return lhsValue.Int <= rhsValue.Int
case query_parser.GreaterThan:
return lhsValue.Int > rhsValue.Int
case query_parser.GreaterThanOrEqual:
return lhsValue.Int >= rhsValue.Int
default:
// TODO(jkline): Log this logic error and all other similar cases.
return false
}
}
func compareUints(lhsValue, rhsValue *query_parser.Operand, oper *query_parser.BinaryOperator) bool {
switch oper.Type {
case query_parser.Equal:
return lhsValue.Uint == rhsValue.Uint
case query_parser.NotEqual:
return lhsValue.Uint != rhsValue.Uint
case query_parser.LessThan:
return lhsValue.Uint < rhsValue.Uint
case query_parser.LessThanOrEqual:
return lhsValue.Uint <= rhsValue.Uint
case query_parser.GreaterThan:
return lhsValue.Uint > rhsValue.Uint
case query_parser.GreaterThanOrEqual:
return lhsValue.Uint >= rhsValue.Uint
default:
// TODO(jkline): Log this logic error and all other similar cases.
return false
}
}
func compareStrings(lhsValue, rhsValue *query_parser.Operand, oper *query_parser.BinaryOperator) bool {
switch oper.Type {
case query_parser.Equal:
r := lhsValue.Str == rhsValue.Str
// Handle special case for type equal clauses.
// Only the lhs can have the AltStr field set.
if !r && lhsValue.HasAltStr {
r = lhsValue.AltStr == rhsValue.Str
}
return r
case query_parser.NotEqual:
return lhsValue.Str != rhsValue.Str
case query_parser.LessThan:
return lhsValue.Str < rhsValue.Str
case query_parser.LessThanOrEqual:
return lhsValue.Str <= rhsValue.Str
case query_parser.GreaterThan:
return lhsValue.Str > rhsValue.Str
case query_parser.GreaterThanOrEqual:
return lhsValue.Str >= rhsValue.Str
case query_parser.Like:
return rhsValue.CompRegex.MatchString(lhsValue.Str)
case query_parser.NotLike:
return !rhsValue.CompRegex.MatchString(lhsValue.Str)
default:
// TODO(jkline): Log this logic error and all other similar cases.
return false
}
}
func compareObjects(lhsValue, rhsValue *query_parser.Operand, oper *query_parser.BinaryOperator) bool {
switch oper.Type {
case query_parser.Equal:
return reflect.DeepEqual(lhsValue.Object, rhsValue.Object)
case query_parser.NotEqual:
return !reflect.DeepEqual(lhsValue.Object, rhsValue.Object)
default: // other operands are non-sensical
return false
}
}
func resolveOperand(k string, v interface{}, o *query_parser.Operand) *query_parser.Operand {
if o.Type != query_parser.TypField {
return o
}
value, hasAltStr, altStr := ResolveField(k, v, o.Column)
if value == nil {
return nil
}
// Convert value to an operand
var newOp query_parser.Operand
newOp.Off = o.Off
switch value := value.(type) {
case bool:
newOp.Type = query_parser.TypBool
newOp.Bool = value
case int:
newOp.Type = query_parser.TypInt
newOp.Int = int64(value)
case int8:
newOp.Type = query_parser.TypInt
newOp.Int = int64(value)
case int16:
newOp.Type = query_parser.TypInt
newOp.Int = int64(value)
case int32: // rune
newOp.Type = query_parser.TypInt
newOp.Int = int64(value)
case int64:
newOp.Type = query_parser.TypInt
newOp.Int = value
case uint:
newOp.Type = query_parser.TypBigInt
var b big.Int
b.SetUint64(uint64(value))
newOp.BigInt = &b
case uint8: // byte
newOp.Type = query_parser.TypInt
newOp.Int = int64(value)
case uint16:
newOp.Type = query_parser.TypInt
newOp.Int = int64(value)
case uint32:
newOp.Type = query_parser.TypInt
newOp.Int = int64(value)
case uint64:
newOp.Type = query_parser.TypBigInt
var b big.Int
b.SetUint64(value)
newOp.BigInt = &b
case float32:
newOp.Type = query_parser.TypFloat
newOp.Float = float64(value)
case float64:
newOp.Type = query_parser.TypFloat
newOp.Float = value
case string:
newOp.Type = query_parser.TypStr
newOp.Str = value
newOp.HasAltStr = hasAltStr
newOp.AltStr = altStr
case *big.Int:
newOp.Type = query_parser.TypBigInt
newOp.BigInt = value
case *big.Rat:
newOp.Type = query_parser.TypBigRat
newOp.BigRat = value
default: // OpObject for structs, arrays, maps, ...
newOp.Type = query_parser.TypObject
newOp.Object = value
}
return &newOp
}
// Resolve a field. In the special case where a type is evaluated, in addition
// to a string being returned, and alternate string is returned. In this case,
// <string-value>, true, <alt-string> is returned. In all other cases,
// <value>,false,"" is returned.
func ResolveField(k string, v interface{}, f *query_parser.Field) (interface{}, bool, string) {
if query_checker.IsKeyField(f) {
return k, false, ""
}
if query_checker.IsTypeField(f) {
if v == nil {
return nil, false, ""
} else {
// Types evaluate to two strings, Str and AltStr.
// This is because types match on full path or just the name.
name := reflect.ValueOf(v).Type().Name()
return reflect.ValueOf(v).Type().PkgPath() + "." + name, true, name
}
}
object := v
segments := f.Segments
// The first segment will always be v itself, skip it.
for i := 1; i < len(segments); i++ {
// object must be a struct in order to look for the next segment.
if reflect.ValueOf(object).Kind() != reflect.Struct {
return nil, false, "" // field does not exist
}
// Look up the segment in object.
_, ok := reflect.ValueOf(object).Type().FieldByName(segments[i].Value)
if !ok {
return nil, false, "" // field does not exist
}
if !reflect.ValueOf(object).FieldByName(segments[i].Value).CanInterface() {
return nil, false, ""
}
object = reflect.ValueOf(object).FieldByName(segments[i].Value).Interface()
}
return object, false, ""
}
// Evaluate the where clause, substituting false for all expressions involving the key and
// true for all other expressions. If the answer is true, it is possible to satisfy the
// expression for any key. As such, all keys must be fetched.
func CheckIfAllKeysMustBeFetched(e *query_parser.Expression) bool {
switch e.Operator.Type {
case query_parser.And:
return CheckIfAllKeysMustBeFetched(e.Operand1.Expr) && CheckIfAllKeysMustBeFetched(e.Operand2.Expr)
case query_parser.Or:
return CheckIfAllKeysMustBeFetched(e.Operand1.Expr) || CheckIfAllKeysMustBeFetched(e.Operand2.Expr)
default: // =, > >=, <, <=, Like, <>, NotLike
if query_checker.IsKey(e.Operand1) {
return false
} else {
return true
}
}
}
// EvalWhereUsingOnlyKey return type. See that function for details.
type EvalWithKeyResult int
const (
INCLUDE EvalWithKeyResult = iota
EXCLUDE
FETCH_VALUE
)
// Evaluate the where clause to determine if the row should be selected, but do so using only
// the key. Possible returns are:
// INCLUDE: the row should included in the results
// EXCLUDE: the row should NOT be included
// FETCH_VALUE: the value and/or type of the value are required to determine if row should be included.
// The above decision is accomplished by evaluating all expressions which reference the key and
// substituing false for all other expressions. If the result is true, INCLUDE is returned.
// If the result is false, but no other expressions (i.e., expressions which refer to the type
// of the value or the value itself) were encountered, EXCLUDE is returned; else, FETCH_VALUE is
// returned indicating the value must be fetched in order to determine if the row should be included
// in the results.
func EvalWhereUsingOnlyKey(s *query_parser.SelectStatement, k string) EvalWithKeyResult {
if s.Where == nil { // all rows will be in result
return INCLUDE
}
return evalExprUsingOnlyKey(s.Where.Expr, k)
}
func evalExprUsingOnlyKey(e *query_parser.Expression, k string) EvalWithKeyResult {
switch e.Operator.Type {
case query_parser.And:
op1Result := evalExprUsingOnlyKey(e.Operand1.Expr, k)
op2Result := evalExprUsingOnlyKey(e.Operand2.Expr, k)
if op1Result == INCLUDE && op2Result == INCLUDE {
return INCLUDE
} else if op1Result == EXCLUDE || op2Result == EXCLUDE {
// One of the operands evaluated to EXCLUDE.
// As such, the value is not needed to reject the row.
return EXCLUDE
} else {
return FETCH_VALUE
}
case query_parser.Or:
op1Result := evalExprUsingOnlyKey(e.Operand1.Expr, k)
op2Result := evalExprUsingOnlyKey(e.Operand2.Expr, k)
if op1Result == INCLUDE || op2Result == INCLUDE {
return INCLUDE
} else if op1Result == EXCLUDE && op2Result == EXCLUDE {
return EXCLUDE
} else {
return FETCH_VALUE
}
default: // =, > >=, <, <=, Like, <>, NotLike
if !query_checker.IsKey(e.Operand1) {
return FETCH_VALUE
} else {
if evalComparisonOperators(k, nil, e) {
return INCLUDE
} else {
return EXCLUDE
}
}
}
}