FB-1862: Implement Str() scalar string function (#2215)

* Implement STR()
This commit is contained in:
Bruce Baranowski 2023-02-14 15:58:36 -05:00 committed by GitHub
parent 3dcc55203f
commit 5c6361918a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 217 additions and 0 deletions

View file

@ -1558,6 +1558,8 @@ func (n *callPlanExpression) Evaluate(currentRow []interface{}) (interface{}, er
return n.EvaluateFormat(currentRow)
case "CHARINDEX":
return n.EvaluateCharIndex(currentRow)
case "STR":
return n.EvaluateStr(currentRow)
default:
return nil, sql3.NewErrInternalf("unhandled function name '%s'", n.name)
}

View file

@ -278,6 +278,8 @@ func (p *ExecutionPlanner) analyzeCallExpression(ctx context.Context, call *pars
return p.analyseFunctionFormat(call, scope)
case "CHARINDEX":
return p.analyseFunctionCharIndex(call, scope)
case "STR":
return p.analyseFunctionStr(call, scope)
default:
return nil, sql3.NewErrCallUnknownFunction(call.Name.NamePos.Line, call.Name.NamePos.Column, call.Name.Name)
}

View file

@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/featurebasedb/featurebase/v3/pql"
"github.com/featurebasedb/featurebase/v3/sql3"
"github.com/featurebasedb/featurebase/v3/sql3/parser"
)
@ -783,3 +784,85 @@ func (n *callPlanExpression) EvaluateCharIndex(currentRow []interface{}) (interf
}
return int64(res), nil
}
func (p *ExecutionPlanner) analyseFunctionStr(call *parser.Call, scope parser.Statement) (parser.Expr, error) {
if len(call.Args) < 1 || len(call.Args) > 3 {
return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args))
}
targetType := parser.NewDataTypeDecimal(4)
if !typesAreAssignmentCompatible(targetType, call.Args[0].DataType()) {
return nil, sql3.NewErrTypeAssignmentIncompatible(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Args[0].DataType().TypeDescription(), targetType.TypeDescription())
}
// the second and third parameters are optional
for i := 1; i < len(call.Args); i++ {
if typeIsVoid(call.Args[i].DataType()) {
return nil, sql3.NewErrLiteralNullNotAllowed(call.Args[i].Pos().Line, call.Args[i].Pos().Column)
}
if !typeIsInteger(call.Args[i].DataType()) {
return nil, sql3.NewErrIntExpressionExpected(call.Args[i].Pos().Line, call.Args[i].Pos().Column)
}
}
call.ResultDataType = parser.NewDataTypeString()
return call, nil
}
func (n *callPlanExpression) EvaluateStr(currentRow []interface{}) (interface{}, error) {
argOneEval, err := n.args[0].Evaluate(currentRow)
if err != nil {
return "", err
}
if argOneEval == nil {
return nil, nil
}
coercedValue, err := coerceValue(n.args[0].Type(), parser.NewDataTypeDecimal(4), argOneEval, parser.Pos{})
if err != nil {
return "", err
}
decimalArgOne, ok := coercedValue.(pql.Decimal)
if !ok {
return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, coercedValue)
}
intArgTwo := int64(10)
if len(n.args) > 1 {
argEval, err := n.args[1].Evaluate(currentRow)
if err != nil {
return nil, err
}
intArgTwo, ok = argEval.(int64)
if !ok {
return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval)
}
}
intArgThree := int64(0)
if len(n.args) > 2 {
argEval, err := n.args[2].Evaluate(currentRow)
if err != nil {
return nil, err
}
intArgThree, ok = argEval.(int64)
if !ok {
return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval)
}
}
floatValue := decimalArgOne.Float64()
strFormat := fmt.Sprintf("%%%d.%df", intArgTwo, intArgThree)
decimalString := fmt.Sprintf(strFormat, floatValue)
// check if value can be displayed within allocated length
if int64(len(decimalString)) > intArgTwo {
decimalString = ""
for i := int64(0); i < intArgTwo; i++ {
decimalString += "*"
}
}
return decimalString, nil
}

View file

@ -982,5 +982,135 @@ var stringScalarFunctionsTests = TableTest{
),
Compare: CompareExactOrdered,
},
{
name: "StrIntValue",
SQLs: sqls(
"select str(12345)",
),
ExpHdrs: hdrs(
hdr("", fldTypeString),
),
ExpRows: rows(
row(string(" 12345")),
),
Compare: CompareExactOrdered,
},
{
name: "StrIntValueAtEdge",
SQLs: sqls(
"select str(12345, 5)",
),
ExpHdrs: hdrs(
hdr("", fldTypeString),
),
ExpRows: rows(
row(string("12345")),
),
Compare: CompareExactOrdered,
},
{
name: "StrIntValueWithPrecision",
SQLs: sqls(
"select str(12345, 5, 5)",
),
ExpHdrs: hdrs(
hdr("", fldTypeString),
),
ExpRows: rows(
row(string("*****")),
),
Compare: CompareExactOrdered,
},
{
name: "StrDecimalValue",
SQLs: sqls(
"select str(12345.678)",
),
ExpHdrs: hdrs(
hdr("", fldTypeString),
),
ExpRows: rows(
row(string(" 12346")),
),
Compare: CompareExactOrdered,
},
{
name: "StrDecimalValueAtEdge",
SQLs: sqls(
"select str(12345.19, 5)",
),
ExpHdrs: hdrs(
hdr("", fldTypeString),
),
ExpRows: rows(
row(string("12345")),
),
Compare: CompareExactOrdered,
},
{
name: "StrDecimalValueWithPrecision",
SQLs: sqls(
"select str(12345.789, 8, 2)",
),
ExpHdrs: hdrs(
hdr("", fldTypeString),
),
ExpRows: rows(
row(string("12345.79")),
),
Compare: CompareExactOrdered,
},
{
name: "StrNegativeDecimalValueWithPrecision",
SQLs: sqls(
"select str(-2345.789, 8, 2)",
),
ExpHdrs: hdrs(
hdr("", fldTypeString),
),
ExpRows: rows(
row(string("-2345.79")),
),
Compare: CompareExactOrdered,
},
{
name: "TooFewArgumentsforStr",
SQLs: sqls(
"select str()",
),
ExpErr: "'str': count of formal parameters (1) does not match count of actual parameters (0)",
},
{
name: "TooManyArgumentsforStr",
SQLs: sqls(
"select str(1, 1, 1, 1)",
),
ExpErr: "'str': count of formal parameters (1) does not match count of actual parameters (4)",
},
{
name: "StrPrecisionLargerThanValue",
SQLs: sqls(
"select str(1234.99, 10, 200)",
),
ExpHdrs: hdrs(
hdr("", fldTypeString),
),
ExpRows: rows(
row(string("**********")),
),
Compare: CompareExactOrdered,
}, {
name: "StrNull",
SQLs: sqls(
"select str(null)",
),
ExpHdrs: hdrs(
hdr("", fldTypeString),
),
ExpRows: rows(
row(nil),
),
Compare: CompareExactUnordered,
},
},
}