Files
tidb/expression/substring.go
2015-10-09 23:16:03 +08:00

292 lines
7.7 KiB
Go

// Copyright 2015 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package expression
import (
"fmt"
"strings"
"github.com/juju/errors"
"github.com/pingcap/tidb/context"
"github.com/pingcap/tidb/util/types"
)
var (
_ Expression = (*FunctionSubstring)(nil)
_ Expression = (*FunctionSubstringIndex)(nil)
_ Expression = (*FunctionLocate)(nil)
)
// FunctionSubstring returns the substring as specified.
// See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_substring
type FunctionSubstring struct {
StrExpr Expression
Pos Expression
Len Expression
}
// Clone implements the Expression Clone interface.
func (f *FunctionSubstring) Clone() Expression {
expr := f.StrExpr.Clone()
nf := &FunctionSubstring{
StrExpr: expr,
Pos: f.Pos,
Len: f.Len,
}
return nf
}
// IsStatic implements the Expression IsStatic interface.
func (f *FunctionSubstring) IsStatic() bool {
return f.StrExpr.IsStatic()
}
// String implements the Expression String interface.
func (f *FunctionSubstring) String() string {
if f.Len != nil {
return fmt.Sprintf("SUBSTRING(%s, %s, %s)", f.StrExpr.String(), f.Pos.String(), f.Len.String())
}
return fmt.Sprintf("SUBSTRING(%s, %s)", f.StrExpr.String(), f.Pos.String())
}
// Eval implements the Expression Eval interface.
func (f *FunctionSubstring) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) {
fs, err := f.StrExpr.Eval(ctx, args)
if err != nil {
return nil, errors.Trace(err)
}
str, ok := fs.(string)
if !ok {
return nil, errors.Errorf("Substring invalid args, need string but get %T", fs)
}
t, err := f.Pos.Eval(ctx, args)
if err != nil {
return nil, errors.Trace(err)
}
p, ok := t.(int64)
if !ok {
return nil, errors.Errorf("Substring invalid pos args, need int but get %T", t)
}
pos := int(p)
length := -1
if f.Len != nil {
t, err := f.Len.Eval(ctx, args)
if err != nil {
return nil, errors.Trace(err)
}
p, ok := t.(int64)
if !ok {
return nil, errors.Errorf("Substring invalid len args, need int but get %T", t)
}
length = int(p)
}
// The forms without a len argument return a substring from string str starting at position pos.
// The forms with a len argument return a substring len characters long from string str, starting at position pos.
// The forms that use FROM are standard SQL syntax. It is also possible to use a negative value for pos.
// In this case, the beginning of the substring is pos characters from the end of the string, rather than the beginning.
// A negative value may be used for pos in any of the forms of this function.
if pos < 0 {
pos = len(str) + pos
} else {
pos--
}
if pos > len(str) || pos <= 0 {
pos = len(str)
}
end := len(str)
if length != -1 {
end = pos + length
}
if end > len(str) {
end = len(str)
}
return str[pos:end], nil
}
// Accept implements Expression Accept interface.
func (f *FunctionSubstring) Accept(v Visitor) (Expression, error) {
return v.VisitFunctionSubstring(f)
}
// FunctionSubstringIndex returns the substring as specified.
// See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_substring-index
type FunctionSubstringIndex struct {
StrExpr Expression
Delim Expression
Count Expression
}
// Clone implements the Expression Clone interface.
func (f *FunctionSubstringIndex) Clone() Expression {
nf := &FunctionSubstringIndex{
StrExpr: f.StrExpr.Clone(),
Delim: f.Delim.Clone(),
Count: f.Count.Clone(),
}
return nf
}
// IsStatic implements the Expression IsStatic interface.
func (f *FunctionSubstringIndex) IsStatic() bool {
return f.StrExpr.IsStatic() && f.Delim.IsStatic() && f.Count.IsStatic()
}
// String implements the Expression String interface.
func (f *FunctionSubstringIndex) String() string {
return fmt.Sprintf("SUBSTRING_INDEX(%s, %s, %s)", f.StrExpr, f.Delim, f.Count)
}
// Eval implements the Expression Eval interface.
func (f *FunctionSubstringIndex) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) {
fs, err := f.StrExpr.Eval(ctx, args)
if err != nil {
return nil, errors.Trace(err)
}
str, ok := fs.(string)
if !ok {
return nil, errors.Errorf("Substring_Index invalid args, need string but get %T", fs)
}
t, err := f.Delim.Eval(ctx, args)
if err != nil {
return nil, errors.Trace(err)
}
delim, ok := t.(string)
if !ok {
return nil, errors.Errorf("Substring_Index invalid delim, need string but get %T", t)
}
t, err = f.Count.Eval(ctx, args)
if err != nil {
return nil, errors.Trace(err)
}
c, err := types.ToInt64(t)
if err != nil {
return nil, errors.Trace(err)
}
count := int(c)
strs := strings.Split(str, delim)
var (
start = 0
end = len(strs)
)
if count > 0 {
// If count is positive, everything to the left of the final delimiter (counting from the left) is returned.
if count < end {
end = count
}
} else {
// If count is negative, everything to the right of the final delimiter (counting from the right) is returned.
count = -count
if count < end {
start = end - count
}
}
substrs := strs[start:end]
return strings.Join(substrs, delim), nil
}
// Accept implements Expression Accept interface.
func (f *FunctionSubstringIndex) Accept(v Visitor) (Expression, error) {
return v.VisitFunctionSubstringIndex(f)
}
// FunctionLocate returns the position of the first occurrence of substring.
// See: https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_locate
type FunctionLocate struct {
Str Expression
SubStr Expression
Pos Expression
}
// Clone implements the Expression Clone interface.
func (f *FunctionLocate) Clone() Expression {
nf := &FunctionLocate{
Str: f.Str.Clone(),
SubStr: f.SubStr.Clone(),
}
if f.Pos != nil {
nf.Pos = f.Pos.Clone()
}
return nf
}
// IsStatic implements the Expression IsStatic interface.
func (f *FunctionLocate) IsStatic() bool {
return f.Str.IsStatic() && f.SubStr.IsStatic() && (f.Pos == nil || f.Pos.IsStatic())
}
// String implements the Expression String interface.
func (f *FunctionLocate) String() string {
if f.Pos != nil {
return fmt.Sprintf("LOCATE(%s, %s, %s)", f.SubStr, f.Str, f.Pos)
}
return fmt.Sprintf("LOCATE(%s, %s)", f.SubStr, f.Str)
}
// Eval implements the Expression Eval interface.
func (f *FunctionLocate) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) {
// eval str
fs, err := f.Str.Eval(ctx, args)
if err != nil {
return nil, errors.Trace(err)
}
if fs == nil {
return nil, nil
}
str, err := types.ToString(fs)
if err != nil {
return nil, errors.Trace(err)
}
// eval substr
fs, err = f.SubStr.Eval(ctx, args)
if err != nil {
return nil, errors.Trace(err)
}
if fs == nil {
return nil, nil
}
substr, err := types.ToString(fs)
if err != nil {
return nil, errors.Trace(err)
}
// eval pos
pos := 0
if f.Pos != nil {
t, err := f.Pos.Eval(ctx, args)
if err != nil {
return nil, errors.Trace(err)
}
p, err := types.ToInt64(t)
if err != nil {
return nil, errors.Trace(err)
}
pos = int(p)
}
// eval locate
if pos < 0 || pos > len(str) {
return 0, errors.Errorf("Locate invalid pos args: %d", pos)
}
str = str[pos:]
i := strings.Index(str, substr)
return i + 1 + pos, nil
}
// Accept implements Expression Accept interface.
func (f *FunctionLocate) Accept(v Visitor) (Expression, error) {
return v.VisitFunctionLocate(f)
}