Skip to content
This repository was archived by the owner on Jan 28, 2021. It is now read-only.

Add Reverse, Repeat, Replace #543

Merged
merged 5 commits into from
Nov 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ var queries = []struct {
"SELECT substring(s, 2, 3) FROM mytable",
[]sql.Row{{"irs"}, {"eco"}, {"hir"}},
},
{
`SELECT substring("foo", 2, 2)`,
[]sql.Row{{"oo"}},
},
{
"SELECT YEAR('2007-12-11') FROM mytable",
[]sql.Row{{int32(2007)}, {int32(2007)}, {int32(2007)}},
Expand Down Expand Up @@ -137,6 +141,12 @@ var queries = []struct {
{"first", "irst", "rst"},
},
},
{
`SELECT substring("first", 1), substring("second", 2), substring("third", 3)`,
[]sql.Row{
{"first", "econd", "ird"},
},
},
{
"SELECT substring(s2, -1), substring(s2, -2), substring(s2, -3) FROM othertable ORDER BY i2",
[]sql.Row{
Expand All @@ -145,7 +155,12 @@ var queries = []struct {
{"t", "st", "rst"},
},
},

{
`SELECT substring("first", -1), substring("second", -2), substring("third", -3)`,
[]sql.Row{
{"t", "nd", "ird"},
},
},
{
"SELECT s FROM mytable INNER JOIN othertable " +
"ON substring(s2, 1, 2) != '' AND i = i2",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ require (
golang.org/x/net v0.0.0-20181029044818-c44066c5c816 // indirect
google.golang.org/grpc v1.16.0 // indirect
gopkg.in/src-d/go-errors.v1 v1.0.0
gopkg.in/src-d/go-vitess.v1 v1.3.0
gopkg.in/src-d/go-vitess.v1 v1.4.0
)
3 changes: 3 additions & 0 deletions sql/expression/function/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,7 @@ var Defaults = sql.Functions{
"ltrim": sql.Function1(NewTrimFunc(lTrimType)),
"rtrim": sql.Function1(NewTrimFunc(rTrimType)),
"trim": sql.Function1(NewTrimFunc(bTrimType)),
"reverse": sql.Function1(NewReverse),
"repeat": sql.Function2(NewRepeat),
"replace": sql.Function3(NewReplace),
}
227 changes: 227 additions & 0 deletions sql/expression/function/reverse_repeat_replace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package function

import (
"fmt"
"strings"

"gopkg.in/src-d/go-mysql-server.v0/sql/expression"
"gopkg.in/src-d/go-mysql-server.v0/sql"
"gopkg.in/src-d/go-errors.v1"
)

// Reverse is a function that returns the reverse of the text provided.
type Reverse struct {
expression.UnaryExpression
}

// NewReverse creates a new Reverse expression.
func NewReverse(e sql.Expression) sql.Expression {
return &Reverse{expression.UnaryExpression{Child: e}}
}

// Eval implements the Expression interface.
func (r *Reverse) Eval(
ctx *sql.Context,
row sql.Row,
) (interface{}, error) {
v, err := r.Child.Eval(ctx, row)
if v == nil || err != nil {
return nil, err
}

v, err = sql.Text.Convert(v)
if err != nil {
return nil, err
}

return reverseString(v.(string)), nil
}

func reverseString(s string) string {
r := []rune(s)
for i, j := 0, len(r) - 1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}

func (r *Reverse) String() string {
return fmt.Sprintf("reverse(%s)", r.Child)
}

// TransformUp implements the Expression interface.
func (r *Reverse) TransformUp(f sql.TransformExprFunc) (sql.Expression, error) {
child, err := r.Child.TransformUp(f)
if err != nil {
return nil, err
}
return f(NewReverse(child))
}

// Type implements the Expression interface.
func (r *Reverse) Type() sql.Type {
return r.Child.Type()
}

var ErrNegativeRepeatCount = errors.NewKind("negative Repeat count: %v")

// Repeat is a function that returns the string repeated n times.
type Repeat struct {
expression.BinaryExpression
}

// NewRepeat creates a new Repeat expression.
func NewRepeat(str sql.Expression, count sql.Expression) sql.Expression {
return &Repeat{expression.BinaryExpression{Left: str, Right: count}}
}

func (r *Repeat) String() string {
return fmt.Sprintf("repeat(%s, %s)", r.Left, r.Right)
}

// Type implements the Expression interface.
func (r *Repeat) Type() sql.Type {
return sql.Text
}

// TransformUp implements the Expression interface.
func (r *Repeat) TransformUp(f sql.TransformExprFunc) (sql.Expression, error) {
left, err := r.Left.TransformUp(f)
if err != nil {
return nil, err
}

right, err := r.Right.TransformUp(f)
if err != nil {
return nil, err
}
return f(NewRepeat(left, right))
}

// Eval implements the Expression interface.
func (r *Repeat) Eval(
ctx *sql.Context,
row sql.Row,
) (interface{}, error) {
str, err := r.Left.Eval(ctx, row)
if str == nil || err != nil {
return nil, err
}

str, err = sql.Text.Convert(str)
if err != nil {
return nil, err
}

count, err := r.Right.Eval(ctx, row)
if count == nil || err != nil {
return nil, err
}

count, err = sql.Int32.Convert(count)
if err != nil {
return nil, err
}
if count.(int32) < 0 {
return nil, ErrNegativeRepeatCount.New(count)
}
return strings.Repeat(str.(string), int(count.(int32))), nil
}

// Replace is a function that returns a string with all occurrences of fromStr replaced by the
// string toStr
type Replace struct {
str sql.Expression
fromStr sql.Expression
toStr sql.Expression
}

// NewReplace creates a new Replace expression.
func NewReplace(str sql.Expression, fromStr sql.Expression, toStr sql.Expression) sql.Expression {
return &Replace{str, fromStr, toStr}
}

// Children implements the Expression interface.
func (r *Replace) Children() []sql.Expression {
return []sql.Expression{r.str, r.fromStr, r.toStr}
}

// Resolved implements the Expression interface.
func (r *Replace) Resolved() bool {
return r.str.Resolved() && r.fromStr.Resolved() && r.toStr.Resolved()
}

// IsNullable implements the Expression interface.
func (r *Replace) IsNullable() bool {
return r.str.IsNullable() || r.fromStr.IsNullable() || r.toStr.IsNullable()
}

func (r *Replace) String() string {
return fmt.Sprintf("replace(%s, %s, %s)", r.str, r.fromStr, r.toStr)
}

// Type implements the Expression interface.
func (r *Replace) Type() sql.Type {
return sql.Text
}

// TransformUp implements the Expression interface.
func (r *Replace) TransformUp(f sql.TransformExprFunc) (sql.Expression, error) {
str, err := r.str.TransformUp(f)
if err != nil {
return nil, err
}

fromStr, err := r.fromStr.TransformUp(f)
if err != nil {
return nil, err
}

toStr, err := r.toStr.TransformUp(f)
if err != nil {
return nil, err
}
return f(NewReplace(str, fromStr, toStr))
}

// Eval implements the Expression interface.
func (r *Replace) Eval(
ctx *sql.Context,
row sql.Row,
) (interface{}, error) {
str, err := r.str.Eval(ctx, row)
if str == nil || err != nil {
return nil, err
}

str, err = sql.Text.Convert(str)
if err != nil {
return nil, err
}

fromStr, err := r.fromStr.Eval(ctx, row)
if fromStr == nil || err != nil {
return nil, err
}

fromStr, err = sql.Text.Convert(fromStr)
if err != nil {
return nil, err
}

toStr, err := r.toStr.Eval(ctx, row)
if toStr == nil || err != nil {
return nil, err
}

toStr, err = sql.Text.Convert(toStr)
if err != nil {
return nil, err
}

if fromStr.(string) == "" {
return str, nil
}

return strings.Replace(str.(string), fromStr.(string), toStr.(string), -1), nil
}
110 changes: 110 additions & 0 deletions sql/expression/function/reverse_repeat_replace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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 TestReverse(t *testing.T) {
f := NewReverse(expression.NewGetField(0, sql.Text, "", false))
testCases := []struct {
name string
row sql.Row
expected interface{}
err bool
}{
{"null input", sql.NewRow(nil), nil, false},
{"empty string", sql.NewRow(""), "", false},
{"handles numbers as strings", sql.NewRow(123), "321", false},
{"valid string", sql.NewRow("foobar"), "raboof", false},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
t.Helper()
require := require.New(t)
ctx := sql.NewEmptyContext()

v, err := f.Eval(ctx, tt.row)
if tt.err {
require.Error(err)
} else {
require.NoError(err)
require.Equal(tt.expected, v)
}
})
}
}

