mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
406 lines
12 KiB
Go
406 lines
12 KiB
Go
package planner
|
|
|
|
import (
|
|
"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) {
|
|
//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()) {
|
|
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()) {
|
|
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))
|
|
}
|
|
|
|
if !typeIsString(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()) {
|
|
return nil, sql3.NewErrIntExpressionExpected(call.Args[i].Pos().Line, call.Args[i].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()) {
|
|
return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].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()) {
|
|
return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
|
}
|
|
|
|
// string seperator
|
|
if !typeIsString(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()) {
|
|
return nil, sql3.NewErrIntExpressionExpected(call.Args[2].Pos().Line, call.Args[2].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()) {
|
|
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)
|
|
}
|
|
|
|
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
|
|
runes := []rune(stringArgOne)
|
|
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 (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()) {
|
|
return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
|
}
|
|
|
|
call.ResultDataType = parser.NewDataTypeString()
|
|
|
|
return call, nil
|
|
}
|
|
|
|
// Convert string to Upper case
|
|
func (n *callPlanExpression) EvaluateUpper(currentRow []interface{}) (interface{}, error) {
|
|
stringArgOne, err := evaluateStringArg(n.args[0], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// convert to Upper
|
|
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)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// this takes a sliding window approach to evaluate substring.
|
|
startIndex, err := evaluateIntArg(n.args[1], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if startIndex < 0 {
|
|
return nil, sql3.NewErrValueOutOfRange(0, 0, startIndex)
|
|
}
|
|
|
|
if startIndex >= len(stringArgOne) {
|
|
return nil, sql3.NewErrValueOutOfRange(0, 0, startIndex)
|
|
}
|
|
|
|
endIndex := len(stringArgOne)
|
|
if len(n.args) > 2 {
|
|
ln, err := evaluateIntArg(n.args[2], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
endIndex = startIndex + ln
|
|
}
|
|
|
|
if endIndex < startIndex {
|
|
return nil, sql3.NewErrValueOutOfRange(0, 0, endIndex)
|
|
}
|
|
|
|
if endIndex > len(stringArgOne) {
|
|
return nil, sql3.NewErrValueOutOfRange(0, 0, endIndex)
|
|
}
|
|
|
|
return stringArgOne[startIndex:endIndex], nil
|
|
}
|
|
|
|
func (n *callPlanExpression) EvaluateLower(currentRow []interface{}) (interface{}, error) {
|
|
stringArgOne, err := evaluateStringArg(n.args[0], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return strings.ToLower(stringArgOne), 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
|
|
}
|
|
|
|
// takes a string, seperator and the position `n`, splits the string and returns n'th substring
|
|
func (n *callPlanExpression) EvaluateStringSplit(currentRow []interface{}) (interface{}, error) {
|
|
inputString, err := evaluateStringArg(n.args[0], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
seperator, err := evaluateStringArg(n.args[1], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(n.args) == 2 {
|
|
return strings.Split(inputString, seperator)[0], nil
|
|
}
|
|
pos, err := evaluateIntArg(n.args[2], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res := strings.Split(inputString, seperator)
|
|
if pos <= 0 {
|
|
return res[0], nil
|
|
} else if len(res) > pos {
|
|
return res[pos], nil
|
|
}
|
|
return "", 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
|
|
}
|
|
|
|
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
|
|
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()) {
|
|
return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
|
}
|
|
|
|
call.ResultDataType = parser.NewDataTypeString()
|
|
|
|
return call, nil
|
|
}
|
|
|
|
// Execute Trim function to remove whitespaces from string
|
|
func (n *callPlanExpression) EvaluateTrim(currentRow []interface{}) (interface{}, error) {
|
|
stringArgOne, err := evaluateStringArg(n.args[0], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Trim the whitespace from string
|
|
return strings.TrimSpace(stringArgOne), nil
|
|
}
|
|
|
|
// Execute RTrim function to remove trailing whitespaces from string
|
|
func (n *callPlanExpression) EvaluateRTrim(currentRow []interface{}) (interface{}, error) {
|
|
stringArgOne, err := evaluateStringArg(n.args[0], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Trim the trailing whitespace from string
|
|
return strings.TrimRight(stringArgOne, " "), nil
|
|
}
|
|
|
|
// Execute LTrim function to remove leading whitespaces from string
|
|
func (n *callPlanExpression) EvaluateLTrim(currentRow []interface{}) (interface{}, error) {
|
|
stringArgOne, err := evaluateStringArg(n.args[0], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Trim the leading whitespace from string
|
|
return strings.TrimLeft(stringArgOne, " "), nil
|
|
}
|
|
|
|
func (p *ExecutionPlanner) analyseFunctionPrefixSuffix(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()) {
|
|
return nil, sql3.NewErrStringExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column)
|
|
}
|
|
|
|
if !typeIsInteger(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 (n *callPlanExpression) EvaluatePrefix(currentRow []interface{}) (interface{}, error) {
|
|
stringArgOne, err := evaluateStringArg(n.args[0], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
intArgTwo, err := evaluateIntArg(n.args[1], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// if the length is less than zero, out of range
|
|
if intArgTwo < 0 {
|
|
return nil, sql3.NewErrValueOutOfRange(0, 0, intArgTwo)
|
|
}
|
|
|
|
if intArgTwo > len(stringArgOne) {
|
|
return nil, sql3.NewErrValueOutOfRange(0, 0, intArgTwo)
|
|
}
|
|
|
|
return stringArgOne[:intArgTwo], nil
|
|
}
|
|
|
|
func (n *callPlanExpression) EvaluateSuffix(currentRow []interface{}) (interface{}, error) {
|
|
stringArgOne, err := evaluateStringArg(n.args[0], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
intArgTwo, err := evaluateIntArg(n.args[1], currentRow)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// if the length is less than zero, out of range
|
|
if intArgTwo < 0 {
|
|
return nil, sql3.NewErrValueOutOfRange(0, 0, intArgTwo)
|
|
}
|
|
|
|
if intArgTwo > len(stringArgOne) {
|
|
return nil, sql3.NewErrValueOutOfRange(0, 0, intArgTwo)
|
|
}
|
|
|
|
return stringArgOne[len(stringArgOne)-intArgTwo:], nil
|
|
}
|