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 }