func TestRepeat(t *testing.T) {
f := NewRepeat(
expression.NewGetField(0, sql.Text, "", false),
expression.NewGetField(1, sql.Int32, "", false),
)

testCases := []struct {
name string
row sql.Row
expected interface{}
err bool
}{
{"null input", sql.NewRow(nil), nil, false},
{"empty string", sql.NewRow("", 2), "", false},
{"count is zero", sql.NewRow("foo", 0), "", false},
{"count is negative", sql.NewRow("foo", -2), "foo", true},
{"valid string", sql.NewRow("foobar", 2), "foobarfoobar", false},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
t.Helper()
require := require.New(t)
ctx := sql.NewEmptyContext()

v, err := f.Eval(ctx, tt.row)
if tt.err {
require.Error(err)
} else {
require.NoError(err)
require.Equal(tt.expected, v)
}
})
}
}

func TestReplace(t *testing.T) {
f := NewReplace(
expression.NewGetField(0, sql.Text, "", false),
expression.NewGetField(1, sql.Text, "", false),
expression.NewGetField(2, sql.Text, "", false),
)

testCases := []struct {
name string
row sql.Row
expected interface{}
err bool
}{
{"null inputs", sql.NewRow(nil), nil, false},
{"empty str", sql.NewRow("", "foo", "bar"), "", false},
{"empty fromStr", sql.NewRow("foobarfoobar", "", "car"), "foobarfoobar", false},
{"empty toStr", sql.NewRow("foobarfoobar", "bar", ""), "foofoo", false},
{"valid strings", sql.NewRow("foobarfoobar", "bar", "car"), "foocarfoocar", false},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
t.Helper()
require := require.New(t)
ctx := sql.NewEmptyContext()

v, err := f.Eval(ctx, tt.row)
if tt.err {
require.Error(err)
} else {
require.NoError(err)
require.Equal(tt.expected, v)
}
})
}
}
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy