diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf
index 3e50438819d14413ae1df145e6160062a8f78ebd..6b25a24b1c06bb7b9776a8290a080018c2c13951 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 5985e48780f409126da4054fcd8d6975f1f49c3c..8af0c9dc7a53c58e78326397c79b1805d609e34e 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 778b4c8a5d388dcb112661a65309c6293921b71d..ce4d80b41f475791ccfbad3455a7fd4e00f469b3 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 0000000000000000000000000000000000000000..ab865bf4c1a87d2258d2a62208e46b712ac8fd7c
--- /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());
+}