From 84446ce63535027123c6ca396e3a95ac3ca25fce Mon Sep 17 00:00:00 2001
From: Arseniy Volynets <vol0ncar@yandex.ru>
Date: Thu, 11 Apr 2024 12:28:00 +0300
Subject: [PATCH] feat: support to_char function

- to_char(datetime, format), format is specified
as in `strftime` function:
https://man.freebsd.org/cgi/man.cgi?query=strftime&sektion=3
---
 doc/sql/query.ebnf                            |  1 +
 .../test_app/test/integration/api_test.lua    | 78 +++++++++++++++++++
 sbroad-core/src/builtins.lua                  | 22 ++++++
 sbroad-core/src/executor/engine.rs            |  2 +-
 4 files changed, 102 insertions(+), 1 deletion(-)

diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf
index 4b3490895..7b9abfccc 100644
--- a/doc/sql/query.ebnf
+++ b/doc/sql/query.ebnf
@@ -33,6 +33,7 @@ expression  ::= (table '.')? column
                | 'NOT' expression
                | '(' expression ')'
                | 'TO_DATE' '(' expression',' format ')'
+               | 'TO_CHAR' '(' expression',' format ')'
                | 'TRIM' '(' ((('LEADING' | 'TRAILING' | 'BOTH')? expression) | ('LEADING' | 'TRAILING' | 'BOTH')) 'FROM' expression ')'
 aggregate   ::= ('AVG' | 'COUNT' | 'MAX' | 'MIN' | 'SUM' | 'TOTAL') '(' expression ')'
                | 'GROUP_CONCAT' '(' expression ','  "'" string "'" ')'
diff --git a/sbroad-cartridge/test_app/test/integration/api_test.lua b/sbroad-cartridge/test_app/test/integration/api_test.lua
index 9d39a905b..4a1caaac2 100644
--- a/sbroad-cartridge/test_app/test/integration/api_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/api_test.lua
@@ -553,3 +553,81 @@ g.test_datetime_motion = function ()
     })
 end
 
+g.test_to_char = function ()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", { [[
+        select to_char("dt", 'to_char: %Y-%m-%d-%H-%M-%S-%z') from "datetime_t"
+    ]]})
+
+    t.assert_equals(err, nil)
+    t.assert_equals(r, {
+        metadata = {
+            {name = "COL_1", type = "string"},
+        },
+        rows = {
+            {"to_char: 2021-08-21-00-00-00-+0300"},
+            {"to_char: 2021-08-20-00-00-00-+0300"},
+        },
+    })
+
+    -- second argument is optional
+    -- FIXME: https://git.picodata.io/picodata/picodata/sbroad/-/issues/645
+    r, err = api:call("sbroad.execute", { [[
+        select to_char(to_date(COLUMN_1, '%Y %d'), null)
+        from (values ('2020 20'))
+    ]]})
+
+    t.assert_equals(err, nil)
+    t.assert_equals(r, {
+        metadata = {
+            {name = "COL_1", type = "string"},
+        },
+        rows = {
+            {box.NULL},
+        },
+    })
+
+    -- check we can use expressions inside to_char
+    r, err = api:call("sbroad.execute", { [[
+        select to_char(to_date(COLUMN_1, '%Y %d'), '%Y-%m-%d' || '-%H-%M-%S-%z')
+        from (values ('2020 20'))
+    ]]})
+
+    t.assert_equals(err, nil)
+    t.assert_equals(r, {
+        metadata = {
+            {name = "COL_1", type = "string"},
+        },
+        rows = {
+            {"2020-01-20-00-00-00-+0000"},
+        },
+    })
+
+    -- invalid modifier used
+    r, err = api:call("sbroad.execute", { [[
+        select to_char(to_date(COLUMN_1, '%Y %d'), '%i-%m-%d')
+        from (values ('2020 20'))
+    ]]})
+
+    t.assert_equals(err, nil)
+    t.assert_equals(r, {
+        metadata = {
+            {name = "COL_1", type = "string"},
+        },
+        rows = {
+            {"i-01-20"},
+        },
+    })
+
+    -- invalid argument
+    -- FIXME: https://git.picodata.io/picodata/picodata/sbroad/-/issues/644
+    -- we need to check function's argument types when building a plan
+    local _, error = api:call("sbroad.execute", { [[
+        select to_char('%d', '%i-%m-%d')
+        from (values ('2020 20'))
+    ]]})
+
+    t.assert_str_contains(tostring(error), "bad argument #1 to 'format' (number expected, got string)")
+end
+
diff --git a/sbroad-core/src/builtins.lua b/sbroad-core/src/builtins.lua
index 48dbd671d..0127a328c 100644
--- a/sbroad-core/src/builtins.lua
+++ b/sbroad-core/src/builtins.lua
@@ -16,6 +16,16 @@ builtins.TO_DATE = function (s, fmt)
     return res
 end
 
+builtins.TO_CHAR = function (date, fmt)
+  local res
+  if fmt then
+    res = date:format(fmt)
+  else
+    res = nil
+  end
+  return res
+end
+
 local function init()
   -- cartridge
   local module = 'sbroad'
@@ -45,6 +55,18 @@ local function init()
       is_deterministic = true,
       if_not_exists=true
   })
+
+  body = string.format("function(...) return %s.builtins.TO_CHAR(...) end",
+  module)
+  box.schema.func.create("TO_CHAR", {
+      language = 'LUA',
+      returns = 'string',
+      body = body,
+      param_list = {'datetime', 'string'},
+      exports = {'SQL'},
+      is_deterministic = true,
+      if_not_exists=true
+  })
 end
 
 return {
diff --git a/sbroad-core/src/executor/engine.rs b/sbroad-core/src/executor/engine.rs
index 518f2185f..bb2065f47 100644
--- a/sbroad-core/src/executor/engine.rs
+++ b/sbroad-core/src/executor/engine.rs
@@ -75,7 +75,7 @@ pub fn get_builtin_functions() -> &'static [Function] {
         BUILTINS.get_or_init(|| {
             vec![
                 Function::new_stable("\"TO_DATE\"".into(), Type::Datetime),
-                Function::new_stable("\"TRIM\"".into(), Type::String),
+                Function::new_stable("\"TO_CHAR\"".into(), Type::String),
             ]
         })
     }
-- 
GitLab