diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b27161194313a0fa3cb1a1cde069e7b28c27c890..b2a6bc61bd63a9e938e6baa7966e6275db85a661 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -11,7 +11,7 @@ build:
     stage: build
     tags:
         - docker
-    image: registry.gitlab.com/picodata/dockers/brod-builder:0.2
+    image: registry.gitlab.com/picodata/dockers/brod-builder:0.3
 
     script:
         - make
@@ -20,20 +20,20 @@ build:
         - target/debug/libsbroad.so
       expire_in: 1 week
 
-
-
 lint:
-    stage: build
+    stage: test
     tags:
         - docker
-    image: guangie88/rustfmt-clippy:stable
+    image: registry.gitlab.com/picodata/dockers/brod-builder:0.3
     script:
-        - cargo clippy -- -Dclippy::all -Wclippy::pedantic
+        - make lint
 
 test:
     stage: test
     tags:
         - docker
-    image: registry.gitlab.com/picodata/dockers/brod-builder:0.2
+    image: registry.gitlab.com/picodata/dockers/brod-builder:0.3
     script:
-        - make test
+        - make test_all
+        - ls target/debug/
+        - ls test_app/.rocks/lib/tarantool/
diff --git a/Cargo.toml b/Cargo.toml
index ea67d278e0cc48367c3fb5e7b8c0aec7ded663e4..c081e92bf17d9b77b5d04a8c58cfd739c1423f3f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ fasthash = "0.4.0"
 serde = { version = "1.0", features = ["derive"] }
 serde_yaml = "0.8"
 sqlparser = "0.11.0"
-tarantool = "0.4.2"
+tarantool = { git = "https://sbroad-cargo-token:t-nZyqJVVuhGQv17BX6v@gitlab.com/picodata/picodata/tarantool-module.git", rev="8e388030"}
 traversal = "0.1.2"
 yaml-rust = "0.4.1"
 
diff --git a/Makefile b/Makefile
index a627082c450ba9f2ef0473d118066701da31a69f..1367dc5da30eae0a6371d4ff9195a5207b1b91dd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,15 @@
 all: build
 
+OS := $(shell uname -s)
+ifeq ($(OS), Linux)
+	SRC_LIB = libsbroad.so
+	DEST_LIB = sbroad.so
+else
+	ifeq ($(OS), Darwin)
+		SRC_LIB = libsbroad.dylib
+		DEST_LIB = sbroad.dylib
+	endif
+endif
 build:
 	cargo build
 
@@ -12,8 +22,8 @@ test:
 lint:
 	cargo clippy -- -Dclippy::all -Wclippy::pedantic
 
-build_test_app_osx:
+build_test_app:
 	cd test_app && cartridge build
-	cp -rf target/debug/libsbroad.dylib test_app/.rocks/lib/tarantool/sbroad.dylib
+	cp -rf target/debug/$(SRC_LIB) test_app/.rocks/lib/tarantool/$(DEST_LIB)
 
-test_osx: test build build_test_app_osx integration_test_app
+test_all: test build build_test_app integration_test_app
diff --git a/src/lua_bridge.rs b/src/lua_bridge.rs
index 1ee3922a0d7cc3298fa3b6a829d809bc5cb0be3f..131468e9e3f7206020892e5ce1ee085b6858508f 100644
--- a/src/lua_bridge.rs
+++ b/src/lua_bridge.rs
@@ -1,152 +1,42 @@
-use std::convert::TryInto;
-use std::ffi::CStr;
-use std::os::raw::c_char;
+use tarantool::ffi::tarantool::luaT_state;
+use tarantool::hlua::{Lua, LuaError, LuaFunction};
 
