Skip to content

Commit

Permalink
*: implement some other builtin functions (pingcap#1137)
Browse files Browse the repository at this point in the history
Support ISNULL(), LTRIM(), RTRIM(), LCASE() and UCASE().
Add alias for LOWER() and UPPER().
  • Loading branch information
zyguan authored and shenli committed Apr 23, 2016
1 parent d73a3fa commit 4ced2f7
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 6 deletions.
22 changes: 21 additions & 1 deletion evaluator/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@
package evaluator

import (
"strings"

"github.com/pingcap/tidb/context"
"github.com/pingcap/tidb/util/types"
)

// BuiltinFunc is the function signature for builtin functions
type BuiltinFunc func([]types.Datum, context.Context) (types.Datum, error)

// Func is for a builtin function.
type Func struct {
// F is the specific calling function.
F func([]types.Datum, context.Context) (types.Datum, error)
F BuiltinFunc
// MinArgs is the minimal arguments needed,
MinArgs int
// MaxArgs is the maximal arguments needed, -1 for infinity.
Expand All @@ -36,6 +41,7 @@ type Func struct {
var Funcs = map[string]Func{
// common functions
"coalesce": {builtinCoalesce, 1, -1},
"isnull": {builtinIsNull, 1, 1},

// math functions
"abs": {builtinAbs, 1, 1},
Expand Down Expand Up @@ -78,9 +84,13 @@ var Funcs = map[string]Func{
"left": {builtinLeft, 2, 2},
"length": {builtinLength, 1, 1},
"lower": {builtinLower, 1, 1},
"lcase": {builtinLower, 1, 1},
"ltrim": {trimFn(strings.TrimLeft, spaceChars), 1, 1},
"repeat": {builtinRepeat, 2, 2},
"upper": {builtinUpper, 1, 1},
"ucase": {builtinUpper, 1, 1},
"replace": {builtinReplace, 3, 3},
"rtrim": {trimFn(strings.TrimRight, spaceChars), 1, 1},
"strcmp": {builtinStrcmp, 2, 2},
"convert": {builtinConvert, 2, 2},
"substring": {builtinSubstring, 2, 3},
Expand Down Expand Up @@ -111,3 +121,13 @@ func builtinCoalesce(args []types.Datum, ctx context.Context) (d types.Datum, er
}
return d, nil
}

// See: https://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html#function_isnull
func builtinIsNull(args []types.Datum, _ context.Context) (d types.Datum, err error) {
if args[0].Kind() == types.KindNull {
d.SetInt64(1)
} else {
d.SetInt64(0)
}
return d, nil
}
17 changes: 17 additions & 0 deletions evaluator/builtin_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,23 @@ func builtinTrim(args []types.Datum, _ context.Context) (d types.Datum, err erro
return d, nil
}

// For LTRIM & RTRIM
// See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_ltrim
// See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_rtrim
func trimFn(fn func(string, string) string, cutset string) BuiltinFunc {
return func(args []types.Datum, ctx context.Context) (d types.Datum, err error) {
if args[0].Kind() == types.KindNull {
return d, nil
}
str, err := args[0].ToString()
if err != nil {
return d, errors.Trace(err)
}
d.SetString(fn(str, cutset))
return d, nil
}
}

func trimLeft(str, remstr string) string {
for {
x := strings.TrimPrefix(str, remstr)
Expand Down
24 changes: 24 additions & 0 deletions evaluator/builtin_string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,4 +565,28 @@ func (s *testEvaluatorSuite) TestTrim(c *C) {
c.Assert(err, IsNil)
c.Assert(r, testutil.DatumEquals, types.NewDatum(v.result))
}

for _, v := range []struct {
str, result interface{}
fn string
}{
{" ", "", "LTRIM"},
{" ", "", "RTRIM"},
{"foo0", "foo0", "LTRIM"},
{"bar0", "bar0", "RTRIM"},
{" foo1", "foo1", "LTRIM"},
{"bar1 ", "bar1", "RTRIM"},
{spaceChars + "foo2 ", "foo2 ", "LTRIM"},
{" bar2" + spaceChars, " bar2", "RTRIM"},
{nil, nil, "LTRIM"},
{nil, nil, "RTRIM"},
} {
f := &ast.FuncCallExpr{
FnName: model.NewCIStr(v.fn),
Args: []ast.ExprNode{ast.NewValueExpr(v.str)},
}
r, err := Eval(ctx, f)
c.Assert(err, IsNil)
c.Assert(r, testutil.DatumEquals, types.NewDatum(v.result))
}
}
12 changes: 12 additions & 0 deletions evaluator/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,15 @@ func (s *testEvaluatorSuite) TestCoalesce(c *C) {
c.Assert(err, IsNil)
c.Assert(v, testutil.DatumEquals, types.NewDatum(nil))
}

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

v, err := builtinIsNull(types.MakeDatums(1), nil)
c.Assert(err, IsNil)
c.Assert(v.GetInt64(), Equals, int64(0))

v, err = builtinIsNull(types.MakeDatums(nil), nil)
c.Assert(err, IsNil)
c.Assert(v.GetInt64(), Equals, int64(1))
}
7 changes: 4 additions & 3 deletions optimizer/typeinferer.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,12 @@ func (v *typeInferrer) handleFuncCallExpr(x *ast.FuncCallExpr) {
tp = types.NewFieldType(mysql.TypeDatetime)
tp.Decimal = v.getFsp(x)
case "dayname", "version", "database", "user", "current_user",
"concat", "concat_ws", "left", "lower", "repeat", "replace", "upper", "convert",
"substring", "substring_index", "trim":
"concat", "concat_ws", "left", "lcase", "lower", "repeat",
"replace", "ucase", "upper", "convert", "substring",
"substring_index", "trim", "ltrim", "rtrim":
tp = types.NewFieldType(mysql.TypeVarString)
chs = v.defaultCharset
case "strcmp":
case "strcmp", "isnull":
tp = types.NewFieldType(mysql.TypeLonglong)
case "connection_id":
tp = types.NewFieldType(mysql.TypeLonglong)
Expand Down
6 changes: 6 additions & 0 deletions optimizer/typeinferer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func (ts *testTypeInferrerSuite) TestInferType(c *C) {

{"c1 is true", mysql.TypeLonglong, charset.CharsetBin},
{"c2 is null", mysql.TypeLonglong, charset.CharsetBin},
{"isnull(1/0)", mysql.TypeLonglong, charset.CharsetBin},
{"cast(1 as decimal)", mysql.TypeNewDecimal, charset.CharsetBin},

{"1 and 1", mysql.TypeLonglong, charset.CharsetBin},
Expand Down Expand Up @@ -120,9 +121,14 @@ func (ts *testTypeInferrerSuite) TestInferType(c *C) {
{"CONCAT_WS('-', 'T', 'i', 'DB')", mysql.TypeVarString, "utf8"},
{"left('TiDB', 2)", mysql.TypeVarString, "utf8"},
{"lower('TiDB')", mysql.TypeVarString, "utf8"},
{"lcase('TiDB')", mysql.TypeVarString, "utf8"},
{"repeat('TiDB', 3)", mysql.TypeVarString, "utf8"},
{"replace('TiDB', 'D', 'd')", mysql.TypeVarString, "utf8"},
{"upper('TiDB')", mysql.TypeVarString, "utf8"},
{"ucase('TiDB')", mysql.TypeVarString, "utf8"},
{"trim(' TiDB ')", mysql.TypeVarString, "utf8"},
{"ltrim(' TiDB')", mysql.TypeVarString, "utf8"},
{"rtrim('TiDB ')", mysql.TypeVarString, "utf8"},
{"connection_id()", mysql.TypeLonglong, charset.CharsetBin},
{"if(1>2, 2, 3)", mysql.TypeLonglong, charset.CharsetBin},
{"case c1 when null then 2 when 2 then 1.1 else 1 END", mysql.TypeNewDecimal, charset.CharsetBin},
Expand Down
29 changes: 27 additions & 2 deletions parser/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ import (
interval "INTERVAL"
into "INTO"
is "IS"
isNull "ISNULL"
isolation "ISOLATION"
join "JOIN"
key "KEY"
Expand All @@ -194,8 +195,10 @@ import (
locate "LOCATE"
lock "LOCK"
lower "LOWER"
lcase "LCASE"
lowPriority "LOW_PRIORITY"
lsh "<<"
ltrim "LTRIM"
max "MAX"
maxRows "MAX_ROWS"
microsecond "MICROSECOND"
Expand Down Expand Up @@ -244,6 +247,7 @@ import (
row "ROW"
rowFormat "ROW_FORMAT"
rsh ">>"
rtrim "RTRIM"
schema "SCHEMA"
schemas "SCHEMAS"
second "SECOND"
Expand Down Expand Up @@ -284,6 +288,7 @@ import (
unsigned "UNSIGNED"
update "UPDATE"
upper "UPPER"
ucase "UCASE"
use "USE"
user "USER"
using "USING"
Expand Down Expand Up @@ -1838,9 +1843,9 @@ UnReservedKeyword:
NotKeywordToken:
"ABS" | "ADDDATE" | "ADMIN" | "COALESCE" | "CONCAT" | "CONCAT_WS" | "CONNECTION_ID" | "CUR_TIME"| "COUNT" | "DAY"
| "DATE_ADD" | "DATE_SUB" | "DAYNAME" | "DAYOFMONTH" | "DAYOFWEEK" | "DAYOFYEAR" | "FOUND_ROWS" | "GROUP_CONCAT"| "HOUR"
| "IFNULL" | "LENGTH" | "LOCATE" | "MAX" | "MICROSECOND" | "MIN" | "MINUTE" | "NULLIF" | "MONTH" | "NOW" | "POW"
| "IFNULL" | "ISNULL" | "LCASE" | "LENGTH" | "LOCATE" | "LOWER" | "LTRIM" | "MAX" | "MICROSECOND" | "MIN" | "MINUTE" | "NULLIF" | "MONTH" | "NOW" | "POW"
| "POWER" | "RAND" | "SECOND" | "SQL_CALC_FOUND_ROWS" | "SUBDATE" | "SUBSTRING" %prec lowerThanLeftParen
| "SUBSTRING_INDEX" | "SUM" | "TRIM" | "VERSION" | "WEEKDAY" | "WEEKOFYEAR" | "YEARWEEK"
| "SUBSTRING_INDEX" | "SUM" | "TRIM" | "RTRIM" | "UCASE" | "UPPER" | "VERSION" | "WEEKDAY" | "WEEKOFYEAR" | "YEARWEEK"

/************************************************************************************
*
Expand Down Expand Up @@ -2405,6 +2410,10 @@ FunctionCallNonKeyword:
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: $3.([]ast.ExprNode)}
}
| "ISNULL" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
}
| "LENGTH" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
Expand All @@ -2427,6 +2436,14 @@ FunctionCallNonKeyword:
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
}
| "LCASE" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
}
| "LTRIM" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
}
| "MICROSECOND" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
Expand Down Expand Up @@ -2475,6 +2492,10 @@ FunctionCallNonKeyword:
args := []ast.ExprNode{$3.(ast.ExprNode), $5.(ast.ExprNode), $7.(ast.ExprNode)}
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: args}
}
| "RTRIM" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
}
| "SECOND" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
Expand Down Expand Up @@ -2561,6 +2582,10 @@ FunctionCallNonKeyword:
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
}
| "UCASE" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
}
| "WEEKDAY" '(' Expression ')'
{
$$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}}
Expand Down
4 changes: 4 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ func (s *testParserSuite) TestBuiltin(c *C) {
{`SELECT ASCII(""), ASCII("A"), ASCII(1);`, true},

{`SELECT LOWER("A"), UPPER("a")`, true},
{`SELECT LCASE("A"), UCASE("a")`, true},

{`SELECT REPLACE('www.mysql.com', 'w', 'Ww')`, true},

Expand Down Expand Up @@ -494,6 +495,9 @@ func (s *testParserSuite) TestBuiltin(c *C) {
{`SELECT TRIM(LEADING 'x' FROM 'xxxbarxxx');`, true},
{`SELECT TRIM(BOTH 'x' FROM 'xxxbarxxx');`, true},
{`SELECT TRIM(TRAILING 'xyz' FROM 'barxxyz');`, true},
{`SELECT LTRIM(' foo ');`, true},
{`SELECT RTRIM(' bar ');`, true},

// Repeat
{`SELECT REPEAT("a", 10);`, true},

Expand Down
15 changes: 15 additions & 0 deletions parser/scanner.l
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ insert {i}{n}{s}{e}{r}{t}
interval {i}{n}{t}{e}{r}{v}{a}{l}
into {i}{n}{t}{o}
is {i}{s}
isnull {i}{s}{n}{u}{l}{l}
isolation {i}{s}{o}{l}{a}{t}{i}{o}{n}
join {j}{o}{i}{n}
key {k}{e}{y}
Expand All @@ -408,7 +409,9 @@ local {l}{o}{c}{a}{l}
locate {l}{o}{c}{a}{t}{e}
lock {l}{o}{c}{k}
lower {l}{o}{w}{e}{r}
lcase {l}{c}{a}{s}{e}
low_priority {l}{o}{w}_{p}{r}{i}{o}{r}{i}{t}{y}
ltrim {l}{t}{r}{i}{m}
max_rows {m}{a}{x}_{r}{o}{w}{s}
microsecond {m}{i}{c}{r}{o}{s}{e}{c}{o}{n}{d}
minute {m}{i}{n}{u}{t}{e}
Expand Down Expand Up @@ -447,6 +450,7 @@ rlike {r}{l}{i}{k}{e}
rollback {r}{o}{l}{l}{b}{a}{c}{k}
row {r}{o}{w}
row_format {r}{o}{w}_{f}{o}{r}{m}{a}{t}
rtrim {r}{t}{r}{i}{m}
schema {s}{c}{h}{e}{m}{a}
schemas {s}{c}{h}{e}{m}{a}{s}
second {s}{e}{c}{o}{n}{d}
Expand Down Expand Up @@ -485,6 +489,7 @@ unlock {u}{n}{l}{o}{c}{k}
nullif {n}{u}{l}{l}{i}{f}
update {u}{p}{d}{a}{t}{e}
upper {u}{p}{p}{e}{r}
ucase {u}{c}{a}{s}{e}
utc_date {u}{t}{c}_{d}{a}{t}{e}
value {v}{a}{l}{u}{e}
values {v}{a}{l}{u}{e}{s}
Expand Down Expand Up @@ -834,6 +839,8 @@ year_month {y}{e}{a}{r}_{m}{o}{n}{t}{h}
return ifKwd
{ifnull} lval.item = string(l.val)
return ifNull
{isnull} lval.item = string(l.val)
return isNull
{ignore} return ignore
{index} return index
{inner} return inner
Expand Down Expand Up @@ -864,7 +871,11 @@ year_month {y}{e}{a}{r}_{m}{o}{n}{t}{h}
{lock} return lock
{lower} lval.item = string(l.val)
return lower
{lcase} lval.item = string(l.val)
return lcase
{low_priority} return lowPriority
{ltrim} lval.item = string(l.val)
return ltrim
{max} lval.item = string(l.val)
return max
{max_rows} lval.item = string(l.val)
Expand Down Expand Up @@ -950,6 +961,8 @@ redundant lval.item = string(l.val)
return replace
{references} return references
{rlike} return rlike
{rtrim} lval.item = string(l.val)
return rtrim

{sys_var} lval.item = string(l.val)
return sysVar
Expand Down Expand Up @@ -1007,6 +1020,8 @@ redundant lval.item = string(l.val)
{update} return update
{upper} lval.item = string(l.val)
return upper
{ucase} lval.item = string(l.val)
return ucase
{use} return use
{user} lval.item = string(l.val)
return user
Expand Down

0 comments on commit 4ced2f7

Please sign in to comment.