Skip to content

Commit

Permalink
Add coalesce function (#513)
Browse files Browse the repository at this point in the history
  • Loading branch information
yaziine committed Nov 23, 2023
1 parent d9f994a commit 79af2d0
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 26 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ profile.*
# Builds
cmd/genji/genji

# VS Code config
# IDE config
.vscode/
.idea/

# gopls log
gopls.log
Expand Down
33 changes: 17 additions & 16 deletions cmd/genji/doc/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ type functionDocs map[string]string

var packageDocs = map[string]functionDocs{
"strings": stringsDocs,
"math": mathDocs,
"": builtinDocs,
"math": mathDocs,
"": builtinDocs,
}

var builtinDocs = functionDocs{
"pk": "The pk() function returns the primary key for the current document",
"count": "Returns a count of the number of times that arg1 is not NULL in a group. The count(*) function (with no arguments) returns the total number of rows in the group.",
"min": "Returns the minimum value of the arg1 expression in a group.",
"max": "Returns the maximum value of the arg1 expressein in a group.",
"sum": "The sum function returns the sum of all values taken by the arg1 expression in a group.",
"avg": "The avg function returns the average of all values taken by the arg1 expression in a group.",
"typeof": "The typeof function returns the type of arg1.",
"len": "The len function returns length of the arg1 expression if arg1 evals to string, array or document, either returns NULL.",
"pk": "The pk() function returns the primary key for the current document",
"count": "Returns a count of the number of times that arg1 is not NULL in a group. The count(*) function (with no arguments) returns the total number of rows in the group.",
"min": "Returns the minimum value of the arg1 expression in a group.",
"max": "Returns the maximum value of the arg1 expressein in a group.",
"sum": "The sum function returns the sum of all values taken by the arg1 expression in a group.",
"avg": "The avg function returns the average of all values taken by the arg1 expression in a group.",
"typeof": "The typeof function returns the type of arg1.",
"len": "The len function returns length of the arg1 expression if arg1 evals to string, array or document, either returns NULL.",
"coalesce": "The coalesce function returns the first non-null argument. NULL is returned if all arguments are null.",
}

var mathDocs = functionDocs{
Expand All @@ -31,9 +32,9 @@ var mathDocs = functionDocs{
}

var stringsDocs = functionDocs{
"lower": "The lower function returns arg1 to lower-case if arg1 evals to string",
"upper": "The upper function returns arg1 to upper-case if arg1 evals to string",
"trim": "The trim function returns arg1 with leading and trailing characters removed. space by default or arg2",
"ltrim": "The ltrim function returns arg1 with leading characters removed. space by default or arg2",
"rtrim": "The rtrim function returns arg1 with trailing characters removed. space by default or arg2",
}
"lower": "The lower function returns arg1 to lower-case if arg1 evals to string",
"upper": "The upper function returns arg1 to upper-case if arg1 evals to string",
"trim": "The trim function returns arg1 with leading and trailing characters removed. space by default or arg2",
"ltrim": "The ltrim function returns arg1 with leading characters removed. space by default or arg2",
"rtrim": "The rtrim function returns arg1 with trailing characters removed. space by default or arg2",
}
34 changes: 33 additions & 1 deletion internal/expr/functions/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ var builtinFunctions = Definitions{
return &Len{Expr: args[0]}, nil
},
},
"coalesce": &definition{
name: "coalesce",
arity: variadicArity,
constructorFn: func(args ...expr.Expr) (expr.Function, error) {
return &Coalesce{Exprs: args}, nil
},
},
}

// BuiltinDefinitions returns a map of builtin functions.
Expand Down Expand Up @@ -716,4 +723,29 @@ func (s *Len) Params() []expr.Expr { return []expr.Expr{s.Expr} }
// String returns the literal representation of len.
func (s *Len) String() string {
return fmt.Sprintf("LEN(%v)", s.Expr)
}
}

type Coalesce struct {
Exprs []expr.Expr
}

func (c *Coalesce) Eval(e *environment.Environment) (types.Value, error) {
for _, exp := range c.Exprs {
v, err := exp.Eval(e)
if err != nil {
return nil, err
}
if v.Type() != types.NullValue {
return v, nil
}
}
return nil, nil
}

func (c *Coalesce) String() string {
return fmt.Sprintf("COALESCE(%v)", c.Exprs)
}

func (c *Coalesce) Params() []expr.Expr {
return c.Exprs
}
10 changes: 6 additions & 4 deletions internal/expr/functions/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"github.com/genjidb/genji/internal/expr"
)

// variadicArity represents an unlimited number of arguments.
const variadicArity = -1

// A Definition transforms a list of expressions into a Function.
type Definition interface {
Name() string
Expand Down Expand Up @@ -57,11 +60,10 @@ func (fd *definition) Name() string {
}

func (fd *definition) Function(args ...expr.Expr) (expr.Function, error) {
if fd.arity == -1 {
return fd.constructorFn(args...)
if fd.arity == variadicArity && len(args) == 0 {
return nil, fmt.Errorf("%s() requires at least one argument", fd.name)
}

if len(args) != fd.arity {
if fd.arity != variadicArity && (len(args) != fd.arity) {
return nil, fmt.Errorf("%s() takes %d argument(s), not %d", fd.name, fd.arity, len(args))
}
return fd.constructorFn(args...)
Expand Down
6 changes: 3 additions & 3 deletions internal/expr/functions/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@ var stringsFunctions = Definitions{
},
"trim": &definition{
name: "trim",
arity: -1,
arity: variadicArity,
constructorFn: func(args ...expr.Expr) (expr.Function, error) {
return &Trim{Expr: args, TrimFunc: strings.Trim, Name: "TRIM"}, nil
},
},
"ltrim": &definition{
name: "ltrim",
arity: -1,
arity: variadicArity,
constructorFn: func(args ...expr.Expr) (expr.Function, error) {
return &Trim{Expr: args, TrimFunc: strings.TrimLeft, Name: "LTRIM"}, nil
},
},
"rtrim": &definition{
name: "rtrim",
arity: -1,
arity: variadicArity,
constructorFn: func(args ...expr.Expr) (expr.Function, error) {
return &Trim{Expr: args, TrimFunc: strings.TrimRight, Name: "RTRIM"}, nil
},
Expand Down
2 changes: 1 addition & 1 deletion internal/stream/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var ErrInvalidResult = errors.New("expression must evaluate to a document")

// An Operator is used to modify a stream.
// It takes an environment containing the current value as well as any other metadata
// created by other operatorsand returns a new environment which will be passed to the next operator.
// created by other operators and returns a new environment which will be passed to the next operator.
// If it returns a nil environment, the env will be ignored.
// If it returns an error, the stream will be interrupted and that error will bubble up
// and returned by this function, unless that error is ErrStreamClosed, in which case
Expand Down
19 changes: 19 additions & 0 deletions sqltests/expr/coalesce.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- test: simple case
> COALESCE(1,2,3)
1

-- test: with null
> COALESCE(null,2,3)
2

-- test: with different values type
> COALESCE('hey',2,3)
'hey'

-- test: with more than one null value with integer
> COALESCE(null, null, null,3)
3

-- test: with more than one null value with text
> COALESCE(null, null, null, 'hey')
'hey'

0 comments on commit 79af2d0

Please sign in to comment.