569 lines · 13.2 KB · Go
RawDownload
// Copyright (c) 2015-2021 MinIO, Inc.//// This file is part of MinIO Object Storage stack//// This program is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// This program is distributed in the hope that it will be useful// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the// GNU Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with this program.  If not, see <http://www.gnu.org/licenses/>. package sql import (	"errors"	"fmt"	"strconv"	"strings"	"time") // FuncName - SQL function name.type FuncName string // SQL Function name constantsconst (	// Conditionals	sqlFnCoalesce FuncName = "COALESCE"	sqlFnNullIf   FuncName = "NULLIF" 	// Conversion	sqlFnCast FuncName = "CAST" 	// Date and time	sqlFnDateAdd     FuncName = "DATE_ADD"	sqlFnDateDiff    FuncName = "DATE_DIFF"	sqlFnExtract     FuncName = "EXTRACT"	sqlFnToString    FuncName = "TO_STRING"	sqlFnToTimestamp FuncName = "TO_TIMESTAMP"	sqlFnUTCNow      FuncName = "UTCNOW" 	// String	sqlFnCharLength      FuncName = "CHAR_LENGTH"	sqlFnCharacterLength FuncName = "CHARACTER_LENGTH"	sqlFnLower           FuncName = "LOWER"	sqlFnSubstring       FuncName = "SUBSTRING"	sqlFnTrim            FuncName = "TRIM"	sqlFnUpper           FuncName = "UPPER") var (	errUnimplementedCast = errors.New("This cast not yet implemented")	errNonStringTrimArg  = errors.New("TRIM() received a non-string argument")	errNonTimestampArg   = errors.New("Expected a timestamp argument")) func (e *FuncExpr) getFunctionName() FuncName {	switch {	case e.SFunc != nil:		return FuncName(strings.ToUpper(e.SFunc.FunctionName))	case e.Count != nil:		return aggFnCount	case e.Cast != nil:		return sqlFnCast	case e.Substring != nil:		return sqlFnSubstring	case e.Extract != nil:		return sqlFnExtract	case e.Trim != nil:		return sqlFnTrim	case e.DateAdd != nil:		return sqlFnDateAdd	case e.DateDiff != nil:		return sqlFnDateDiff	default:		return ""	}} // evalSQLFnNode assumes that the FuncExpr is not an aggregation// function.func (e *FuncExpr) evalSQLFnNode(r Record, tableAlias string) (res *Value, err error) {	// Handle functions that have phrase arguments	switch e.getFunctionName() {	case sqlFnCast:		expr := e.Cast.Expr		res, err = expr.castTo(r, strings.ToUpper(e.Cast.CastType), tableAlias)		return 	case sqlFnSubstring:		return handleSQLSubstring(r, e.Substring, tableAlias) 	case sqlFnExtract:		return handleSQLExtract(r, e.Extract, tableAlias) 	case sqlFnTrim:		return handleSQLTrim(r, e.Trim, tableAlias) 	case sqlFnDateAdd:		return handleDateAdd(r, e.DateAdd, tableAlias) 	case sqlFnDateDiff:		return handleDateDiff(r, e.DateDiff, tableAlias) 	} 	// For all simple argument functions, we evaluate the arguments here	argVals := make([]*Value, len(e.SFunc.ArgsList))	for i, arg := range e.SFunc.ArgsList {		argVals[i], err = arg.evalNode(r, tableAlias)		if err != nil {			return nil, err		}	} 	switch e.getFunctionName() {	case sqlFnCoalesce:		return coalesce(argVals) 	case sqlFnNullIf:		return nullif(argVals[0], argVals[1]) 	case sqlFnCharLength, sqlFnCharacterLength:		return charlen(argVals[0]) 	case sqlFnLower:		return lowerCase(argVals[0]) 	case sqlFnUpper:		return upperCase(argVals[0]) 	case sqlFnUTCNow:		return handleUTCNow() 	case sqlFnToString, sqlFnToTimestamp:		// TODO: implement		fallthrough 	default:		return nil, errNotImplemented	}} func coalesce(args []*Value) (res *Value, err error) {	for _, arg := range args {		if arg.IsNull() {			continue		}		return arg, nil	}	return FromNull(), nil} func nullif(v1, v2 *Value) (res *Value, err error) {	// Handle Null cases	if v1.IsNull() || v2.IsNull() {		return v1, nil	} 	err = inferTypesForCmp(v1, v2)	if err != nil {		return nil, err	} 	atleastOneNumeric := v1.isNumeric() || v2.isNumeric()	bothNumeric := v1.isNumeric() && v2.isNumeric()	if atleastOneNumeric || !bothNumeric {		return v1, nil	} 	if v1.SameTypeAs(*v2) {		return v1, nil	} 	cmpResult, cmpErr := v1.compareOp(opEq, v2)	if cmpErr != nil {		return nil, cmpErr	} 	if cmpResult {		return FromNull(), nil	} 	return v1, nil} func charlen(v *Value) (*Value, error) {	inferTypeAsString(v)	s, ok := v.ToString()	if !ok {		err := fmt.Errorf("%s/%s expects a string argument", sqlFnCharLength, sqlFnCharacterLength)		return nil, errIncorrectSQLFunctionArgumentType(err)	}	return FromInt(int64(len([]rune(s)))), nil} func lowerCase(v *Value) (*Value, error) {	inferTypeAsString(v)	s, ok := v.ToString()	if !ok {		err := fmt.Errorf("%s expects a string argument", sqlFnLower)		return nil, errIncorrectSQLFunctionArgumentType(err)	}	return FromString(strings.ToLower(s)), nil} func upperCase(v *Value) (*Value, error) {	inferTypeAsString(v)	s, ok := v.ToString()	if !ok {		err := fmt.Errorf("%s expects a string argument", sqlFnUpper)		return nil, errIncorrectSQLFunctionArgumentType(err)	}	return FromString(strings.ToUpper(s)), nil} func handleDateAdd(r Record, d *DateAddFunc, tableAlias string) (*Value, error) {	q, err := d.Quantity.evalNode(r, tableAlias)	if err != nil {		return nil, err	}	inferTypeForArithOp(q)	qty, ok := q.ToFloat()	if !ok {		return nil, fmt.Errorf("QUANTITY must be a numeric argument to %s()", sqlFnDateAdd)	} 	ts, err := d.Timestamp.evalNode(r, tableAlias)	if err != nil {		return nil, err	}	if err = inferTypeAsTimestamp(ts); err != nil {		return nil, err	}	t, ok := ts.ToTimestamp()	if !ok {		return nil, fmt.Errorf("%s() expects a timestamp argument", sqlFnDateAdd)	} 	return dateAdd(strings.ToUpper(d.DatePart), qty, t)} func handleDateDiff(r Record, d *DateDiffFunc, tableAlias string) (*Value, error) {	tval1, err := d.Timestamp1.evalNode(r, tableAlias)	if err != nil {		return nil, err	}	if err = inferTypeAsTimestamp(tval1); err != nil {		return nil, err	}	ts1, ok := tval1.ToTimestamp()	if !ok {		return nil, fmt.Errorf("%s() expects two timestamp arguments", sqlFnDateDiff)	} 	tval2, err := d.Timestamp2.evalNode(r, tableAlias)	if err != nil {		return nil, err	}	if err = inferTypeAsTimestamp(tval2); err != nil {		return nil, err	}	ts2, ok := tval2.ToTimestamp()	if !ok {		return nil, fmt.Errorf("%s() expects two timestamp arguments", sqlFnDateDiff)	} 	return dateDiff(strings.ToUpper(d.DatePart), ts1, ts2)} func handleUTCNow() (*Value, error) {	return FromTimestamp(time.Now().UTC()), nil} func handleSQLSubstring(r Record, e *SubstringFunc, tableAlias string) (val *Value, err error) {	// Both forms `SUBSTRING('abc' FROM 2 FOR 1)` and	// SUBSTRING('abc', 2, 1) are supported. 	// Evaluate the string argument	v1, err := e.Expr.evalNode(r, tableAlias)	if err != nil {		return nil, err	}	inferTypeAsString(v1)	s, ok := v1.ToString()	if !ok {		err := fmt.Errorf("Incorrect argument type passed to %s", sqlFnSubstring)		return nil, errIncorrectSQLFunctionArgumentType(err)	} 	// Assemble other arguments	arg2, arg3 := e.From, e.For	// Check if the second form of substring is being used	if e.From == nil {		arg2, arg3 = e.Arg2, e.Arg3	} 	// Evaluate the FROM argument	v2, err := arg2.evalNode(r, tableAlias)	if err != nil {		return nil, err	}	inferTypeForArithOp(v2)	startIdx, ok := v2.ToInt()	if !ok {		err := fmt.Errorf("Incorrect type for start index argument in %s", sqlFnSubstring)		return nil, errIncorrectSQLFunctionArgumentType(err)	} 	length := -1	// Evaluate the optional FOR argument	if arg3 != nil {		v3, err := arg3.evalNode(r, tableAlias)		if err != nil {			return nil, err		}		inferTypeForArithOp(v3)		lenInt, ok := v3.ToInt()		if !ok {			err := fmt.Errorf("Incorrect type for length argument in %s", sqlFnSubstring)			return nil, errIncorrectSQLFunctionArgumentType(err)		}		length = int(lenInt)		if length < 0 {			err := fmt.Errorf("Negative length argument in %s", sqlFnSubstring)			return nil, errIncorrectSQLFunctionArgumentType(err)		}	} 	res, err := evalSQLSubstring(s, int(startIdx), length)	return FromString(res), err} func handleSQLTrim(r Record, e *TrimFunc, tableAlias string) (res *Value, err error) {	chars := ""	ok := false	if e.TrimChars != nil {		charsV, cerr := e.TrimChars.evalNode(r, tableAlias)		if cerr != nil {			return nil, cerr		}		inferTypeAsString(charsV)		chars, ok = charsV.ToString()		if !ok {			return nil, errNonStringTrimArg		}	} 	fromV, ferr := e.TrimFrom.evalNode(r, tableAlias)	if ferr != nil {		return nil, ferr	}	inferTypeAsString(fromV)	from, ok := fromV.ToString()	if !ok {		return nil, errNonStringTrimArg	} 	result, terr := evalSQLTrim(e.TrimWhere, chars, from)	if terr != nil {		return nil, terr	}	return FromString(result), nil} func handleSQLExtract(r Record, e *ExtractFunc, tableAlias string) (res *Value, err error) {	timeVal, verr := e.From.evalNode(r, tableAlias)	if verr != nil {		return nil, verr	} 	if err = inferTypeAsTimestamp(timeVal); err != nil {		return nil, err	} 	t, ok := timeVal.ToTimestamp()	if !ok {		return nil, errNonTimestampArg	} 	return extract(strings.ToUpper(e.Timeword), t)} func errUnsupportedCast(fromType, toType string) error {	return fmt.Errorf("Cannot cast from %v to %v", fromType, toType)} func errCastFailure(msg string) error {	return fmt.Errorf("Error casting: %s", msg)} // Allowed cast typesconst (	castBool      = "BOOL"	castInt       = "INT"	castInteger   = "INTEGER"	castString    = "STRING"	castFloat     = "FLOAT"	castDecimal   = "DECIMAL"	castNumeric   = "NUMERIC"	castTimestamp = "TIMESTAMP") func (e *Expression) castTo(r Record, castType string, tableAlias string) (res *Value, err error) {	v, err := e.evalNode(r, tableAlias)	if err != nil {		return nil, err	} 	switch castType {	case castInt, castInteger:		i, err := intCast(v)		return FromInt(i), err 	case castFloat:		f, err := floatCast(v)		return FromFloat(f), err 	case castString:		s, err := stringCast(v)		return FromString(s), err 	case castTimestamp:		t, err := timestampCast(v)		return FromTimestamp(t), err 	case castBool:		b, err := boolCast(v)		return FromBool(b), err 	case castDecimal, castNumeric:		fallthrough 	default:		return nil, errUnimplementedCast	}} func intCast(v *Value) (int64, error) {	// This conversion truncates floating point numbers to	// integer.	strToInt := func(s string) (int64, bool) {		i, errI := strconv.ParseInt(s, 10, 64)		if errI == nil {			return i, true		}		f, errF := strconv.ParseFloat(s, 64)		if errF == nil {			return int64(f), true		}		return 0, false	} 	switch x := v.value.(type) {	case float64:		// Truncate fractional part		return int64(x), nil	case int64:		return x, nil	case string:		// Parse as number, truncate floating point if		// needed.		// String might contain trimming spaces, which		// needs to be trimmed.		res, ok := strToInt(strings.TrimSpace(x))		if !ok {			return 0, errCastFailure("could not parse as int")		}		return res, nil	case []byte:		// Parse as number, truncate floating point if		// needed.		// String might contain trimming spaces, which		// needs to be trimmed.		res, ok := strToInt(strings.TrimSpace(string(x)))		if !ok {			return 0, errCastFailure("could not parse as int")		}		return res, nil 	default:		return 0, errUnsupportedCast(v.GetTypeString(), castInt)	}} func floatCast(v *Value) (float64, error) {	switch x := v.value.(type) {	case float64:		return x, nil	case int64:		return float64(x), nil	case string:		f, err := strconv.ParseFloat(strings.TrimSpace(x), 64)		if err != nil {

Lines 1–500 of 569.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t