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

Commit

Permalink
Merge pull request #431 from kuba--/coalesce-389
Browse files Browse the repository at this point in the history
Implementation of COALESCE function.
  • Loading branch information
ajnavarro authored Oct 9, 2018
2 parents 9a8f039 + 969ac5c commit 9d7c9df
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 0 deletions.
126 changes: 126 additions & 0 deletions sql/expression/function/coalesce.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package function

import (
"fmt"
"strings"

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

// Coalesce returns the first non-NULL value in the list, or NULL if there are no non-NULL values.
type Coalesce struct {
args []sql.Expression
}

// NewCoalesce creates a new Coalesce sql.Expression.
func NewCoalesce(args ...sql.Expression) (sql.Expression, error) {
if len(args) == 0 {
return nil, sql.ErrInvalidArgumentNumber.New("1 or more", 0)
}

return &Coalesce{args}, nil
}

// Type implements the sql.Expression interface.
// The return type of Type() is the aggregated type of the argument types.
func (c *Coalesce) Type() sql.Type {
for _, arg := range c.args {
if arg == nil {
continue
}
t := arg.Type()
if t == nil {
continue
}
return t
}

return nil
}

// IsNullable implements the sql.Expression interface.
// Returns true if all arguments are nil
// or of the first non-nil argument is nullable, otherwise false.
func (c *Coalesce) IsNullable() bool {
for _, arg := range c.args {
if arg == nil {
continue
}
return arg.IsNullable()
}
return true
}

func (c *Coalesce) String() string {
var args = make([]string, len(c.args))
for i, arg := range c.args {
args[i] = arg.String()
}
return fmt.Sprintf("coalesce(%s)", strings.Join(args, ", "))
}

// TransformUp implements the sql.Expression interface.
func (c *Coalesce) TransformUp(fn sql.TransformExprFunc) (sql.Expression, error) {
var (
args = make([]sql.Expression, len(c.args))
err error
)

for i, arg := range c.args {
if arg != nil {
arg, err = arg.TransformUp(fn)
if err != nil {
return nil, err
}
}
args[i] = arg
}

expr, err := NewCoalesce(args...)
if err != nil {
return nil, err
}

return fn(expr)
}

// Resolved implements the sql.Expression interface.
// The function checks if first non-nil argument is resolved.
func (c *Coalesce) Resolved() bool {
for _, arg := range c.args {
if arg == nil {
continue
}
if !arg.Resolved() {
return false
}
}
return true
}

// Children implements the sql.Expression interface.
func (c *Coalesce) Children() []sql.Expression { return c.args }

// Eval implements the sql.Expression interface.
// The function evaluates the first non-nil argument. If the value is nil,
// then we keep going, otherwise we return the first non-nil value.
func (c *Coalesce) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
for _, arg := range c.args {
if arg == nil {
continue
}

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

if val == nil {
continue
}

return val, nil
}

return nil, nil
}
64 changes: 64 additions & 0 deletions sql/expression/function/coalesce_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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 TestEmptyCoalesce(t *testing.T) {
_, err := NewCoalesce()
require.True(t, sql.ErrInvalidArgumentNumber.Is(err))
}

func TestCoalesce(t *testing.T) {
testCases := []struct {
name string
input []sql.Expression
expected interface{}
typ sql.Type
nullable bool
}{
{"coalesce(1, 2, 3)", []sql.Expression{expression.NewLiteral(1, sql.Int32), expression.NewLiteral(2, sql.Int32), expression.NewLiteral(3, sql.Int32)}, 1, sql.Int32, false},
{"coalesce(NULL, NULL, 3)", []sql.Expression{nil, nil, expression.NewLiteral(3, sql.Int32)}, 3, sql.Int32, false},
{"coalesce(NULL, NULL, '3')", []sql.Expression{nil, nil, expression.NewLiteral("3", sql.Text)}, "3", sql.Text, false},
{"coalesce(NULL, '2', 3)", []sql.Expression{nil, expression.NewLiteral("2", sql.Text), expression.NewLiteral(3, sql.Int32)}, "2", sql.Text, false},
{"coalesce(NULL, NULL, NULL)", []sql.Expression{nil, nil, nil}, nil, nil, true},
}

for _, tt := range testCases {
c, err := NewCoalesce(tt.input...)
require.NoError(t, err)

require.Equal(t, tt.typ, c.Type())
require.Equal(t, tt.nullable, c.IsNullable())
v, err := c.Eval(sql.NewEmptyContext(), nil)
require.NoError(t, err)
require.Equal(t, tt.expected, v)
}
}

func TestComposeCoalasce(t *testing.T) {
c1, err := NewCoalesce(nil)
require.NoError(t, err)
require.Equal(t, nil, c1.Type())
v, err := c1.Eval(sql.NewEmptyContext(), nil)
require.NoError(t, err)
require.Equal(t, nil, v)

c2, err := NewCoalesce(nil, expression.NewLiteral(1, sql.Int32))
require.NoError(t, err)
require.Equal(t, sql.Int32, c2.Type())
v, err = c2.Eval(sql.NewEmptyContext(), nil)
require.NoError(t, err)
require.Equal(t, 1, v)

c, err := NewCoalesce(nil, c1, c2)
require.NoError(t, err)
require.Equal(t, sql.Int32, c.Type())
v, err = c.Eval(sql.NewEmptyContext(), nil)
require.NoError(t, err)
require.Equal(t, 1, v)
}

0 comments on commit 9d7c9df

Please sign in to comment.