From b12c90fdd1b89ff295465bb894b1e035d4770445 Mon Sep 17 00:00:00 2001 From: HHans09 <91560812+HHans09@users.noreply.github.com> Date: Mon, 30 Jan 2023 16:08:46 -0500 Subject: [PATCH] Fb-1816 : CharIndex str func (#2216) * rebased and updated * Updates per review comments Rebased * Rebased * Rebased --- sql3/planner/expression.go | 2 + sql3/planner/expressionanalyzercall.go | 2 + sql3/planner/inbuiltfunctionsstring.go | 88 ++++++++++++++++++++++++- sql3/test/defs/defs_string_functions.go | 81 +++++++++++++++++++++++ 4 files changed, 172 insertions(+), 1 deletion(-) diff --git a/sql3/planner/expression.go b/sql3/planner/expression.go index 892b9f176..814c10b1e 100644 --- a/sql3/planner/expression.go +++ b/sql3/planner/expression.go @@ -1522,6 +1522,8 @@ func (n *callPlanExpression) Evaluate(currentRow []interface{}) (interface{}, er return n.EvaluateReplicate(currentRow) case "FORMAT": return n.EvaluateFormat(currentRow) + case "CHARINDEX": + return n.EvaluateCharIndex(currentRow) default: return nil, sql3.NewErrInternalf("unhandled function name '%s'", n.name) } diff --git a/sql3/planner/expressionanalyzercall.go b/sql3/planner/expressionanalyzercall.go index f238a2310..8b090c082 100644 --- a/sql3/planner/expressionanalyzercall.go +++ b/sql3/planner/expressionanalyzercall.go @@ -275,6 +275,8 @@ func (p *ExecutionPlanner) analyzeCallExpression(call *parser.Call, scope parser return p.analyseFunctionPrefixSuffixReplicate(call, scope) case "FORMAT": return p.analyseFunctionFormat(call, scope) + case "CHARINDEX": + return p.analyseFunctionCharIndex(call, scope) default: return nil, sql3.NewErrCallUnknownFunction(call.Name.NamePos.Line, call.Name.NamePos.Column, call.Name.Name) } diff --git a/sql3/planner/inbuiltfunctionsstring.go b/sql3/planner/inbuiltfunctionsstring.go index 2b2e91b11..07cc42dfe 100644 --- a/sql3/planner/inbuiltfunctionsstring.go +++ b/sql3/planner/inbuiltfunctionsstring.go @@ -214,7 +214,6 @@ func (p *ExecutionPlanner) analyseFunctionFormat(call *parser.Call, scope parser 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) } @@ -228,6 +227,31 @@ func (p *ExecutionPlanner) analyseFunctionFormat(call *parser.Call, scope parser 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) @@ -697,3 +721,65 @@ func (n *callPlanExpression) EvaluateFormat(currentRow []interface{}) (interface } 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 +} diff --git a/sql3/test/defs/defs_string_functions.go b/sql3/test/defs/defs_string_functions.go index d52e87693..8b066c36a 100644 --- a/sql3/test/defs/defs_string_functions.go +++ b/sql3/test/defs/defs_string_functions.go @@ -901,5 +901,86 @@ var stringScalarFunctionsTests = TableTest{ ), Compare: CompareExactOrdered, }, + { + name: "CharIndexIncorrectNumberofArguments", + SQLs: sqls( + "select charindex('is','this is great',3,4)", + ), + ExpErr: "'charindex': count of formal parameters (3) does not match count of actual parameters (4)", + }, + { + name: "CharIndexIncorrectTypeofArgumentsforPosition", + SQLs: sqls( + "select charindex('is','this is great','you')", + ), + ExpErr: "integer expression expected", + }, + { + name: "CharIndexIncorrectTypeofArgumentsforInputString", + SQLs: sqls( + "select charindex('is',23,3)", + ), + ExpErr: "string expression expected", + }, + { + name: "CharIndexIncorrectTypeofArgumentsforSubString", + SQLs: sqls( + "select charindex(1,'this is great',3)", + ), + ExpErr: "string expression expected", + }, + { + name: "CharIndexPositionOutofRangewithNegativePosition", + SQLs: sqls( + "select charindex('is','this is great',-1)", + ), + ExpErr: "value '-1' out of range", + }, + { + name: "CharIndexPositionOutofRange", + SQLs: sqls( + "select charindex('is','this is great',15)", + ), + ExpErr: "value '15' out of range", + }, + { + name: "CharIndexofSubstring", + SQLs: sqls( + "Select charindex('is','this is great')", + ), + ExpHdrs: hdrs( + hdr("", fldTypeInt), + ), + ExpRows: rows( + row(int64(2)), + ), + Compare: CompareExactOrdered, + }, + { + name: "CharIndexofSubstringwithPosition", + SQLs: sqls( + "Select charindex('is','this is great',3)", + ), + ExpHdrs: hdrs( + hdr("", fldTypeInt), + ), + ExpRows: rows( + row(int64(5)), + ), + Compare: CompareExactOrdered, + }, + { + name: "CharIndexSubstringNotfound", + SQLs: sqls( + "Select charindex('abc','this is great',3)", + ), + ExpHdrs: hdrs( + hdr("", fldTypeInt), + ), + ExpRows: rows( + row(int64(-1)), + ), + Compare: CompareExactOrdered, + }, }, }