From 9548e71f4666952686e10ad83cfb8a5ecb24efb3 Mon Sep 17 00:00:00 2001 From: rachithrr Date: Mon, 12 Dec 2022 22:42:53 +0530 Subject: [PATCH] FB-1805: implement REPLACEALL() (#2349) --- sql3/planner/expression.go | 2 + sql3/planner/expressionanalyzercall.go | 3 +- sql3/planner/inbuiltfunctionsstring.go | 84 ++++++++++++++++++------- sql3/test/defs/defs_string_functions.go | 53 ++++++++++++++++ 4 files changed, 118 insertions(+), 24 deletions(-) diff --git a/sql3/planner/expression.go b/sql3/planner/expression.go index d5602a540..f6b891b31 100644 --- a/sql3/planner/expression.go +++ b/sql3/planner/expression.go @@ -1482,6 +1482,8 @@ func (n *callPlanExpression) Evaluate(currentRow []interface{}) (interface{}, er return n.EvaluateUpper(currentRow) case "SUBSTRING": return n.EvaluateSubstring(currentRow) + case "REPLACEALL": + return n.EvaluateReplaceAll(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 c25a8767d..c3dfed12d 100644 --- a/sql3/planner/expressionanalyzercall.go +++ b/sql3/planner/expressionanalyzercall.go @@ -247,7 +247,8 @@ func (p *ExecutionPlanner) analyzeCallExpression(call *parser.Call, scope parser return p.analyzeFunctionUpper(call, scope) case "SUBSTRING": return p.analyseFunctionSubstring(call, scope) - + case "REPLACEALL": + return p.analyseFunctionReplaceAll(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 e68c813ee..5ede7441f 100644 --- a/sql3/planner/inbuiltfunctionsstring.go +++ b/sql3/planner/inbuiltfunctionsstring.go @@ -1,12 +1,12 @@ package planner import ( - "fmt" "strconv" "strings" "github.com/molecula/featurebase/v3/sql3" "github.com/molecula/featurebase/v3/sql3/parser" + "github.com/molecula/featurebase/v3/sql3/planner/types" ) func (p *ExecutionPlanner) analyseFunctionReverse(call *parser.Call, scope parser.Statement) (parser.Expr, error) { @@ -45,16 +45,33 @@ func (p *ExecutionPlanner) analyseFunctionSubstring(call *parser.Call, scope par return call, nil } -// reverses the string -func (n *callPlanExpression) EvaluateReverse(currentRow []interface{}) (interface{}, error) { - argOneEval, err := n.args[0].Evaluate(currentRow) - if err != nil { - return nil, err +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()) { + 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()) { + return nil, sql3.NewErrStringExpressionExpected(call.Args[1].Pos().Line, call.Args[1].Pos().Column) + } + // string to replace with + if !typeIsString(call.Args[2].DataType()) { + return nil, sql3.NewErrStringExpressionExpected(call.Args[2].Pos().Line, call.Args[2].Pos().Column) } - stringArgOne, ok := argOneEval.(string) - if !ok { - return nil, sql3.NewErrInternalf("unexpected type converion %T", argOneEval) + call.ResultDataType = parser.NewDataTypeString() + + return call, nil +} + +// reverses the string +func (n *callPlanExpression) EvaluateReverse(currentRow []interface{}) (interface{}, error) { + stringArgOne, err := evaluateStringArg(n.args[0], currentRow) + if err != nil { + return nil, err } // reverse the string @@ -82,33 +99,22 @@ func (p *ExecutionPlanner) analyzeFunctionUpper(call *parser.Call, scope parser. // Convert string to Upper case func (n *callPlanExpression) EvaluateUpper(currentRow []interface{}) (interface{}, error) { - argOneEval, err := n.args[0].Evaluate(currentRow) + stringArgOne, err := evaluateStringArg(n.args[0], currentRow) if err != nil { return nil, err } - stringArgOne, ok := argOneEval.(string) - if !ok { - return nil, sql3.NewErrInternalf("unexpected type converion %T", argOneEval) - } // convert to Upper - res := strings.ToUpper(stringArgOne) - return fmt.Sprintf("%s", res), nil + return strings.ToUpper(stringArgOne), nil } // Takes string, startIndex and length and returns the substring. func (n *callPlanExpression) EvaluateSubstring(currentRow []interface{}) (interface{}, error) { - - argOneEval, err := n.args[0].Evaluate(currentRow) + stringArgOne, err := evaluateStringArg(n.args[0], currentRow) if err != nil { return nil, err } - stringArgOne, ok := argOneEval.(string) - if !ok { - return nil, sql3.NewErrInternalf("unexpected type converion %T", argOneEval) - } - // this takes a sliding window approach to evaluate substring. startIndex, err := strconv.Atoi(n.args[1].String()) if err != nil { @@ -140,3 +146,35 @@ func (n *callPlanExpression) EvaluateSubstring(currentRow []interface{}) (interf return stringArgOne[startIndex:endIndex], nil } + +// takes string, findstring, replacestring. +// replaces all occurances of findstring with replacestring +func (n *callPlanExpression) EvaluateReplaceAll(currentRow []interface{}) (interface{}, error) { + stringArgOne, err := evaluateStringArg(n.args[0], currentRow) + if err != nil { + return nil, err + } + stringArgTwo, err := evaluateStringArg(n.args[1], currentRow) + if err != nil { + return nil, err + } + stringArgThree, err := evaluateStringArg(n.args[2], currentRow) + if err != nil { + return nil, err + } + return strings.ReplaceAll(stringArgOne, stringArgTwo, stringArgThree), nil +} + +func evaluateStringArg(n types.PlanExpression, currentRow []interface{}) (string, error) { + argOneEval, err := n.Evaluate(currentRow) + if err != nil { + return "", err + } + + stringArgOne, ok := argOneEval.(string) + if !ok { + return "", sql3.NewErrInternalf("unexpected type converion %T", argOneEval) + } + + return stringArgOne, nil +} diff --git a/sql3/test/defs/defs_string_functions.go b/sql3/test/defs/defs_string_functions.go index 596199cd3..38ebd27ed 100644 --- a/sql3/test/defs/defs_string_functions.go +++ b/sql3/test/defs/defs_string_functions.go @@ -135,5 +135,58 @@ var stringScalarFunctionsTests = TableTest{ ), ExpErr: "string expression expected", }, + { + name: "ReplaceAllString", + SQLs: sqls( + "select replaceall('hello database','data','feature')", + ), + ExpHdrs: hdrs( + hdr("", fldTypeString), + ), + ExpRows: rows( + row(string("hello featurebase")), + ), + Compare: CompareExactUnordered, + }, + { + name: "ReplaceAllStringMultiple", + SQLs: sqls( + "select replaceall('Buffalo Buffalo buffalo buffalo Buffalo', 'Buffalo', 'Buffalo buffalo');", + ), + ExpHdrs: hdrs( + hdr("", fldTypeString), + ), + ExpRows: rows( + row(string("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")), + ), + Compare: CompareExactUnordered, + }, + { + name: "ReplaceAllReverseSubstringUpper", + SQLs: sqls( + "select replaceall(reverse('gnitset'),substring('testing',4),upper('ed'));", + ), + ExpHdrs: hdrs( + hdr("", fldTypeString), + ), + ExpRows: rows( + row(string("testED")), + ), + Compare: CompareExactUnordered, + }, + { + name: "IncorrectArgumentsReplaceAll", + SQLs: sqls( + "select replaceall('ab','b')", + ), + ExpErr: "'replaceall': count of formal parameters (3) does not match count of actual parameters (2)", + }, + { + name: "IncorrectInputforReplaceAll", + SQLs: sqls( + "select replaceall('test','e',1)", + ), + ExpErr: "string expression expected", + }, }, }