From 27963441abf59bf03e85304b3c35cfbdbd193c99 Mon Sep 17 00:00:00 2001 From: rachithrr Date: Thu, 15 Dec 2022 22:04:51 +0530 Subject: [PATCH] FB-1815: Implement CHAR() (#2369) --- sql3/planner/expression.go | 2 + sql3/planner/expressionanalyzercall.go | 2 + sql3/planner/inbuiltfunctionsstring.go | 53 +++++++++++++++++++++---- sql3/test/defs/defs_string_functions.go | 20 ++++++++++ 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/sql3/planner/expression.go b/sql3/planner/expression.go index d41c3d670..52cb9703e 100644 --- a/sql3/planner/expression.go +++ b/sql3/planner/expression.go @@ -1486,6 +1486,8 @@ func (n *callPlanExpression) Evaluate(currentRow []interface{}) (interface{}, er return n.EvaluateUpper(currentRow) case "STRINGSPLIT": return n.EvaluateStringSplit(currentRow) + case "CHAR": + return n.EvaluateChar(currentRow) case "SUBSTRING": return n.EvaluateSubstring(currentRow) case "LOWER": diff --git a/sql3/planner/expressionanalyzercall.go b/sql3/planner/expressionanalyzercall.go index bb13fc548..4eb01a64c 100644 --- a/sql3/planner/expressionanalyzercall.go +++ b/sql3/planner/expressionanalyzercall.go @@ -243,6 +243,8 @@ func (p *ExecutionPlanner) analyzeCallExpression(call *parser.Call, scope parser return p.analyzeFunctionSubtable(call, scope) case "REVERSE": return p.analyseFunctionReverse(call, scope) + case "CHAR": + return p.analyseFunctionChar(call, scope) case "UPPER": return p.analyzeFunctionUpper(call, scope) case "STRINGSPLIT": diff --git a/sql3/planner/inbuiltfunctionsstring.go b/sql3/planner/inbuiltfunctionsstring.go index 9ad34270b..a42c6dcf2 100644 --- a/sql3/planner/inbuiltfunctionsstring.go +++ b/sql3/planner/inbuiltfunctionsstring.go @@ -1,7 +1,6 @@ package planner import ( - "strconv" "strings" "github.com/molecula/featurebase/v3/sql3" @@ -24,6 +23,21 @@ func (p *ExecutionPlanner) analyseFunctionReverse(call *parser.Call, scope parse 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()) { + return nil, sql3.NewErrIntExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) + } + + call.ResultDataType = parser.NewDataTypeString() + + 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)) @@ -145,6 +159,17 @@ func (n *callPlanExpression) EvaluateUpper(currentRow []interface{}) (interface{ return strings.ToUpper(stringArgOne), nil } +func (n *callPlanExpression) EvaluateChar(currentRow []interface{}) (interface{}, error) { + // Get the integer argument from the function call + intArg, err := evaluateIntArg(n.args[0], currentRow) + if err != nil { + return "", err + } + + // Return the character that corresponds to the integer value + return string(rune(intArg)), nil +} + // Takes string, startIndex and length and returns the substring. func (n *callPlanExpression) EvaluateSubstring(currentRow []interface{}) (interface{}, error) { stringArgOne, err := evaluateStringArg(n.args[0], currentRow) @@ -153,9 +178,9 @@ func (n *callPlanExpression) EvaluateSubstring(currentRow []interface{}) (interf } // this takes a sliding window approach to evaluate substring. - startIndex, err := strconv.Atoi(n.args[1].String()) + startIndex, err := evaluateIntArg(n.args[1], currentRow) if err != nil { - return nil, sql3.NewErrInternalf("unexpected type converion %T", n.args[1]) + return nil, err } if startIndex >= len(stringArgOne) { return "", nil @@ -163,9 +188,9 @@ func (n *callPlanExpression) EvaluateSubstring(currentRow []interface{}) (interf endIndex := len(stringArgOne) if len(n.args) > 2 { - ln, err := strconv.Atoi(n.args[2].String()) + ln, err := evaluateIntArg(n.args[2], currentRow) if err != nil { - return nil, sql3.NewErrInternalf("unexpected type converion %T", n.args[1]) + return nil, err } endIndex = startIndex + ln } @@ -224,9 +249,9 @@ func (n *callPlanExpression) EvaluateStringSplit(currentRow []interface{}) (inte if len(n.args) == 2 { return strings.Split(inputString, seperator)[0], nil } - pos, err := strconv.Atoi(n.args[2].String()) + pos, err := evaluateIntArg(n.args[2], currentRow) if err != nil { - return nil, sql3.NewErrInternalf("unexpected type converion %T", n.args[2]) + return nil, err } res := strings.Split(inputString, seperator) @@ -251,6 +276,20 @@ func evaluateStringArg(n types.PlanExpression, currentRow []interface{}) (string return stringArgOne, nil } +func evaluateIntArg(n types.PlanExpression, currentRow []interface{}) (int, error) { + argOneEval, err := n.Evaluate(currentRow) + if err != nil { + return 0, err + } + + intArgOne, ok := argOneEval.(int64) + if !ok { + return 0, sql3.NewErrInternalf("unexpected type converion %T", argOneEval) + } + + return int(intArgOne), 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 diff --git a/sql3/test/defs/defs_string_functions.go b/sql3/test/defs/defs_string_functions.go index a407e4afc..fd75d16e0 100644 --- a/sql3/test/defs/defs_string_functions.go +++ b/sql3/test/defs/defs_string_functions.go @@ -134,6 +134,26 @@ var stringScalarFunctionsTests = TableTest{ ), Compare: CompareExactUnordered, }, + { + name: "CharInt", + SQLs: sqls( + "select char(82)", + ), + ExpHdrs: hdrs( + hdr("", fldTypeString), + ), + ExpRows: rows( + row(string("R")), + ), + Compare: CompareExactUnordered, + }, + { + name: "CharString", + SQLs: sqls( + "select char('R')", + ), + ExpErr: "integer expression expected", + }, { name: "ConvertingStringtoUpper", SQLs: sqls(