package planner import ( "fmt" "strings" "github.com/featurebasedb/featurebase/v3/pql" "github.com/featurebasedb/featurebase/v3/sql3" "github.com/featurebasedb/featurebase/v3/sql3/parser" ) func (p *ExecutionPlanner) analyseFunctionReverse(call *parser.Call, scope parser.Statement) (parser.Expr, error) { //one argument if len(call.Args) != 1 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } call.ResultDataType = parser.NewDataTypeString() return call, nil } func (p *ExecutionPlanner) analyzeFunctionLower(call *parser.Call, scope parser.Statement) (parser.Expr, error) { if len(call.Args) != 1 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } call.ResultDataType = parser.NewDataTypeString() return call, nil } func (p *ExecutionPlanner) analyzeFunctionUpper(call *parser.Call, scope parser.Statement) (parser.Expr, error) { //one argument for Upper Function if len(call.Args) != 1 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } call.ResultDataType = parser.NewDataTypeString() return call, nil } func (p *ExecutionPlanner) analyseFunctionChar(call *parser.Call, scope parser.Statement) (parser.Expr, error) { //one argument if len(call.Args) != 1 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args)) } if !typeIsInteger(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrIntExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } call.ResultDataType = parser.NewDataTypeString() return call, nil } func (p *ExecutionPlanner) analyseFunctionAscii(call *parser.Call, scope parser.Statement) (parser.Expr, error) { //one argument if len(call.Args) != 1 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } call.ResultDataType = parser.NewDataTypeInt() return call, nil } func (p *ExecutionPlanner) analyseFunctionSubstring(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, 2, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } // the third parameter is optional for i := 1; i < len(call.Args); i++ { if !typeIsInteger(call.Args[i].DataType()) && !typeIsVoid(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 (p *ExecutionPlanner) analyseFunctionReplaceAll(call *parser.Call, scope parser.Statement) (parser.Expr, error) { if len(call.Args) != 3 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 3, len(call.Args)) } // input string if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } // string to find and replace if !typeIsString(call.Args[1].DataType()) && !typeIsVoid(call.Args[1].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[1].Pos().Line, call.Args[1].Pos().Column) } // string to replace with if !typeIsString(call.Args[2].DataType()) && !typeIsVoid(call.Args[2].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[2].Pos().Line, call.Args[2].Pos().Column) } call.ResultDataType = parser.NewDataTypeString() return call, nil } func (p *ExecutionPlanner) analyseFunctionStringSplit(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, 2, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } // string seperator if !typeIsString(call.Args[1].DataType()) && !typeIsVoid(call.Args[1].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[1].Pos().Line, call.Args[1].Pos().Column) } // third argument is the position. optional, defaults to 0 if len(call.Args) == 3 && !typeIsInteger(call.Args[2].DataType()) && !typeIsVoid(call.Args[2].DataType()) { return nil, sql3.NewErrIntExpressionExpected(call.Args[2].Pos().Line, call.Args[2].Pos().Column) } call.ResultDataType = parser.NewDataTypeString() return call, nil } // Analyze function for Trim/RTrim/LTrim func (p *ExecutionPlanner) analyseFunctionTrim(call *parser.Call, scope parser.Statement) (parser.Expr, error) { //one argument for Trim Functions if len(call.Args) != 1 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } call.ResultDataType = parser.NewDataTypeString() return call, nil } func (p *ExecutionPlanner) analyseFunctionPrefixSuffixReplicate(call *parser.Call, scope parser.Statement) (parser.Expr, error) { if len(call.Args) != 2 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 2, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } if !typeIsInteger(call.Args[1].DataType()) && !typeIsVoid(call.Args[1].DataType()) { return nil, sql3.NewErrIntExpressionExpected(call.Args[1].Pos().Line, call.Args[1].Pos().Column) } call.ResultDataType = parser.NewDataTypeString() return call, nil } func (p *ExecutionPlanner) analyseFunctionSpace(call *parser.Call, scope parser.Statement) (parser.Expr, error) { //one argument if len(call.Args) != 1 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args)) } if !typeIsInteger(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrIntExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } call.ResultDataType = parser.NewDataTypeString() return call, nil } func (p *ExecutionPlanner) analyseFunctionLen(call *parser.Call, scope parser.Statement) (parser.Expr, error) { if len(call.Args) != 1 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } call.ResultDataType = parser.NewDataTypeInt() return call, nil } // format(format_string, args...) func (p *ExecutionPlanner) analyseFunctionFormat(call *parser.Call, scope parser.Statement) (parser.Expr, error) { // should have at least one argument to check call.args[0] string if len(call.Args) < 1 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 1, len(call.Args)) } if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } 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) } } call.ResultDataType = parser.NewDataTypeString() return call, nil } // charindex(substring, str, pos) func (p *ExecutionPlanner) analyseFunctionCharIndex(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, 3, len(call.Args)) } // first paramater is the substring for which index needs to be identified if !typeIsString(call.Args[0].DataType()) && !typeIsVoid(call.Args[0].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } // second paramater is the string where we search the position of the substring ( first param) if !typeIsString(call.Args[1].DataType()) && !typeIsVoid(call.Args[1].DataType()) { return nil, sql3.NewErrStringExpressionExpected(call.Args[1].Pos().Line, call.Args[1].Pos().Column) } // the third parameter is the position. optional, defaults to 0 if len(call.Args) == 3 && !typeIsInteger(call.Args[2].DataType()) && !typeIsVoid(call.Args[2].DataType()) { return nil, sql3.NewErrIntExpressionExpected(call.Args[2].Pos().Line, call.Args[2].Pos().Column) } call.ResultDataType = parser.NewDataTypeInt() return call, nil } // EvaluateReverse reverses the string func (n *callPlanExpression) EvaluateReverse(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArg, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } // reverse the string runes := []rune(stringArg) for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { runes[i], runes[j] = runes[j], runes[i] } return string(runes), nil } func (n *callPlanExpression) EvaluateLower(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArg, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } return strings.ToLower(stringArg), nil } // EvaluateUpper converts string to Upper case func (n *callPlanExpression) EvaluateUpper(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArg, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } // convert to Upper return strings.ToUpper(stringArg), nil } func (n *callPlanExpression) EvaluateChar(currentRow []interface{}) (interface{}, error) { // Get the integer argument from the function call argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return 0, err } if argEval == nil { return nil, nil } intArg, ok := argEval.(int64) if !ok { return 0, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } // ascii range is [0-255] if intArg < 0 || intArg > 255 { return nil, sql3.NewErrValueOutOfRange(0, 0, intArg) } // Return the character that corresponds to the integer value return string(rune(intArg)), nil } // EvaluateAscii this takes a string and returns the ascii value. // sthe string should be of the length 1. func (n *callPlanExpression) EvaluateAscii(currentRow []interface{}) (interface{}, error) { // Get the string argument from the function call argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArg, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } if len(stringArg) == 0 { return "", nil } if len(stringArg) != 1 { return nil, sql3.NewErrStringLengthMismatch(0, 0, 1, stringArg) } res := []rune(stringArg) return int64(res[0]), nil } // EvaluateSubstring takes string, startIndex and length and returns the substring. func (n *callPlanExpression) EvaluateSubstring(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArgOne, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } // this takes a sliding window approach to evaluate substring. argEval, err = n.args[1].Evaluate(currentRow) if err != nil { return 0, err } if argEval == nil { return nil, nil } startIndex, ok := argEval.(int64) if !ok { return 0, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } if startIndex < 0 || startIndex >= int64(len(stringArgOne)) { return nil, sql3.NewErrValueOutOfRange(0, 0, startIndex) } endIndex := int64(len(stringArgOne)) if len(n.args) > 2 { argEval, err = n.args[2].Evaluate(currentRow) if err != nil { return 0, err } if argEval == nil { return nil, nil } ln, ok := argEval.(int64) if !ok { return 0, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } endIndex = startIndex + ln } if endIndex < startIndex || endIndex > int64(len(stringArgOne)) { return nil, sql3.NewErrValueOutOfRange(0, 0, endIndex) } return stringArgOne[startIndex:endIndex], nil } // EvaluateReplaceAll takes string, findstring, replacestring. // replaces all occurances of findstring with replacestring func (n *callPlanExpression) EvaluateReplaceAll(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArgOne, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } argEval, err = n.args[1].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArgTwo, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } argEval, err = n.args[2].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArgThree, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } return strings.ReplaceAll(stringArgOne, stringArgTwo, stringArgThree), nil } // EvaluateStringSplit takes a string, seperator and the position `n`, splits the string and returns n'th substring func (n *callPlanExpression) EvaluateStringSplit(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } inputString, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } argEval, err = n.args[1].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } seperator, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } if len(n.args) == 2 { return strings.Split(inputString, seperator)[0], nil } argEval, err = n.args[2].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } pos, ok := argEval.(int64) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } res := strings.Split(inputString, seperator) if pos <= 0 { return res[0], nil } else if int64(len(res)) > pos { return res[pos], nil } return "", nil } // EvaluateTrim function to remove whitespaces from string func (n *callPlanExpression) EvaluateTrim(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArg, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } // Trim the whitespace from string return strings.TrimSpace(stringArg), nil } // EvaluateRTrim function removes trailing whitespaces from string func (n *callPlanExpression) EvaluateRTrim(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArg, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } // Trim the trailing whitespace from string return strings.TrimRight(stringArg, " "), nil } // EvaluateLTrim function removes leading whitespaces from string func (n *callPlanExpression) EvaluateLTrim(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArg, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } // Trim the leading whitespace from string return strings.TrimLeft(stringArg, " "), nil } func (n *callPlanExpression) EvaluatePrefix(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArgOne, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } argEval, err = n.args[1].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } intArgTwo, ok := argEval.(int64) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } if intArgTwo < 0 || intArgTwo > int64(len(stringArgOne)) { return nil, sql3.NewErrValueOutOfRange(0, 0, intArgTwo) } return stringArgOne[:intArgTwo], nil } func (n *callPlanExpression) EvaluateSuffix(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArgOne, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } argEval, err = n.args[1].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } intArgTwo, ok := argEval.(int64) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } if intArgTwo < 0 || intArgTwo > int64(len(stringArgOne)) { return nil, sql3.NewErrValueOutOfRange(0, 0, intArgTwo) } return stringArgOne[int64(len(stringArgOne))-intArgTwo:], nil } func (n *callPlanExpression) EvaluateSpace(currentRow []interface{}) (interface{}, error) { // Get the integer argument from the function call argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } intArg, ok := argEval.(int64) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } // Return a string containing a number of spaces equal to the integer value spaces := "" for i := int64(0); i < intArg; i++ { spaces += " " } return spaces, nil } func (n *callPlanExpression) EvaluateLen(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArg, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } return int64(len([]rune(stringArg))), nil } func (n *callPlanExpression) EvaluateReplicate(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } stringArg, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } argEval, err = n.args[1].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } intArg, ok := argEval.(int64) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } if intArg < 0 { return nil, sql3.NewErrValueOutOfRange(0, 0, intArg) } return strings.Repeat(stringArg, int(intArg)), nil } // EvaluateFormat function formats according to a format specifier and returns resulting string. func (n *callPlanExpression) EvaluateFormat(currentRow []interface{}) (interface{}, error) { // first arg must be a string argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } formatString, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } var args []interface{} // loop, since args can be of any length. for _, arg := range n.args[1:] { argEval, err = arg.Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { // this should never happen. return nil, sql3.NewErrLiteralNullNotAllowed(0, 0) } args = append(args, argEval) } return fmt.Sprintf(formatString, args...), nil } // EvaluateCharIndex function gets the position of the substring in the given string func (n *callPlanExpression) EvaluateCharIndex(currentRow []interface{}) (interface{}, error) { argEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } subString, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } argEval, err = n.args[1].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } inputString, ok := argEval.(string) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } if len(n.args) == 2 { res := strings.Index(inputString, subString) if res > -1 { newpos := int64(res) return int64(newpos), nil } return int64(res), nil } argEval, err = n.args[2].Evaluate(currentRow) if err != nil { return nil, err } if argEval == nil { return nil, nil } pos, ok := argEval.(int64) if !ok { return nil, sql3.NewErrUnexpectedTypeConversion(0, 0, argEval) } if pos < 0 { return nil, sql3.NewErrValueOutOfRange(0, 0, pos) } if pos >= int64(len(inputString)) { return nil, sql3.NewErrValueOutOfRange(0, 0, pos) } res := strings.Index(inputString[pos:], subString) if res > -1 { newpos := int64(res) + pos return int64(newpos), nil } 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 }