From 93dc653dc57a58702c4e8e8af9a52d3fd4fd58e6 Mon Sep 17 00:00:00 2001
From: Arseniy Volynets <a.volynets@picodata.io>
Date: Mon, 2 Sep 2024 14:35:55 +0300
Subject: [PATCH] feat: export lower/upper string functions

---
 doc/sql/query.ebnf                            |  7 +++++
 .../test_app/test/integration/api_test.lua    | 28 +++++++++++++++++++
 sbroad-core/src/executor/engine.rs            |  2 ++
 .../src/frontend/sql/ir/tests/funcs.rs        | 21 ++++++++++++++
 4 files changed, 58 insertions(+)
 create mode 100644 sbroad-core/src/frontend/sql/ir/tests/funcs.rs

diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf
index 3e50438819..6b25a24b1c 100644
--- a/doc/sql/query.ebnf
+++ b/doc/sql/query.ebnf
@@ -29,6 +29,10 @@ expression  ::= ('NOT'* (
                     | to_char
                     | to_date
                     | trim
+                    | substr
+                    | lower
+                    | upper
+                    | '(' expression ')'
                     | 'NOT'? 'EXISTS' '(' dql ')'
                     | '(' dql ')'
                     | '(' expression (',' expression)* ')'
@@ -56,6 +60,9 @@ trim        ::= 'TRIM' '('
                 ((('LEADING' | 'TRAILING' | 'BOTH')? removal_chars
                 | ('LEADING' | 'TRAILING' | 'BOTH')) 'FROM')? string ')'
 substr      ::= 'SUBSTR' '(' string ',' from (',' count)? ')'
+lower       ::= 'LOWER' '(' string ')'
+upper       ::= 'UPPER' '(' string ')'
+substr      ::= 'SUBSTR' '(' string ',' from (',' count)? ')'
 values      ::= 'VALUES'
                 ('(' (expression(',' expression)*) ')')
                 (',' ('(' (expression(',' expression)*) ')'))*
diff --git a/sbroad-cartridge/test_app/test/integration/api_test.lua b/sbroad-cartridge/test_app/test/integration/api_test.lua
index 5985e48780..8af0c9dc7a 100644
--- a/sbroad-cartridge/test_app/test/integration/api_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/api_test.lua
@@ -698,6 +698,34 @@ g.test_union_operator_works = function ()
     })
 end
 
+g.test_lower_upper = function ()
+    local api = cluster:server("api-1").net_box
+
+    local meta = {
+        {name = "a", type = "string"},
+        {name = "b", type = "string"},
+    }
+    local r, err = api:call("sbroad.execute", { [[
+        select lower("COLUMN_1") as a, upper("COLUMN_1") as b from (values ('Aba'))
+    ]] })
+
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, meta)
+    t.assert_equals(r.rows, {
+        {'aba', 'ABA'}
+    })
+
+    r, err = api:call("sbroad.execute", { [[
+        select upper(lower("COLUMN_1")) as a, lower(upper("COLUMN_1")) as b from (values ('Aba'))
+    ]] })
+
+    t.assert_equals(err, nil)
+    t.assert_equals(r.metadata, meta)
+    t.assert_equals(r.rows, {
+        {'ABA', 'aba'}
+    })
+end
+
 g.test_like_works = function ()
     local api = cluster:server("api-1").net_box
 
diff --git a/sbroad-core/src/executor/engine.rs b/sbroad-core/src/executor/engine.rs
index 778b4c8a5d..ce4d80b41f 100644
--- a/sbroad-core/src/executor/engine.rs
+++ b/sbroad-core/src/executor/engine.rs
@@ -80,6 +80,8 @@ pub fn get_builtin_functions() -> &'static [Function] {
                 Function::new_stable("to_date".into(), Type::Datetime, false),
                 Function::new_stable("to_char".into(), Type::String, false),
                 Function::new_stable("substr".into(), Type::String, true),
+                Function::new_stable("lower".into(), Type::String, true),
+                Function::new_stable("upper".into(), Type::String, true),
             ]
         })
     }
diff --git a/sbroad-core/src/frontend/sql/ir/tests/funcs.rs b/sbroad-core/src/frontend/sql/ir/tests/funcs.rs
new file mode 100644
index 0000000000..ab865bf4c1
--- /dev/null
+++ b/sbroad-core/src/frontend/sql/ir/tests/funcs.rs
@@ -0,0 +1,21 @@
+use crate::ir::transformation::helpers::sql_to_optimized_ir;
+use pretty_assertions::assert_eq;
+
+#[test]
+fn lower_upper() {
+    let input = r#"select upper(lower('a' || 'B')), upper(a) from t1"#;
+
+    let plan = sql_to_optimized_ir(input, vec![]);
+    println!("{}", plan.as_explain().unwrap());
+
+    let expected_explain = String::from(
+        r#"projection (upper((lower((ROW('a'::string) || ROW('B'::string)))::string))::string -> "col_1", upper(("t1"."a"::string))::string -> "col_2")
+    scan "t1"
+execution options:
+sql_vdbe_max_steps = 45000
+vtable_max_rows = 5000
+"#,
+    );
+
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
+}
-- 
GitLab