diff --git a/cmd/genji/doc/functions.go b/cmd/genji/doc/functions.go index da77f5d6..a40f6ed7 100644 --- a/cmd/genji/doc/functions.go +++ b/cmd/genji/doc/functions.go @@ -15,7 +15,8 @@ var builtinDocs = functionDocs{ "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": "Then len function returns length of the arg1 expression if arg1 evals to string, array or document, either returns NULL.", + "len": "The len function returns length of the arg1 expression if arg1 evals to string, array or document, either returns NULL.", + "lower": "The lower function returns arg1 to lower-case if arg1 evals to string", } var mathDocs = functionDocs{ diff --git a/internal/expr/functions/builtins.go b/internal/expr/functions/builtins.go index dd56a6ff..aaa0d8d2 100644 --- a/internal/expr/functions/builtins.go +++ b/internal/expr/functions/builtins.go @@ -2,6 +2,7 @@ package functions import ( "fmt" + "strings" "github.com/cockroachdb/errors" "github.com/genjidb/genji/document" @@ -67,6 +68,13 @@ var builtinFunctions = Definitions{ return &Len{Expr: args[0]}, nil }, }, + "lower": &definition{ + name: "lower", + arity: 1, + constructorFn: func(args ...expr.Expr) (expr.Function, error) { + return &Lower{Expr: args[0]}, nil + }, + }, } // BuiltinDefinitions returns a map of builtin functions. @@ -717,3 +725,43 @@ func (s *Len) Params() []expr.Expr { return []expr.Expr{s.Expr} } func (s *Len) String() string { return fmt.Sprintf("LEN(%v)", s.Expr) } + +// Lower is the LOWER function +// It returns the lower-case version of a string +type Lower struct { + Expr expr.Expr +} + +func (s* Lower) Eval(env *environment.Environment) (types.Value, error) { + val, err := s.Expr.Eval(env) + if err != nil { + return nil, err + } + + if val.Type() != types.TextValue { + return types.NewNullValue(), nil + } + + lowerCaseString := strings.ToLower(types.As[string](val)) + + return types.NewTextValue(lowerCaseString), nil +} + +func (s *Lower) IsEqual(other expr.Expr) bool { + if other == nil { + return false + } + + o, ok := other.(*Lower) + if !ok { + return false + } + + return expr.Equal(s.Expr, o.Expr) +} + +func (s *Lower) Params() []expr.Expr { return []expr.Expr{s.Expr} } + +func (s *Lower) String() string { + return fmt.Sprintf("LOWER(%v)", s.Expr) +} \ No newline at end of file diff --git a/sqltests/SELECT/lower.sql b/sqltests/SELECT/lower.sql new file mode 100644 index 00000000..2a120968 --- /dev/null +++ b/sqltests/SELECT/lower.sql @@ -0,0 +1,113 @@ +-- setup: +CREATE TABLE test( + a TEXT, + b INT, + c BOOL, + d DOUBLE, + e ARRAY, + f ( + ... + ) +); + +INSERT INTO test (a, b, c, d, e, f) VALUES ( + "FOO", + 42, + true, + 42.42, + ["A", "b", "C", "d", "E"], + { + a: "HELLO", + b: "WorlD" + } +); + +-- test: TEXT value +SELECT LOWER(a) FROM test; +/* result: +{ + "LOWER(a)": "foo" +} +*/ + + +-- test: INT value +SELECT LOWER(b) FROM test; +/* result: +{ + "LOWER(b)": NULL +} +*/ + + +-- test: BOOL value +SELECT LOWER(c) FROM test; +/* result: +{ + "LOWER(c)": NULL +} +*/ + +-- test: DOUBLE value +SELECT LOWER(d) FROM test; +/* result: +{ + "LOWER(d)": NULL +} +*/ + +-- test: ARRAY value +SELECT LOWER(e) FROM test; +/* result: +{ + "LOWER(e)": NULL +} +*/ + +-- test: DOCUMENT value +SELECT LOWER(f) FROM test; +/* result: +{ + "LOWER(f)": NULL +} +*/ + +-- test: cast INT +SELECT LOWER(CAST(b as TEXT)) FROM test; +/* result: +{ + "LOWER(CAST(b AS text))": "42" +} +*/ + +-- test: cast BOOL +SELECT LOWER(CAST(c as TEXT)) FROM test; +/* result: +{ + "LOWER(CAST(c AS text))": "true" +} +*/ + +-- test: cast DOUBLE +SELECT LOWER(CAST(d as TEXT)) FROM test; +/* result: +{ + "LOWER(CAST(d AS text))": "42.42" +} +*/ + +-- test: cast ARRAY +SELECT LOWER(CAST(e as TEXT)) FROM test; +/* result: +{ + "LOWER(CAST(e AS text))": "[\"a\", \"b\", \"c\", \"d\", \"e\"]" +} +*/ + +-- test: cast DOCUMENT +SELECT LOWER(CAST(f as TEXT)) FROM test; +/* result: +{ + "LOWER(CAST(f AS text))": "{\"a\": \"hello\", \"b\": \"world\"}" +} +*/