Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add coalesce function #513

Merged
merged 9 commits into from
Nov 23, 2023
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
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'
Loading