From 73cb224b9cda521118b9d1e61fa35cf03681ce74 Mon Sep 17 00:00:00 2001
From: Arseniy Volynets <vol0ncar@yandex.ru>
Date: Wed, 24 Apr 2024 10:56:19 +0000
Subject: [PATCH] feat: support CURRENT_DATE special func

---
 Cargo.lock                                    | 12 ++++++
 doc/sql/query.ebnf                            |  1 +
 .../test_app/test/integration/api_test.lua    | 38 +++++++++++++++++++
 sbroad-core/Cargo.toml                        |  1 +
 sbroad-core/src/frontend/sql.rs               | 12 ++++++
 sbroad-core/src/frontend/sql/ir/tests.rs      | 26 +++++++++++++
 sbroad-core/src/frontend/sql/query.pest       |  3 +-
 7 files changed, 92 insertions(+), 1 deletion(-)

diff --git a/Cargo.lock b/Cargo.lock
index 44532d2a19..82a7a74499 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1168,6 +1168,7 @@ dependencies = [
  "serde_yaml",
  "smol_str",
  "tarantool",
+ "time",
  "uuid 1.4.0",
 ]
 
@@ -1440,8 +1441,10 @@ version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
 dependencies = [
+ "itoa",
  "serde",
  "time-core",
+ "time-macros",
 ]
 
 [[package]]
@@ -1450,6 +1453,15 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
 
+[[package]]
+name = "time-macros"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
+dependencies = [
+ "time-core",
+]
+
 [[package]]
 name = "tinytemplate"
 version = "1.2.1"
diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf
index 21d7e4fae0..a0329c7831 100644
--- a/doc/sql/query.ebnf
+++ b/doc/sql/query.ebnf
@@ -33,6 +33,7 @@ expression  ::= (table '.')? column
                | ('(' (expression(',' expression)*) ')')
                | 'NOT' expression
                | '(' expression ')'
+               | 'CURRENT_DATE'
                | 'TO_DATE' '(' expression',' format ')'
                | 'TO_CHAR' '(' expression',' format ')'
                | 'TRIM' '(' ((('LEADING' | 'TRAILING' | 'BOTH')? expression) | ('LEADING' | 'TRAILING' | 'BOTH')) 'FROM' expression ')'
diff --git a/sbroad-cartridge/test_app/test/integration/api_test.lua b/sbroad-cartridge/test_app/test/integration/api_test.lua
index 4a1caaac23..581bf0b37f 100644
--- a/sbroad-cartridge/test_app/test/integration/api_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/api_test.lua
@@ -631,3 +631,41 @@ g.test_to_char = function ()
     t.assert_str_contains(tostring(error), "bad argument #1 to 'format' (number expected, got string)")
 end
 
+g.test_current_date = function ()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", { [[
+        select currEnt_dAte from "datetime_t"
+    ]]})
+
+    local current_date = datetime.now()
+    current_date:set({hour=0, min=0, sec=0, nsec=0, tzoffset=0})
+    t.assert_equals(err, nil)
+    t.assert_equals(r, {
+        metadata = {
+            {name = "COL_1", type = "datetime"},
+        },
+        rows = {
+            {current_date},
+            {current_date},
+        },
+    })
+
+    r, err = api:call("sbroad.execute", { [[
+        select to_char(COL_1, '%Y') from (select to_date(COLUMN_2, '%Y.%m.%d') from (
+          values ('2077.1.1'), ('2000.10.10')
+        ))
+        where COL_1 > CURRENT_DATE
+    ]]})
+    t.assert_equals(err, nil)
+    t.assert_equals(r, {
+        metadata = {
+            {name = "COL_1", type = "string"},
+        },
+        rows = {
+            {'2077'},
+        },
+    })
+end
+
+
diff --git a/sbroad-core/Cargo.toml b/sbroad-core/Cargo.toml
index 1715b77873..e699d3ec86 100644
--- a/sbroad-core/Cargo.toml
+++ b/sbroad-core/Cargo.toml
@@ -28,6 +28,7 @@ serde_yaml = "0.8"
 tarantool = { git = "https://git.picodata.io/picodata/picodata/tarantool-module.git" }
 uuid = { version = "1.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
 smol_str = { version = "0.2.1", features = ["serde"] }
+time = { version = ">=0.3.0, <0.3.18", features = ["formatting"] }
 
 [dev-dependencies]
 pretty_assertions = "1.3"
diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs
index 563fdc1a98..b41b16190d 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -13,7 +13,9 @@ use std::{
     collections::{HashMap, HashSet},
     str::FromStr,
 };
+use tarantool::datetime::Datetime;
 use tarantool::index::{IndexType, RtreeIndexDistanceType};
+use time::{OffsetDateTime, Time};
 
 use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::engine::{helpers::normalize_name_for_space_api, Metadata};
@@ -1179,6 +1181,9 @@ where
     /// As `ColumnPositionMap` is used for parsing references and as it may be shared for the same
     /// relational node we cache it so that we don't have to recreate it every time.
     column_positions_cache: HashMap<usize, ColumnPositionMap>,
+    /// Time at the start of the plan building stage without timezone.
+    /// It is used to replace CURRENT_DATE to actual value.
+    current_time: OffsetDateTime,
 }
 
 impl<'worker, M> ExpressionsWorker<'worker, M>
