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()), } }