This repository has been archived by the owner on Jan 28, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 109
Add support for CONCAT_WS #500
Merged
Merged
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,6 +75,7 @@ | |
## Functions | ||
- ARRAY_LENGTH | ||
- CONCAT | ||
- CONCAT_WS | ||
- IS_BINARY | ||
- SPLIT | ||
- SUBSTRING | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package function | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"gopkg.in/src-d/go-mysql-server.v0/sql" | ||
) | ||
|
||
// ConcatWithSeparator joins several strings together. | ||
type ConcatWithSeparator struct { | ||
args []sql.Expression | ||
} | ||
|
||
// NewConcatWithSeparator creates a new NewConcatWithSeparator UDF. | ||
func NewConcatWithSeparator(args ...sql.Expression) (sql.Expression, error) { | ||
if len(args) == 0 { | ||
return nil, sql.ErrInvalidArgumentNumber.New("1 or more", 0) | ||
} | ||
|
||
for _, arg := range args { | ||
// Don't perform this check until it's resolved. Otherwise we | ||
// can't get the type for sure. | ||
if !arg.Resolved() { | ||
continue | ||
} | ||
|
||
if len(args) > 1 && sql.IsArray(arg.Type()) { | ||
return nil, ErrConcatArrayWithOthers.New() | ||
} | ||
|
||
if sql.IsTuple(arg.Type()) { | ||
return nil, sql.ErrInvalidType.New("tuple") | ||
} | ||
} | ||
|
||
return &ConcatWithSeparator{args}, nil | ||
} | ||
|
||
// Type implements the Expression interface. | ||
func (f *ConcatWithSeparator) Type() sql.Type { return sql.Text } | ||
|
||
// IsNullable implements the Expression interface. | ||
func (f *ConcatWithSeparator) IsNullable() bool { | ||
for _, arg := range f.args { | ||
if arg.IsNullable() { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (f *ConcatWithSeparator) String() string { | ||
var args = make([]string, len(f.args)) | ||
for i, arg := range f.args { | ||
args[i] = arg.String() | ||
} | ||
return fmt.Sprintf("concat_ws(%s)", strings.Join(args, ", ")) | ||
} | ||
|
||
// TransformUp implements the Expression interface. | ||
func (f *ConcatWithSeparator) TransformUp(fn sql.TransformExprFunc) (sql.Expression, error) { | ||
var args = make([]sql.Expression, len(f.args)) | ||
for i, arg := range f.args { | ||
arg, err := arg.TransformUp(fn) | ||
if err != nil { | ||
return nil, err | ||
} | ||
args[i] = arg | ||
} | ||
|
||
expr, err := NewConcatWithSeparator(args...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return fn(expr) | ||
} | ||
|
||
// Resolved implements the Expression interface. | ||
func (f *ConcatWithSeparator) Resolved() bool { | ||
for _, arg := range f.args { | ||
if !arg.Resolved() { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// Children implements the Expression interface. | ||
func (f *ConcatWithSeparator) Children() []sql.Expression { return f.args } | ||
|
||
// Eval implements the Expression interface. | ||
func (f *ConcatWithSeparator) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) { | ||
var parts []string | ||
|
||
for i, arg := range f.args { | ||
val, err := arg.Eval(ctx, row) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if val == nil && i == 0 { | ||
return nil, nil | ||
} | ||
|
||
if val == nil { | ||
continue | ||
} | ||
|
||
if sql.IsArray(arg.Type()) { | ||
val, err = sql.Array(sql.Text).Convert(val) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, v := range val.([]interface{}) { | ||
parts = append(parts, v.(string)) | ||
} | ||
} else { | ||
val, err = sql.Text.Convert(val) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
parts = append(parts, val.(string)) | ||
} | ||
} | ||
|
||
return strings.Join(parts[1:], parts[0]), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package function | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"gopkg.in/src-d/go-mysql-server.v0/sql" | ||
"gopkg.in/src-d/go-mysql-server.v0/sql/expression" | ||
) | ||
|
||
func TestConcatWithSeparator(t *testing.T) { | ||
t.Run("multiple arguments", func(t *testing.T) { | ||
require := require.New(t) | ||
f, err := NewConcatWithSeparator( | ||
expression.NewLiteral(",", sql.Text), | ||
expression.NewLiteral("foo", sql.Text), | ||
expression.NewLiteral(5, sql.Text), | ||
expression.NewLiteral(true, sql.Boolean), | ||
) | ||
require.NoError(err) | ||
|
||
v, err := f.Eval(sql.NewEmptyContext(), nil) | ||
require.NoError(err) | ||
require.Equal("foo,5,true", v) | ||
}) | ||
|
||
t.Run("some argument is empty", func(t *testing.T) { | ||
require := require.New(t) | ||
f, err := NewConcatWithSeparator( | ||
expression.NewLiteral(",", sql.Text), | ||
expression.NewLiteral("foo", sql.Text), | ||
expression.NewLiteral("", sql.Text), | ||
expression.NewLiteral(true, sql.Boolean), | ||
) | ||
require.NoError(err) | ||
|
||
v, err := f.Eval(sql.NewEmptyContext(), nil) | ||
require.NoError(err) | ||
require.Equal("foo,,true", v) | ||
}) | ||
|
||
t.Run("some argument is nil", func(t *testing.T) { | ||
require := require.New(t) | ||
f, err := NewConcatWithSeparator( | ||
expression.NewLiteral(",", sql.Text), | ||
expression.NewLiteral("foo", sql.Text), | ||
expression.NewLiteral(nil, sql.Text), | ||
expression.NewLiteral(true, sql.Boolean), | ||
) | ||
require.NoError(err) | ||
|
||
v, err := f.Eval(sql.NewEmptyContext(), nil) | ||
require.NoError(err) | ||
require.Equal("foo,true", v) | ||
}) | ||
|
||
t.Run("separator is nil", func(t *testing.T) { | ||
require := require.New(t) | ||
f, err := NewConcatWithSeparator( | ||
expression.NewLiteral(nil, sql.Text), | ||
expression.NewLiteral("foo", sql.Text), | ||
expression.NewLiteral(5, sql.Text), | ||
expression.NewLiteral(true, sql.Boolean), | ||
) | ||
require.NoError(err) | ||
|
||
v, err := f.Eval(sql.NewEmptyContext(), nil) | ||
require.NoError(err) | ||
require.Equal(nil, v) | ||
}) | ||
|
||
t.Run("concat_ws array", func(t *testing.T) { | ||
require := require.New(t) | ||
f, err := NewConcatWithSeparator( | ||
expression.NewLiteral([]interface{}{",",5, "bar", true}, sql.Array(sql.Text)), | ||
) | ||
require.NoError(err) | ||
|
||
v, err := f.Eval(sql.NewEmptyContext(), nil) | ||
require.NoError(err) | ||
require.Equal("5,bar,true", v) | ||
}) | ||
} | ||
|
||
func TestNewConcatWithSeparator(t *testing.T) { | ||
require := require.New(t) | ||
|
||
_, err := NewConcatWithSeparator(expression.NewLiteral(nil, sql.Array(sql.Text))) | ||
require.NoError(err) | ||
|
||
_, err = NewConcatWithSeparator(expression.NewLiteral(nil, sql.Array(sql.Text)), expression.NewLiteral(nil, sql.Int64)) | ||
require.Error(err) | ||
require.True(ErrConcatArrayWithOthers.Is(err)) | ||
|
||
_, err = NewConcatWithSeparator(expression.NewLiteral(nil, sql.Tuple(sql.Text, sql.Text))) | ||
require.Error(err) | ||
require.True(sql.ErrInvalidType.Is(err)) | ||
|
||
_, err = NewConcatWithSeparator( | ||
expression.NewLiteral(nil, sql.Text), | ||
expression.NewLiteral(nil, sql.Boolean), | ||
expression.NewLiteral(nil, sql.Int64), | ||
expression.NewLiteral(nil, sql.Text), | ||
) | ||
require.NoError(err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth mentioning that null arguments are skipped and only returns null if sep is null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
"Returns null if the separator is null. Following null fields are skipped." - Would this be ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets copy paste from mysql docs:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I only just saw the comment. The descriptions now come from MySQLs docs.