Skip to content

Commit

Permalink
*: Support builtin function field (pingcap#2449)
Browse files Browse the repository at this point in the history
  • Loading branch information
shenli authored and tiancaiamao committed Jan 15, 2017
1 parent 3044455 commit 6731333
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 2 deletions.
1 change: 1 addition & 0 deletions ast/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const (
Concat = "concat"
ConcatWS = "concat_ws"
Convert = "convert"
Field = "field"
Lcase = "lcase"
Left = "left"
Length = "length"
Expand Down
2 changes: 2 additions & 0 deletions expression/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ var Funcs = map[string]Func{
ast.Concat: {builtinConcat, 1, -1},
ast.ConcatWS: {builtinConcatWS, 2, -1},
ast.Convert: {builtinConvert, 2, 2},
ast.Field: {builtinField, 2, -1},
ast.Lcase: {builtinLower, 1, 1},
ast.Left: {builtinLeft, 2, 2},
ast.Length: {builtinLength, 1, 1},
Expand Down Expand Up @@ -361,6 +362,7 @@ var funcs = map[string]functionClass{
ast.Concat: &concatFunctionClass{baseFunctionClass{ast.Concat, 1, -1}},
ast.ConcatWS: &concatWSFunctionClass{baseFunctionClass{ast.ConcatWS, 2, -1}},
ast.Convert: &convertFunctionClass{baseFunctionClass{ast.Convert, 2, 2}},
ast.Field: &fieldFunctionClass{baseFunctionClass{ast.Field, 2, -1}},
ast.Lcase: &lowerFunctionClass{baseFunctionClass{ast.Lcase, 1, 1}},
ast.Left: &leftFunctionClass{baseFunctionClass{ast.Left, 2, 2}},
ast.Length: &lengthFunctionClass{baseFunctionClass{ast.Length, 1, 1}},
Expand Down
88 changes: 88 additions & 0 deletions expression/builtin_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ var (
_ functionClass = &charFunctionClass{}
_ functionClass = &charLengthFunctionClass{}
_ functionClass = &findInSetFunctionClass{}
_ functionClass = &fieldFunctionClass{}
)

var (
Expand Down Expand Up @@ -1366,3 +1367,90 @@ func builtinFindInSet(args []types.Datum, _ context.Context) (d types.Datum, err
}
return
}

type fieldFunctionClass struct {
baseFunctionClass
}

func (c *fieldFunctionClass) getFunction(args []Expression, ctx context.Context) (builtinFunc, error) {
return &builtinFieldSig{newBaseBuiltinFunc(args, ctx)}, errors.Trace(c.verifyArgs(args))
}

type builtinFieldSig struct {
baseBuiltinFunc
}

func (b *builtinFieldSig) eval(row []types.Datum) (types.Datum, error) {
args, err := b.evalArgs(row)
if err != nil {
return types.Datum{}, errors.Trace(err)
}
return builtinField(args, b.ctx)
}

// See http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_field
// Returns the index (position) of arg0 in the arg1, arg2, arg3, ... list.
// Returns 0 if arg0 is not found.
// If arg0 is NULL, the return value is 0 because NULL fails equality comparison with any value.
// If all arguments are strings, all arguments are compared as strings.
// If all arguments are numbers, they are compared as numbers.
// Otherwise, the arguments are compared as double.
func builtinField(args []types.Datum, ctx context.Context) (d types.Datum, err error) {
d.SetInt64(0)
if args[0].IsNull() {
return
}
var (
pos int64
allString, allNumber bool
newArgs []types.Datum
)
allString, allNumber = true, true
for i := 0; i < len(args) && (allString || allNumber); i++ {
switch args[i].Kind() {
case types.KindInt64, types.KindUint64, types.KindFloat32, types.KindFloat64, types.KindMysqlDecimal:
allString = false
case types.KindString, types.KindBytes:
allNumber = false
default:
allString, allNumber = false, false
}
}
newArgs, err = argsToSpecifiedType(args, allString, allNumber, ctx)
if err != nil {
return d, errors.Trace(err)
}
arg0, sc := newArgs[0], ctx.GetSessionVars().StmtCtx
for i, curArg := range newArgs[1:] {
cmpResult, _ := arg0.CompareDatum(sc, curArg)
if cmpResult == 0 {
pos = int64(i + 1)
break
}
}
d.SetInt64(pos)
return d, errors.Trace(err)
}

// argsToSpecifiedType converts the type of all arguments in args into string type or double type.
func argsToSpecifiedType(args []types.Datum, allString bool, allNumber bool, ctx context.Context) (newArgs []types.Datum, err error) {
if allNumber { // If all arguments are numbers, they can be compared directly without type converting.
return args, nil
}
sc := ctx.GetSessionVars().StmtCtx
newArgs = make([]types.Datum, len(args))
for i, arg := range args {
if allString {
str, err := arg.ToString()
if err != nil {
return newArgs, errors.Trace(err)
}
newArgs[i] = types.NewStringDatum(str)
} else {
// If error occurred when convert arg to float64, ignore it and set f as 0.
f, _ := arg.ToFloat64(sc)
newArgs[i] = types.NewFloat64Datum(f)
}
}
return
}
25 changes: 25 additions & 0 deletions expression/builtin_string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,3 +792,28 @@ func (s *testEvaluatorSuite) TestFindInSet(c *C) {
c.Assert(r, testutil.DatumEquals, types.NewDatum(t.ret))
}
}

func (s *testEvaluatorSuite) TestField(c *C) {
defer testleak.AfterTest(c)()

tbl := []struct {
argLst []interface{}
ret interface{}
}{
{[]interface{}{"ej", "Hej", "ej", "Heja", "hej", "foo"}, int64(2)},
{[]interface{}{"fo", "Hej", "ej", "Heja", "hej", "foo"}, int64(0)},
{[]interface{}{"ej", "Hej", "ej", "Heja", "ej", "hej", "foo"}, int64(2)},
{[]interface{}{1, 2, 3, 11, 1}, int64(4)},
{[]interface{}{nil, 2, 3, 11, 1}, int64(0)},
{[]interface{}{1.1, 2.1, 3.1, 11.1, 1.1}, int64(4)},
{[]interface{}{1.1, "2.1", "3.1", "11.1", "1.1"}, int64(4)},
{[]interface{}{"1.1a", 2.1, 3.1, 11.1, 1.1}, int64(4)},
{[]interface{}{1.10, 0, 11e-1}, int64(2)},
{[]interface{}{"abc", 0, 1, 11.1, 1.1}, int64(1)},
}
for _, t := range tbl {
r, err := builtinField(types.MakeDatums(t.argLst...), s.ctx)
c.Assert(err, IsNil)
c.Assert(r, testutil.DatumEquals, types.NewDatum(t.ret))
}
}
1 change: 1 addition & 0 deletions parser/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ var tokenMap = map[string]int{
"EXPLAIN": explain,
"EXTRACT": extract,
"FALSE": falseKwd,
"FIELD": fieldKwd,
"FIELDS": fields,
"FIND_IN_SET": findInSet,
"FIRST": first,
Expand Down
7 changes: 6 additions & 1 deletion parser/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ import (
dayofyear "DAYOFYEAR"
fromDays "FROM_DAYS"
events "EVENTS"
fieldKwd "FIELD_KWD"
findInSet "FIND_IN_SET"
foundRows "FOUND_ROWS"
fromUnixTime "FROM_UNIXTIME"
Expand Down Expand Up @@ -2146,7 +2147,7 @@ NotKeywordToken:
| "MAX" | "MICROSECOND" | "MIN" | "MINUTE" | "NULLIF" | "MONTH" | "MONTHNAME" | "NOW" | "POW" | "POWER" | "RAND"
| "SECOND" | "SIGN" | "SLEEP" | "SQL_CALC_FOUND_ROWS" | "STR_TO_DATE" | "SUBDATE" | "SUBSTRING" %prec lowerThanLeftParen |
"SUBSTRING_INDEX" | "SUM" | "TRIM" | "RTRIM" | "UCASE" | "UPPER" | "VERSION" | "WEEKDAY" | "WEEKOFYEAR" | "YEARWEEK" | "ROUND"
| "STATS_PERSISTENT" | "GET_LOCK" | "RELEASE_LOCK" | "CEIL" | "CEILING" | "FROM_UNIXTIME" | "TIMEDIFF" | "LN" | "LOG" | "LOG2" | "LOG10"
| "STATS_PERSISTENT" | "GET_LOCK" | "RELEASE_LOCK" | "CEIL" | "CEILING" | "FROM_UNIXTIME" | "TIMEDIFF" | "LN" | "LOG" | "LOG2" | "LOG10" | "FIELD_KWD"

/************************************************************************************
*
Expand Down Expand Up @@ -2683,6 +2684,10 @@ FunctionCallNonKeyword:
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
}
| "FIELD_KWD" '(' ExpressionList ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1), Args: $3.([]ast.ExprNode)}
}
| FunctionNameDateArithMultiForms '(' Expression ',' Expression ')'
{
$$ = &ast.FuncCallExpr{
Expand Down
1 change: 1 addition & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ func (s *testParserSuite) TestBuiltin(c *C) {
{`SELECT CHAR(65);`, true},
{`SELECT CHAR_LENGTH('abc');`, true},
{`SELECT CHARACTER_LENGTH('abc');`, true},
{`SELECT FIELD('ej', 'Hej', 'ej', 'Heja', 'hej', 'foo');`, true},
{`SELECT FIND_IN_SET('foo', 'foo,bar')`, true},
{`SELECT FIND_IN_SET('foo')`, false},

Expand Down
2 changes: 1 addition & 1 deletion plan/typeinferer.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ func (v *typeInferrer) handleFuncCallExpr(x *ast.FuncCallExpr) {
case "connection_id":
tp = types.NewFieldType(mysql.TypeLonglong)
tp.Flag |= mysql.UnsignedFlag
case "find_in_set":
case "find_in_set", ast.Field:
tp = types.NewFieldType(mysql.TypeLonglong)
case "if":
// TODO: fix this
Expand Down
1 change: 1 addition & 0 deletions plan/typeinferer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ func (ts *testTypeInferrerSuite) TestInferType(c *C) {
{"sign(null)", mysql.TypeLonglong, charset.CharsetBin},
{"unix_timestamp()", mysql.TypeLonglong, charset.CharsetBin},
{"unix_timestamp('2015-11-13 10:20:19')", mysql.TypeLonglong, charset.CharsetBin},
{"field('foo', null)", mysql.TypeLonglong, charset.CharsetBin},
{"find_in_set('foo', 'foo,bar')", mysql.TypeLonglong, charset.CharsetBin},
{"find_in_set('foo', null)", mysql.TypeLonglong, charset.CharsetBin},
{"find_in_set(null, 'bar')", mysql.TypeLonglong, charset.CharsetBin},
Expand Down

0 comments on commit 6731333

Please sign in to comment.