From ee91fe16fc2ebe52a59dc4711279806e27683fa6 Mon Sep 17 00:00:00 2001
From: Igor Kuznetsov <kuznetsovin@gmail.com>
Date: Thu, 14 Apr 2022 16:57:31 +0300
Subject: [PATCH] feat: extract lua function to the loader

---
 src/executor/engine/cartridge.rs | 112 +++++++++++++++++++------------
 src/executor/result.rs           |   2 +-
 src/parser.rs                    |  18 ++++-
 test_app/README.md               |   2 +
 test_app/app/roles/api.lua       |   6 ++
 5 files changed, 93 insertions(+), 47 deletions(-)

diff --git a/src/executor/engine/cartridge.rs b/src/executor/engine/cartridge.rs
index 633d055e7d..d183d78332 100644
--- a/src/executor/engine/cartridge.rs
+++ b/src/executor/engine/cartridge.rs
@@ -258,10 +258,62 @@ impl Runtime {
         buckets: &Buckets,
     ) -> Result<BoxExecuteFormat, QueryPlannerError> {
         let lua = tarantool::lua_state();
-        match lua.exec(
-            r#"local vshard = require('vshard')
+
+        let exec_sql: LuaFunction<_> = lua.get("execute_sql").ok_or_else(|| {
+            QueryPlannerError::LuaError("Lua function `execute_sql` not found".into())
+        })?;
+
+        let lua_buckets = match buckets {
+            Buckets::All => vec![],
+            Buckets::Filtered(list) => list.iter().copied().collect(),
+        };
+
+        let waiting_timeout = &self.metadata().get_exec_waiting_timeout();
+        let res: BoxExecuteFormat =
+            match exec_sql.call_with_args((query, lua_buckets, waiting_timeout)) {
+                Ok(v) => v,
+                Err(e) => {
+                    say(
+                        SayLevel::Error,
+                        file!(),
+                        line!().try_into().unwrap_or(0),
+                        Option::from("exec_query"),
+                        &format!("{:?}", e),
+                    );
+                    return Err(QueryPlannerError::LuaError(format!("Lua error: {:?}", e)));
+                }
+            };
+
+        Ok(res)
+    }
+}
+
+/// Extra lua functions loader. It is necessary for query execution.
+///
+/// # Errors
+/// - Failed to load lua code.
+pub fn load_extra_function() -> Result<(), QueryPlannerError> {
+    let lua = tarantool::lua_state();
+
+    match lua.exec(
+        r#"local vshard = require('vshard')
     local yaml = require('yaml')
     local log = require('log')
+    local cartridge = require('cartridge')
+
+    function get_schema()
+        return cartridge.get_schema()
+    end
+
+    function get_waiting_timeout()
+        local cfg = cartridge.config_get_readonly()
+
+        if cfg["executor_waiting_timeout"] == nil then
+            return 0
+        end
+
+        return cfg["executor_waiting_timeout"]
+    end
 
     ---get_uniq_replicaset_for_buckets - gets unique set of replicaset by bucket list
     ---@param buckets table - list of buckets.
@@ -325,48 +377,20 @@ impl Runtime {
     return result
     end
 "#,
-        ) {
-            Ok(_) => {}
-            Err(e) => {
-                say(
-                    SayLevel::Error,
-                    file!(),
-                    line!().try_into().unwrap_or(0),
-                    Option::from("exec_query"),
-                    &format!("{:?}", e),
-                );
-                return Err(QueryPlannerError::LuaError(format!(
-                    "Failed lua code loading: {:?}",
-                    e
-                )));
-            }
+    ) {
+        Ok(_) => Ok(()),
+        Err(e) => {
+            say(
+                SayLevel::Error,
+                file!(),
+                line!().try_into().unwrap_or(0),
+                Option::from("exec_query"),
+                &format!("{:?}", e),
+            );
+            Err(QueryPlannerError::LuaError(format!(
+                "Failed lua code loading: {:?}",
+                e
+            )))
         }
-
-        let exec_sql: LuaFunction<_> = lua.get("execute_sql").ok_or_else(|| {
-            QueryPlannerError::LuaError("Lua function `execute_sql` not found".into())
-        })?;
-
-        let lua_buckets = match buckets {
-            Buckets::All => vec![],
-            Buckets::Filtered(list) => list.iter().copied().collect(),
-        };
-
-        let waiting_timeout = &self.metadata().get_exec_waiting_timeout();
-        let res: BoxExecuteFormat =
-            match exec_sql.call_with_args((query, lua_buckets, waiting_timeout)) {
-                Ok(v) => v,
-                Err(e) => {
-                    say(
-                        SayLevel::Error,
-                        file!(),
-                        line!().try_into().unwrap_or(0),
-                        Option::from("exec_query"),
-                        &format!("{:?}", e),
-                    );
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {:?}", e)));
-                }
-            };
-
-        Ok(res)
     }
 }
diff --git a/src/executor/result.rs b/src/executor/result.rs
index b1d4ac2dfe..efedaa6926 100644
--- a/src/executor/result.rs
+++ b/src/executor/result.rs
@@ -179,7 +179,7 @@ impl Default for BoxExecuteFormat {
 
 impl BoxExecuteFormat {
     /// Create empty query result set
-    #[allow(dead_code)]
+    #[must_use]
     pub fn new() -> Self {
         BoxExecuteFormat {
             metadata: Vec::new(),
diff --git a/src/parser.rs b/src/parser.rs
index 563ba05a72..103da88577 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -1,5 +1,3 @@
-mod extargs;
-
 use std::cell::RefCell;
 use std::convert::TryInto;
 use std::os::raw::c_int;
@@ -10,11 +8,14 @@ use tarantool::log::{say, SayLevel};
 use tarantool::tuple::{AsTuple, FunctionArgs, FunctionCtx, Tuple};
 
 use crate::errors::QueryPlannerError;
+use crate::executor::engine::cartridge::load_extra_function;
 use crate::executor::engine::{cartridge, Engine};
 use crate::executor::Query;
 
 use self::extargs::{BucketCalcArgs, BucketCalcArgsDict};
 
+mod extargs;
+
 thread_local!(static QUERY_ENGINE: RefCell<cartridge::Runtime> = RefCell::new(cartridge::Runtime::new().unwrap()));
 
 #[derive(Serialize, Deserialize)]
@@ -37,6 +38,19 @@ pub extern "C" fn invalidate_caching_schema(ctx: FunctionCtx, _: FunctionArgs) -
     0
 }
 
+#[no_mangle]
+pub extern "C" fn load_lua_extra_function(ctx: FunctionCtx, _: FunctionArgs) -> c_int {
+    match load_extra_function() {
+        Ok(_) => {
+            ctx.return_mp(&true).unwrap();
+            0
+        }
+        Err(e) => {
+            tarantool::set_error!(TarantoolErrorCode::ProcC, "{}", e.to_string())
+        }
+    }
+}
+
 #[no_mangle]
 pub extern "C" fn calculate_bucket_id(ctx: FunctionCtx, args: FunctionArgs) -> c_int {
     let args: Tuple = args.into();
diff --git a/test_app/README.md b/test_app/README.md
index 191d131681..e135a56850 100644
--- a/test_app/README.md
+++ b/test_app/README.md
@@ -34,6 +34,8 @@ The `storage` role has the `insert_map(space, values_map)` function, which inser
 
 ## General observations
 
+`Sbroad` library uses internal lua functions in the cartridge executor and preloads them with `load_lua_extra_function` call in the `init` cartridge function.
+
 As the `sbroad` library caches the cluster cartridge schema internally, any `sbroad` function that is called checks the internal cluster schema, and if that is empty it loads the schema from the main app. If the app schema was updated then the internal cache needs to be cleared. To clear the cache we need to add the `invalidate_caching_schema` call to the `apply_config` cartridge function.
 
 ## Local load testing
diff --git a/test_app/app/roles/api.lua b/test_app/app/roles/api.lua
index 2435755711..28b15dd970 100644
--- a/test_app/app/roles/api.lua
+++ b/test_app/app/roles/api.lua
@@ -88,6 +88,12 @@ local function init(opts) -- luacheck: no unused args
             if_not_exists = true, language = 'C' 
     })
 
+    box.schema.func.create('sbroad.load_lua_extra_function', {
+        if_not_exists = true, language = 'C'
+    })
+
+    box.func["sbroad.load_lua_extra_function"]:call({})
+
     return true
 end
 
-- 
GitLab