198 lines
4.4 KiB
Go
198 lines
4.4 KiB
Go
package queryexpr
|
|
|
|
import (
|
|
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression"
|
|
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
type isTypeInfo struct {
|
|
isAny bool
|
|
attributeType expression.DynamoDBAttributeType
|
|
goTypes []reflect.Type
|
|
}
|
|
|
|
var validIsTypeNames = map[string]isTypeInfo{
|
|
"ANY": {isAny: true},
|
|
"B": {
|
|
attributeType: expression.Binary,
|
|
// TODO
|
|
},
|
|
"BOOL": {
|
|
attributeType: expression.Boolean,
|
|
goTypes: []reflect.Type{reflect.TypeOf(boolExprValue(false))},
|
|
},
|
|
"S": {
|
|
attributeType: expression.String,
|
|
goTypes: []reflect.Type{reflect.TypeOf(stringExprValue(""))},
|
|
},
|
|
"N": {
|
|
attributeType: expression.Number,
|
|
goTypes: []reflect.Type{reflect.TypeOf(int64ExprValue(0)), reflect.TypeOf(bigNumExprValue{})},
|
|
},
|
|
"NULL": {
|
|
attributeType: expression.Null,
|
|
goTypes: []reflect.Type{reflect.TypeOf(nullExprValue{})},
|
|
},
|
|
"L": {
|
|
attributeType: expression.List,
|
|
goTypes: []reflect.Type{reflect.TypeOf(listExprValue{}), reflect.TypeOf(listProxyValue{})},
|
|
},
|
|
"M": {
|
|
attributeType: expression.Map,
|
|
goTypes: []reflect.Type{reflect.TypeOf(mapExprValue{}), reflect.TypeOf(mapProxyValue{})},
|
|
},
|
|
"BS": {
|
|
attributeType: expression.BinarySet,
|
|
// TODO
|
|
},
|
|
"NS": {
|
|
attributeType: expression.NumberSet,
|
|
// TODO
|
|
},
|
|
"SS": {
|
|
attributeType: expression.StringSet,
|
|
// TODO
|
|
},
|
|
}
|
|
|
|
func (a *astIsOp) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, error) {
|
|
leftIR, err := a.Ref.evalToIR(ctx, info)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if a.Value == nil {
|
|
return leftIR, nil
|
|
}
|
|
|
|
nameIR, isNameIR := leftIR.(irNamePath)
|
|
if !isNameIR {
|
|
return nil, OperandNotANameError(a.Ref.String())
|
|
}
|
|
|
|
rightIR, err := a.Value.evalToIR(ctx, info)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
valueIR, isValueIR := rightIR.(irValue)
|
|
if !isValueIR {
|
|
return nil, ValueMustBeLiteralError{}
|
|
}
|
|
strValue, isStringValue := valueIR.exprValue().(stringableExprValue)
|
|
if !isStringValue {
|
|
return nil, ValueMustBeStringError{}
|
|
}
|
|
|
|
typeInfo, isValidType := validIsTypeNames[strings.ToUpper(strValue.asString())]
|
|
if !isValidType {
|
|
return nil, InvalidTypeForIsError{TypeName: strValue.asString()}
|
|
}
|
|
|
|
var ir = irIs{name: nameIR, typeInfo: typeInfo}
|
|
if a.HasNot {
|
|
if typeInfo.isAny {
|
|
ir.hasNot = true
|
|
} else {
|
|
return &irBoolNot{atom: ir}, nil
|
|
}
|
|
}
|
|
return ir, nil
|
|
}
|
|
|
|
func (a *astIsOp) evalItem(ctx *evalContext, item models.Item) (exprValue, error) {
|
|
ref, err := a.Ref.evalItem(ctx, item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if a.Value == nil {
|
|
return ref, nil
|
|
}
|
|
|
|
expTypeVal, err := a.Value.evalItem(ctx, item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
str, canToStr := expTypeVal.(stringableExprValue)
|
|
if !canToStr {
|
|
return nil, ValueMustBeStringError{}
|
|
}
|
|
typeInfo, hasTypeInfo := validIsTypeNames[strings.ToUpper(str.asString())]
|
|
if !hasTypeInfo {
|
|
return nil, InvalidTypeForIsError{TypeName: str.asString()}
|
|
}
|
|
|
|
var resultOfIs bool
|
|
if typeInfo.isAny {
|
|
resultOfIs = ref != undefinedExprValue{}
|
|
} else {
|
|
refType := reflect.TypeOf(ref)
|
|
|
|
for _, t := range typeInfo.goTypes {
|
|
if t.AssignableTo(refType) {
|
|
resultOfIs = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if a.HasNot {
|
|
resultOfIs = !resultOfIs
|
|
}
|
|
return boolExprValue(resultOfIs), nil
|
|
}
|
|
|
|
func (a *astIsOp) canModifyItem(ctx *evalContext, item models.Item) bool {
|
|
if a.Value != nil {
|
|
return false
|
|
}
|
|
return a.Ref.canModifyItem(ctx, item)
|
|
}
|
|
|
|
func (a *astIsOp) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error {
|
|
if a.Value != nil {
|
|
return PathNotSettableError{}
|
|
}
|
|
return a.Ref.setEvalItem(ctx, item, value)
|
|
}
|
|
|
|
func (a *astIsOp) deleteAttribute(ctx *evalContext, item models.Item) error {
|
|
if a.Value != nil {
|
|
return PathNotSettableError{}
|
|
}
|
|
return a.Ref.deleteAttribute(ctx, item)
|
|
}
|
|
|
|
func (a *astIsOp) String() string {
|
|
var sb strings.Builder
|
|
|
|
sb.WriteString(a.Ref.String())
|
|
if a.Value != nil {
|
|
sb.WriteString(" is ")
|
|
if a.HasNot {
|
|
sb.WriteString("not ")
|
|
}
|
|
sb.WriteString(a.Value.String())
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
type irIs struct {
|
|
name nameIRAtom
|
|
hasNot bool
|
|
typeInfo isTypeInfo
|
|
}
|
|
|
|
func (i irIs) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) {
|
|
nb := i.name.calcName(info)
|
|
if i.typeInfo.isAny {
|
|
if i.hasNot {
|
|
return expression.AttributeNotExists(nb), nil
|
|
}
|
|
return expression.AttributeExists(nb), nil
|
|
}
|
|
return expression.AttributeType(nb, i.typeInfo.attributeType), nil
|
|
}
|