-use tarantool::ffi::lua::{
-    luaT_state, lua_State, lua_getglobal, lua_pushinteger, lua_setfield, lua_settop, lua_tostring,
-    LUA_GLOBALSINDEX,
-};
+pub fn get_cluster_schema() -> Result<String, LuaError> {
+    let lua = unsafe { Lua::from_existing_state(luaT_state(), false) };
 
-use crate::errors::QueryPlannerError;
+    let get_schema: LuaFunction<_> = lua.eval("return require('cartridge').get_schema")?;
+    let res = get_schema.call()?;
 
-const LUA_FUNCS: &str = "local cartridge = require('cartridge');
-local vshard = require('vshard')
-local yaml = require('yaml')
-
-function execute_sql(bucket_id, query)
-    local data, err = vshard.router.call(
-        bucket_id,
-        'read',
-        'box.execute',
-        { query }
-    )
-
-    if err ~= nil then
-        error(err)
-    end
-
-    return yaml.encode(data)
-end
-
-function get_cluster_schema()
-  return cartridge.get_schema()
-end";
-
-extern "C" {
-    pub fn luaL_loadbuffer(
-        l: *mut lua_State,
-        buff: *const c_char,
-        sz: usize,
-        name: *const c_char,
-    ) -> i32;
-    pub fn lua_pcall(state: *mut lua_State, nargs: i32, nresults: i32, msgh: i32) -> i32;
-    pub fn lua_pushlstring(state: *mut lua_State, s: *const c_char, len: usize);
+    Ok(res)
 }
 
-#[derive(Debug, Clone, Copy)]
-pub struct LuaBridge {
-    state: *mut lua_State,
-}
+pub fn exec_query(bucket_id: u64, query: &str) -> Result<String, LuaError> {
+    let lua = unsafe { Lua::from_existing_state(luaT_state(), false) };
 
-#[allow(dead_code)]
-impl LuaBridge {
-    pub fn new() -> Self {
-        let state = unsafe { luaT_state() };
+    lua.exec(
+        r#"
+        local vshard = require('vshard')
+        local yaml = require('yaml')
 
-        let global_name = unsafe { crate::c_ptr!("lua_bridge_initialized") };
-
-        let mut res = unsafe {
-            luaL_loadbuffer(
-                state,
-                LUA_FUNCS.as_ptr().cast::<i8>(),
-                LUA_FUNCS.len(),
-                crate::c_ptr!("helpers"),
+        function execute_sql(bucket_id, query)
+            local data, err = vshard.router.call(
+                bucket_id,
+                'read',
+                'box.execute',
+                { query }
             )
-        };
-        if res != 0 {
-            panic!();
-        };
-
-        res = unsafe { lua_pcall(state, 0, 0, 0) };
-        if res != 0 {
-            panic!();
-        };
-
-        // set global lua state
-        unsafe {
-            lua_pushinteger(state, 1); // push the value onto the stack
-            lua_setfield(state, LUA_GLOBALSINDEX, global_name); // save the global value
-        };
-
-        LuaBridge { state }
-    }
-
-    pub fn get_cluster_schema(self) -> String {
-        unsafe {
-            lua_getglobal(self.state, crate::c_ptr!("get_cluster_schema"));
-        }
 
-        let res = unsafe { lua_pcall(self.state, 0, 1, 0) };
-        if res != 0 {
-            panic!("{} {:?}", res, unsafe {
-                CStr::from_ptr(lua_tostring(self.state, -1))
-            });
-        };
+            if err ~= nil then
+                error(err)
+            end
 
-        // copy result string pointer from stack, because lua_tostring returns const char *
-        let uri = unsafe { lua_tostring(self.state, -1) };
-        let r = unsafe { CStr::from_ptr(uri) };
+            return yaml.encode(data)
+        end
+    "#,
+    )?;
 
-        // copy result string from raw pointer to safety variable
-        let result = r.to_str().unwrap().to_string();
-
-        //remove result pointer result from stack
-        self.lua_pop(1);
-
-        result
-    }
-
-    pub fn execute_sql(self, bucket_id: usize, query: &str) -> Result<String, QueryPlannerError> {
-        let bid: isize = match bucket_id.try_into() {
-            Ok(r) => r,
-            Err(_e) => return Err(QueryPlannerError::IncorrectBucketIdError),
-        };
-
-        unsafe {
-            lua_getglobal(self.state, crate::c_ptr!("execute_sql"));
-            lua_pushinteger(self.state, bid);
-
-            // lua c api recommends `lua_pushlstring` for arbitrary strings and `lua_pushstring` for zero-terminated strings
-            // as &str in Rust is byte array need use lua_pushlstring that doesn't transform input query string to zero-terminate CString
-            lua_pushlstring(self.state, query.as_ptr().cast::<i8>(), query.len());
-        }
-
-        let res = unsafe { lua_pcall(self.state, 2, 1, 0) };
-        if res != 0 {
-            panic!("{} {:?}", res, unsafe {
-                CStr::from_ptr(lua_tostring(self.state, -1))
-            });
-        };
-
-        let uri = unsafe { lua_tostring(self.state, -1) };
-        let r = unsafe { CStr::from_ptr(uri) };
-        let result = r.to_str().unwrap().to_string();
-
-        //remove result pointer result from stack
-        self.lua_pop(1);
-
-        Ok(result)
-    }
-
-    fn lua_pop(self, n: i32) {
-        unsafe { lua_settop(self.state, -n - 1) }
-    }
-}
+    let exec_sql: LuaFunction<_> = lua.get("execute_sql").unwrap();
+    let res = exec_sql.call_with_args((bucket_id, query))?;
 
-#[macro_export]
-macro_rules! c_ptr {
-    ($s:literal) => {
-        ::std::ffi::CStr::from_bytes_with_nul_unchecked(::std::concat!($s, "\0").as_bytes())
-            .as_ptr()
-    };
+    Ok(res)
 }
diff --git a/src/parser.rs b/src/parser.rs
index 86f41d13fcb6f73c12d1f5a9f3512850c9a6f6d1..998f55d63f981386d96b0f9845ee716f4517a6ca 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -10,12 +10,11 @@ use tarantool::tuple::{AsTuple, FunctionArgs, FunctionCtx, Tuple};
 
 use crate::bucket::str_to_bucket_id;
 use crate::errors::QueryPlannerError;
-use crate::lua_bridge::LuaBridge;
+use crate::lua_bridge::{exec_query, get_cluster_schema};
 use crate::query::ParsedTree;
 use crate::schema::Cluster;
 
 thread_local!(static CARTRIDGE_SCHEMA: RefCell<Cluster> = RefCell::new(Cluster::new()));
-thread_local!(static LUA_STATE: RefCell<LuaBridge> = RefCell::new(LuaBridge::new()));
 
 #[derive(Serialize, Deserialize)]
 struct Args {
@@ -33,13 +32,16 @@ pub extern "C" fn parse_sql(ctx: FunctionCtx, args: FunctionArgs) -> c_int {
     let args: Tuple = args.into();
     let args = args.into_struct::<Args>().unwrap();
 
-    let lua = LUA_STATE.try_with(|s| s.clone().into_inner()).unwrap();
-
     CARTRIDGE_SCHEMA.with(|s| {
         let mut schema = s.clone().into_inner();
         // Update cartridge schema after cache invalidation by calling `apply_config()` in lua code.
         if schema.is_empty() {
-            let text_schema = lua.get_cluster_schema();
+            let text_schema = match get_cluster_schema() {
+                Ok(s) => s,
+                Err(e) => {
+                    return tarantool::set_error!(TarantoolErrorCode::ProcC, "{}", e.to_string())
+                }
+            };
             schema = Cluster::from(text_schema);
 
             *s.borrow_mut() = schema.clone();
@@ -92,7 +94,7 @@ pub extern "C" fn calculate_bucket_id(ctx: FunctionCtx, args: FunctionArgs) -> c
 
 #[derive(Debug, Serialize, Deserialize)]
 struct ExecQueryArgs {
-    pub bucket_id: usize,
+    pub bucket_id: u64,
     pub query: String,
 }
 
@@ -103,14 +105,11 @@ pub extern "C" fn execute_query(ctx: FunctionCtx, args: FunctionArgs) -> c_int {
     let args: Tuple = args.into();
     let args = args.into_struct::<ExecQueryArgs>().unwrap();
 
-    match LUA_STATE.try_with(|s| s.clone().into_inner()) {
-        Ok(lua) => match lua.execute_sql(args.bucket_id, &args.query) {
-            Ok(p) => {
-                ctx.return_mp(&p).unwrap();
-                0
-            }
-            Err(e) => tarantool::set_error!(TarantoolErrorCode::ProcC, "{}", e.to_string()),
-        },
+    match exec_query(args.bucket_id, &args.query) {
+        Ok(p) => {
+            ctx.return_mp(&p).unwrap();
+            0
+        }
         Err(e) => tarantool::set_error!(TarantoolErrorCode::ProcC, "{}", e.to_string()),
     }
 }