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

Feature/lpad rpad #522

Merged
merged 4 commits into from
Nov 5, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
RPAD, LPAD
Signed-off-by: Theo Despoudis <thdespou@hotmail.com>
  • Loading branch information
theodesp committed Nov 1, 2018
commit a131d366e47f6290eb75ea695faed72ca564c73f
2 changes: 1 addition & 1 deletion _example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func main() {

config := server.Config{
Protocol: "tcp",
Address: "localhost:3306",
Address: "localhost:3307",
Auth: auth.NewNativeSingle("user", "pass", auth.AllPermissions),
}

Expand Down
4 changes: 2 additions & 2 deletions sql/expression/function/logarithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
// logarithm function
var ErrInvalidArgumentForLogarithm = errors.NewKind("invalid argument value for logarithm: %v")

// LogBaseMaker returns LogBase creator functions with a specific base.
func LogBaseMaker(base float64) func(e sql.Expression) sql.Expression {
// MakeLogBase returns LogBase creator functions with a specific base.
func MakeLogBase(base float64) func(e sql.Expression) sql.Expression {
return func(e sql.Expression) sql.Expression {
return NewLogBase(base, e)
}
Expand Down
12 changes: 6 additions & 6 deletions sql/expression/function/logarithm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestLn(t *testing.T) {
}

for _, tt := range testCases {
f := LogBaseMaker(math.E)(expression.NewGetField(0, tt.rowType, "", false))
f := MakeLogBase(math.E)(expression.NewGetField(0, tt.rowType, "", false))
t.Run(tt.name, func(t *testing.T) {
t.Helper()
require := require.New(t)
Expand All @@ -48,7 +48,7 @@ func TestLn(t *testing.T) {
}

// Test Nil
f := LogBaseMaker(math.E)(expression.NewGetField(0, sql.Float64, "", true))
f := MakeLogBase(math.E)(expression.NewGetField(0, sql.Float64, "", true))
require := require.New(t)
result, err := f.Eval(sql.NewEmptyContext(), sql.NewRow(nil))
require.NoError(err)
Expand All @@ -75,7 +75,7 @@ func TestLog2(t *testing.T) {
}

for _, tt := range testCases {
f := LogBaseMaker(float64(2))(expression.NewGetField(0, tt.rowType, "", false))
f := MakeLogBase(float64(2))(expression.NewGetField(0, tt.rowType, "", false))
t.Run(tt.name, func(t *testing.T) {
t.Helper()
require := require.New(t)
Expand All @@ -91,7 +91,7 @@ func TestLog2(t *testing.T) {
}

// Test Nil
f := LogBaseMaker(float64(2))(expression.NewGetField(0, sql.Float64, "", true))
f := MakeLogBase(float64(2))(expression.NewGetField(0, sql.Float64, "", true))
require := require.New(t)
result, err := f.Eval(sql.NewEmptyContext(), sql.NewRow(nil))
require.NoError(err)
Expand All @@ -118,7 +118,7 @@ func TestLog10(t *testing.T) {
}

for _, tt := range testCases {
f := LogBaseMaker(float64(10))(expression.NewGetField(0, tt.rowType, "", false))
f := MakeLogBase(float64(10))(expression.NewGetField(0, tt.rowType, "", false))
t.Run(tt.name, func(t *testing.T) {
t.Helper()
require := require.New(t)
Expand All @@ -134,7 +134,7 @@ func TestLog10(t *testing.T) {
}

// Test Nil
f := LogBaseMaker(float64(10))(expression.NewGetField(0, sql.Float64, "", true))
f := MakeLogBase(float64(10))(expression.NewGetField(0, sql.Float64, "", true))
require := require.New(t)
result, err := f.Eval(sql.NewEmptyContext(), sql.NewRow(nil))
require.NoError(err)
Expand Down
10 changes: 6 additions & 4 deletions sql/expression/function/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ var Defaults = sql.Functions{
"json_extract": sql.FunctionN(NewJSONExtract),
"connection_id": sql.Function0(NewConnectionID),
"soundex": sql.Function1(NewSoundex),
"ln": sql.Function1(LogBaseMaker(float64(math.E))),
"log2": sql.Function1(LogBaseMaker(float64(2))),
"log10": sql.Function1(LogBaseMaker(float64(10))),
"log": sql.FunctionN(NewLog),
"ln": sql.Function1(MakeLogBase(float64(math.E))),
"log2": sql.Function1(MakeLogBase(float64(2))),
"log10": sql.Function1(MakeLogBase(float64(10))),
"log": sql.FunctionN(NewLog),
"rpad": sql.FunctionN(MakePadder(rightPadType)),
"lpad": sql.FunctionN(MakePadder(leftPadType)),
}
165 changes: 165 additions & 0 deletions sql/expression/function/rpad_lpad.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package function

import (
"fmt"
"reflect"
"strings"

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

type padType int

const (
leftPadType = iota
rightPadType
)

// MakePadder returns a Pad creator functions with a specific padType.
func MakePadder(pType padType) func(e ...sql.Expression) (sql.Expression, error) {
return func(e ...sql.Expression) (sql.Expression, error) {
return NewPad(pType, e...)
}
}

// NewLogBase creates a new LogBase expression.
func NewPad(pType padType, args ...sql.Expression) (sql.Expression, error) {
argLen := len(args)
if argLen != 3 {
return nil, sql.ErrInvalidArgumentNumber.New("3", argLen)
}

return &Pad{args[0], args[1], args[2], pType}, nil
}

// Pad is a function that pads a string with another string.
type Pad struct {
str sql.Expression
len sql.Expression
padStr sql.Expression
padType padType
}

// Children implements the Expression interface.
func (p *Pad) Children() []sql.Expression {
return []sql.Expression{p.str, p.len, p.padStr}
}

// Resolved implements the Expression interface.
func (p *Pad) Resolved() bool {
return p.str.Resolved() && p.len.Resolved() && (p.padStr.Resolved())
}

// IsNullable implements the Expression interface.
func (p *Pad) IsNullable() bool {
return p.str.IsNullable() || p.len.IsNullable() || p.padStr.IsNullable()
}

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

func (p *Pad) String() string {
if p.padType == leftPadType {
return fmt.Sprintf("lpad(%s, %s, %s)", p.str, p.len, p.padStr)
}
return fmt.Sprintf("rpad(%s, %s, %s)", p.str, p.len, p.padStr)
}

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

len, err := p.len.TransformUp(f)
if err != nil {
return nil, err
}

padStr, err := p.padStr.TransformUp(f)
if err != nil {
return nil, err
}
padded, _ := NewPad(p.padType, str, len, padStr)
return f(padded)
}

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

if str == nil {
return nil, nil
}

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

length, err := p.len.Eval(ctx, row)
if err != nil {
return nil, err
}

if length == nil {
return nil, nil
}

length, err = sql.Int64.Convert(length)
if err != nil {
return nil, err
}

padStr, err := p.padStr.Eval(ctx, row)
if err != nil {
return nil, err
}

if padStr == nil {
return nil, nil
}

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

return padString(str.(string), length.(int64), padStr.(string), p.padType)
}

func padString(str string, length int64, padStr string, padType padType) (string, error) {
if length <= 0 {
return "", nil
}
if int64(len(str)) >= length {
return str[:length], nil
}
if len(padStr) == 0 {
return "", nil
}

padLen := int(length - int64(len(str)))
quo, rem := divmod(int64(padLen), int64(len(padStr)))

if padType == leftPadType {
result := strings.Repeat(padStr, int(quo)) + padStr[:rem] + str
return result[:length], nil
} else {
result := str + strings.Repeat(padStr, int(quo)) + padStr[:rem]
return result[(int64(len(result)) - length):], nil
}
}

func divmod(a, b int64) (quotient, remainder int64) {
quotient = a / b
remainder = a % b
return
}
109 changes: 109 additions & 0 deletions sql/expression/function/rpad_lpad_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package function

import (
"testing"

"gopkg.in/src-d/go-mysql-server.v0/sql"
"gopkg.in/src-d/go-mysql-server.v0/sql/expression"
"github.com/stretchr/testify/require"
)

func TestLPad(t *testing.T) {
f, err := NewPad(
leftPadType,
expression.NewGetField(0, sql.Text, "str", false),
expression.NewGetField(1, sql.Int64, "len", false),
expression.NewGetField(2, sql.Text, "padStr", false),
)
require.NoError(t, err)
testCases := []struct {
name string
row sql.Row
expected interface{}
err bool
}{
{"null string", sql.NewRow(nil, 1, "bar"), nil, false},
{"null len", sql.NewRow("foo", nil, "bar"), nil, false},
{"null padStr", sql.NewRow("foo", 1, nil), nil, false},

{"negative length", sql.NewRow("foo", -1, "bar"), "", false},
{"length 0", sql.NewRow("foo", 0, "bar"), "", false},
{"invalid length", sql.NewRow("foo", "a", "bar"), "", true},

{"empty padStr and len < len(str)", sql.NewRow("foo", 1, ""), "f", false},
{"empty padStr and len > len(str)", sql.NewRow("foo", 4, ""), "", false},
{"empty padStr and len == len(str)", sql.NewRow("foo", 3, ""), "foo", false},

{"non empty padStr and len < len(str)", sql.NewRow("foo", 1, "abcd"), "f", false},
{"non empty padStr and len == len(str)", sql.NewRow("foo", 3, "abcd"), "foo", false},

{"padStr repeats exactly once", sql.NewRow("foo", 6, "abc"), "abcfoo", false},
{"padStr does not repeat once", sql.NewRow("foo", 5, "abc"), "abfoo", false},
{"padStr repeats many times", sql.NewRow("foo", 10, "abc"), "abcabcafoo", 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 TestRPad(t *testing.T) {
f, err := NewPad(
rightPadType,
expression.NewGetField(0, sql.Text, "str", false),
expression.NewGetField(1, sql.Int64, "len", false),
expression.NewGetField(2, sql.Text, "padStr", false),
)
require.NoError(t, err)
testCases := []struct {
name string
row sql.Row
expected interface{}
err bool
}{
{"null string", sql.NewRow(nil, 1, "bar"), nil, false},
{"null len", sql.NewRow("foo", nil, "bar"), nil, false},
{"null padStr", sql.NewRow("foo", 1, nil), nil, false},

{"negative length", sql.NewRow("foo", -1, "bar"), "", false},
{"length 0", sql.NewRow("foo", 0, "bar"), "", false},
{"invalid length", sql.NewRow("foo", "a", "bar"), "", true},

{"empty padStr and len < len(str)", sql.NewRow("foo", 1, ""), "f", false},
{"empty padStr and len > len(str)", sql.NewRow("foo", 4, ""), "", false},
{"empty padStr and len == len(str)", sql.NewRow("foo", 3, ""), "foo", false},

{"non empty padStr and len < len(str)", sql.NewRow("foo", 1, "abcd"), "f", false},
{"non empty padStr and len == len(str)", sql.NewRow("foo", 3, "abcd"), "foo", false},

{"padStr repeats exactly once", sql.NewRow("foo", 6, "abc"), "fooabc", false},
{"padStr does not repeat once", sql.NewRow("foo", 5, "abc"), "fooab", false},
{"padStr repeats many times", sql.NewRow("foo", 10, "abc"), "fooabcabca", 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)
}
})
}
}
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