@@ -1194,6 +1199,7 @@ where
             met_tnt_param: false,
             met_pg_param: false,
             column_positions_cache: HashMap::with_capacity(COLUMN_POSITIONS_CACHE_CAPACITY),
+            current_time: OffsetDateTime::now_utc(),
         }
     }
 
@@ -1852,6 +1858,12 @@ where
                     }?;
                     ParseExpression::Cast { cast_type, child: Box::new(child_parse_expr) }
                 }
+                Rule::CurrentDate => {
+                    let date = worker.current_time.replace_time(Time::MIDNIGHT);
+                    let val = Value::Datetime(Datetime::from_inner(date));
+                    let plan_id = plan.add_const(val);
+                    ParseExpression::PlanId { plan_id }
+                }
                 Rule::CountAsterisk => {
                     let plan_id = plan.nodes.push(Node::Expression(Expression::CountAsterisk));
                     ParseExpression::PlanId { plan_id }
diff --git a/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad-core/src/frontend/sql/ir/tests.rs
index 08229263d1..da4c7b664f 100644
--- a/sbroad-core/src/frontend/sql/ir/tests.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests.rs
@@ -7,6 +7,7 @@ use crate::ir::transformation::helpers::sql_to_optimized_ir;
 use crate::ir::tree::traversal::PostOrder;
 use crate::ir::value::Value;
 use pretty_assertions::assert_eq;
+use time::{format_description, OffsetDateTime, Time};
 
 fn sql_to_optimized_ir_add_motions_err(query: &str) -> SbroadError {
     let metadata = &RouterConfigurationMock::new();
@@ -3395,6 +3396,31 @@ vtable_max_rows = 5000
     )
 }
 
+#[test]
+fn front_sql_current_date() {
+    let input = r#"
+    SELECT current_date FROM (values ('2010/10/10'))
+    where to_date('2010/10/10', '%Y/%d/%m') < current_Date"#;
+
+    let today = OffsetDateTime::now_utc().replace_time(Time::MIDNIGHT);
+    let format = format_description::parse("[year]-[month]-[day]").unwrap();
+    let plan = sql_to_optimized_ir(input, vec![]);
+    let expected_explain = format!(
+        r#"projection ({today} 0:00:00.0 +00:00:00::datetime -> "COL_1")
+    selection ROW("TO_DATE"(('2010/10/10'::string, '%Y/%d/%m'::string))::datetime) < ROW({today} 0:00:00.0 +00:00:00::datetime)
+        scan
+            values
+                value row (data=ROW('2010/10/10'::string))
+execution options:
+sql_vdbe_max_steps = 45000
+vtable_max_rows = 5000
+"#,
+        today = today.format(&format).unwrap()
+    );
+
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
+}
+
 #[test]
 fn front_sql_check_non_null_columns_specified() {
     let input = r#"insert into "test_space" ("sys_op") values (1)"#;
diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest
index 5b1ffb35da..524af41093 100644
--- a/sbroad-core/src/frontend/sql/query.pest
+++ b/sbroad-core/src/frontend/sql/query.pest
@@ -233,7 +233,7 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* }
     ExprAtomValue = _{ UnaryNot* ~ AtomicExpr ~ IsNullPostfix? }
         UnaryNot   = @{ NotFlag }
         IsNullPostfix = { ^"is" ~ NotFlag? ~ ^"null" }
-        AtomicExpr = _{ Literal | Parameter | Cast | Trim | IdentifierWithOptionalContinuation | ExpressionInParentheses | UnaryOperator | SubQuery | Row }
+        AtomicExpr = _{ Literal | Parameter | Cast | Trim | CurrentDate | IdentifierWithOptionalContinuation | ExpressionInParentheses | UnaryOperator | SubQuery | Row }
             Literal = { True | False | Null | Double | Decimal | Unsigned | Integer | SingleQuotedString }
                 True     = { ^"true" }
                 False    = { ^"false" }
@@ -252,6 +252,7 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* }
                     FunctionArgs = { Distinct? ~ (Expr ~ ("," ~ Expr)*)? }
                     CountAsterisk = { "*" }
             ExpressionInParentheses = { "(" ~ Expr ~ ")" }
+            CurrentDate = { ^"current_date" }
             Trim = {
                 ^"trim" ~ "("
                 ~ (((TrimKind? ~ TrimPattern) | TrimKind) ~ ^"from")? ~ TrimTarget
-- 
GitLab