diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000000000000000000000000000000000000..5617d5bd634213fb61658a66e82ea8210b0695fb
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,4 @@
+[target.aarch64-apple-darwin]
+rustflags = [
+    "-C", "link-arg=-undefined",  "-C", "link-arg=dynamic_lookup"
+]
diff --git a/Cargo.toml b/Cargo.toml
index 1282ef2f9d384a543fcfcd4ef20b7c9c2049e701..a2f5f82156b6c4324574d69cbfca9da24bc0f8cc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,7 @@ edition = "2021"
 
 [dependencies]
 decimal = "2.1.0"
-fasthash = "0.4.0"
+hash32 = "0.2"
 itertools = "0.10.3"
 pest = "2.0"
 pest_derive = "2.0"
@@ -20,10 +20,10 @@ serde_yaml = "0.8"
 tarantool = { git = "https://sbroad-cargo-token:t-nZyqJVVuhGQv17BX6v@gitlab.com/picodata/picodata/tarantool-module.git", rev="d8921ec6"}
 traversal = "0.1.2"
 yaml-rust = "0.4.1"
+rmp-serde = "0.14"
 
 [dev-dependencies]
 pretty_assertions = "1.0.0"
-rmp-serde = "0.14"
 
 [lib]
 crate-type = ["cdylib"]
diff --git a/src/executor.rs b/src/executor.rs
index 078d8b4d736153b4b3752dc9da9466c1ea1432c5..cc6bcbf6df901590ed1d50ba92ece0d51832dc23 100644
--- a/src/executor.rs
+++ b/src/executor.rs
@@ -36,7 +36,7 @@ use std::collections::HashMap;
 mod bucket;
 pub mod engine;
 pub(crate) mod ir;
-mod result;
+pub mod result;
 mod vtable;
 
 impl Plan {
diff --git a/src/executor/engine.rs b/src/executor/engine.rs
index da1dfa7fdcf4187e112ad29fe8463c2d2c415f53..dc26e9b04643c4becccf1021727eca450d86be1e 100644
--- a/src/executor/engine.rs
+++ b/src/executor/engine.rs
@@ -2,11 +2,14 @@
 //!
 //! Traits that define an execution engine interface.
 
+use std::collections::HashMap;
+
 use crate::errors::QueryPlannerError;
 use crate::executor::bucket::Buckets;
 use crate::executor::ir::ExecutionPlan;
 use crate::executor::result::BoxExecuteFormat;
 use crate::executor::vtable::VirtualTable;
+use crate::ir::value::Value as IrValue;
 
 pub mod cartridge;
 
@@ -33,6 +36,13 @@ pub trait Metadata {
             format!("\"{}\"", s)
         }
     }
+
+    /// Provides an `Vec<&str>` with sharding keys or an error
+    ///
+    /// # Errors
+    /// - Metadata does not contains space
+    /// - Metadata contains incorrect sharding keys format
+    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<&str>, QueryPlannerError>;
 }
 
 /// An execution engine trait.
@@ -78,6 +88,17 @@ pub trait Engine {
         buckets: &Buckets,
     ) -> Result<BoxExecuteFormat, QueryPlannerError>;
 
+    /// Filter lua table values and return in right order
+    ///
+    /// # Errors
+    /// - args does not contains all sharding keys
+    /// - internal metadata errors
+    fn extract_sharding_keys(
+        &self,
+        space: String,
+        args: HashMap<String, IrValue>,
+    ) -> Result<Vec<IrValue>, QueryPlannerError>;
+
     /// Determine shard for query execution by sharding key value
     fn determine_bucket_id(&self, s: &str) -> u64;
 }
diff --git a/src/executor/engine/cartridge.rs b/src/executor/engine/cartridge.rs
index d1339467b2151a63839e130121454a36a5b24e07..633d055e7dabcd18c137133f29e927f78ef4f164 100644
--- a/src/executor/engine/cartridge.rs
+++ b/src/executor/engine/cartridge.rs
@@ -1,5 +1,6 @@
 //! Tarantool cartridge engine module.
 
+use std::collections::HashMap;
 use std::convert::TryInto;
 
 use tarantool::log::{say, SayLevel};
@@ -14,6 +15,7 @@ use crate::executor::ir::ExecutionPlan;
 use crate::executor::result::BoxExecuteFormat;
 use crate::executor::vtable::VirtualTable;
 use crate::executor::Metadata;
+use crate::ir::value::Value as IrValue;
 
 mod backend;
 pub mod cache;
@@ -155,6 +157,31 @@ impl Engine for Runtime {
         Ok(vtable)
     }
 
+    /// Extract from the `HashMap<String, Value>` only those values that
+    /// correspond to the fields of the sharding key.
+    /// Returns the values of sharding keys in ordered form
+    fn extract_sharding_keys(
+        &self,
+        space: String,
+        rec: HashMap<String, IrValue>,
+    ) -> Result<Vec<IrValue>, QueryPlannerError> {
+        self.metadata()
+            .get_sharding_key_by_space(space.as_str())?
+            .iter()
+            .try_fold(Vec::new(), |mut acc: Vec<IrValue>, &val| {
+                match rec.get(val) {
+                    Some(value) => {
+                        acc.push(value.clone());
+                        Ok(acc)
+                    }
+                    None => Err(QueryPlannerError::CustomError(format!(
+                        "The dict of args missed key/value to calculate bucket_id. Column: {}",
+                        val
+                    ))),
+                }
+            })
+    }
+
     /// Calculate bucket for a key.
     fn determine_bucket_id(&self, s: &str) -> u64 {
         str_to_bucket_id(s, self.bucket_count)
diff --git a/src/executor/engine/cartridge/cache.rs b/src/executor/engine/cartridge/cache.rs
index 2e30d4caaa4c3e845fc8529aac1aec83a3d7d1f1..02547089e86e9b7363399d9300bd70f3c1027cef 100644
--- a/src/executor/engine/cartridge/cache.rs
+++ b/src/executor/engine/cartridge/cache.rs
@@ -60,27 +60,6 @@ impl ClusterAppConfig {
         Err(QueryPlannerError::InvalidClusterSchema)
     }
 
-    /// Get table sharding key.
-    ///
-    /// # Panics
-    /// - Invalid schema.
-    #[allow(dead_code)]
-    #[must_use]
-    pub fn get_sharding_key_by_space(self, space: &str) -> Vec<String> {
-        let mut result = Vec::new();
-        let spaces = self.schema["spaces"].as_hash().unwrap();
-
-        for (space_name, params) in spaces.iter() {
-            let current_space_name = space_name.as_str().unwrap();
-            if current_space_name == space {
-                for k in params["sharding_key"].as_vec().unwrap() {
-                    result.push(Self::to_name(k.as_str().unwrap()));
-                }
-            }
-        }
-        result
-    }
-
     pub(in crate::executor::engine::cartridge) fn is_empty(&self) -> bool {
         self.schema.is_null()
     }
@@ -169,6 +148,28 @@ impl Metadata for ClusterAppConfig {
     fn get_exec_waiting_timeout(&self) -> u64 {
         self.waiting_timeout
     }
+
+    /// Get sharding keys by space name
+    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<&str>, QueryPlannerError> {
+        if let Some(vec) = self.schema["spaces"][space]["sharding_key"].as_vec() {
+            return vec
+                .iter()
+                .try_fold(Vec::new(), |mut acc: Vec<&str>, str| match str.as_str() {
+                    Some(val) => {
+                        acc.push(val);
+                        Ok(acc)
+                    }
+                    _ => Err(QueryPlannerError::CustomError(format!(
+                        "Schema {} contains incorrect sharding keys format",
+                        space
+                    ))),
+                });
+        }
+        Err(QueryPlannerError::CustomError(format!(
+            "Failed to get space {} or sharding key",
+            space
+        )))
+    }
 }
 
 #[cfg(test)]
diff --git a/src/executor/engine/cartridge/cache/tests.rs b/src/executor/engine/cartridge/cache/tests.rs
index 901eb527bfe7135827eb398fda597ff01d8051fd..1451c68136d000638f890401a09c46d66930a81b 100644
--- a/src/executor/engine/cartridge/cache/tests.rs
+++ b/src/executor/engine/cartridge/cache/tests.rs
@@ -85,12 +85,10 @@ fn test_yaml_schema_parser() {
     let mut s = ClusterAppConfig::new();
     s.load_schema(test_schema).unwrap();
 
-    let mut expected_keys = Vec::new();
-    expected_keys.push("\"identification_number\"".to_string());
-    expected_keys.push("\"product_code\"".to_string());
+    let expected_keys = vec!["identification_number", "product_code"];
 
     // FIXME: do we need "to_name()" here?
-    let actual_keys = s.get_sharding_key_by_space("hash_testing");
+    let actual_keys = s.get_sharding_key_by_space("hash_testing").unwrap();
     assert_eq!(actual_keys, expected_keys)
 }
 
diff --git a/src/executor/engine/cartridge/hash.rs b/src/executor/engine/cartridge/hash.rs
index 5cf31af77a05b6d84f18e461dc386c240179158c..1552289c084d4a87a0182efe91a792558ae3f782 100644
--- a/src/executor/engine/cartridge/hash.rs
+++ b/src/executor/engine/cartridge/hash.rs
@@ -1,13 +1,14 @@
 //! Bucket hash module.
 
-use fasthash::{murmur3::Hasher32, FastHasher};
-use std::hash::Hasher;
+use hash32::{Hasher, Murmur3Hasher};
 
-/// Determine bucket value using `murmur3` hash function
-pub(in crate::executor::engine) fn str_to_bucket_id(s: &str, bucket_count: usize) -> u64 {
-    let mut hash = Hasher32::new();
-    hash.write(s.as_bytes());
-    hash.finish() % bucket_count as u64 + 1
+#[must_use]
+/// A simple function to calculate the bucket id from a string slice.
+/// `(MurMur3 hash at str) % bucket_count + 1`
+pub fn str_to_bucket_id(s: &str, bucket_count: usize) -> u64 {
+    let mut hasher = Murmur3Hasher::default();
+    hasher.write(s.as_bytes());
+    u64::from(hasher.finish()) % bucket_count as u64 + 1
 }
 
 #[cfg(test)]
diff --git a/src/executor/engine/cartridge/hash/tests.rs b/src/executor/engine/cartridge/hash/tests.rs
index b7c50c2cb3489756305c68b9dea9b61c071a1dc9..29f138aa22200ec5bf6249135bc6da64b12ca78b 100644
--- a/src/executor/engine/cartridge/hash/tests.rs
+++ b/src/executor/engine/cartridge/hash/tests.rs
@@ -4,12 +4,15 @@ use super::*;
 fn test_bucket_id_by_str() {
     assert_eq!(str_to_bucket_id("100тесты", 30000), 17339);
 
-    assert_eq!(
-        str_to_bucket_id("4TEST5501605647472000000100000000d92beee8-749f-4539-aa15-3d2941dbb0f1x32https://google.com", 30000),
-        13815
+    assert_eq!(str_to_bucket_id(
+        "4TEST5501605647472000000100000000d9\
+            2beee8-749f-4539-aa15-3d2941dbb0f1x32https://google.com", 30000),
+               13815
     );
 
-    assert_eq!(360, str_to_bucket_id("1123", 30000),);
+    assert_eq!(str_to_bucket_id("1123", 30000), 360);
+
+    assert_eq!(str_to_bucket_id("daymonthyear", 30000), 3333);
 }
 
 #[test]
diff --git a/src/executor/engine/mock.rs b/src/executor/engine/mock.rs
index 0c9ec71d2c8afe680b1bd09695e965d1705f3737..8092b16f340aae21f405613ef2416aeb482be58b 100644
--- a/src/executor/engine/mock.rs
+++ b/src/executor/engine/mock.rs
@@ -9,10 +9,12 @@ use crate::executor::result::{BoxExecuteFormat, Value};
 use crate::executor::vtable::VirtualTable;
 use crate::executor::Metadata;
 use crate::ir::relation::{Column, Table, Type};
+use crate::ir::value::Value as IrValue;
 
 #[allow(clippy::module_name_repetitions)]
 #[derive(Debug, Clone)]
 pub struct MetadataMock {
+    schema: HashMap<String, Vec<String>>,
     tables: HashMap<String, Table>,
     bucket_count: usize,
 }
@@ -29,6 +31,14 @@ impl Metadata for MetadataMock {
     fn get_exec_waiting_timeout(&self) -> u64 {
         0
     }
+
+    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<&str>, QueryPlannerError> {
+        Ok(self
+            .schema
+            .get(space)
+            .map(|v| v.iter().map(|s| s.as_str()).collect::<Vec<&str>>())
+            .unwrap())
+    }
 }
 
 impl MetadataMock {
@@ -103,6 +113,15 @@ impl MetadataMock {
         );
 
         MetadataMock {
+            schema: [
+                ("EMPLOYEES".into(), vec!["ID".into()]),
+                (
+                    "hash_testing".into(),
+                    vec!["identification_number".into(), "product_code".into()],
+                ),
+            ]
+            .into_iter()
+            .collect(),
             tables,
             bucket_count: 10000,
         }
@@ -180,6 +199,22 @@ impl Engine for EngineMock {
         Ok(result)
     }
 
+    fn extract_sharding_keys(
+        &self,
+        space: String,
+        args: HashMap<String, IrValue>,
+    ) -> Result<Vec<IrValue>, QueryPlannerError> {
+        Ok(self
+            .metadata()
+            .get_sharding_key_by_space(&space)
+            .unwrap()
+            .iter()
+            .fold(Vec::new(), |mut acc: Vec<IrValue>, &v| {
+                acc.push(args.get(v).unwrap().clone());
+                acc
+            }))
+    }
+
     fn determine_bucket_id(&self, s: &str) -> u64 {
         str_to_bucket_id(s, self.metadata.bucket_count)
     }
diff --git a/src/executor/result.rs b/src/executor/result.rs
index 0e0fc1a720f530dbdc42679f9df01d978f3fd25f..82fffab1e2eae1eae463546f074428272f6ce8c3 100644
--- a/src/executor/result.rs
+++ b/src/executor/result.rs
@@ -1,7 +1,9 @@
 use std::fmt;
 
 use decimal::d128;
+use serde::de::Visitor;
 use serde::ser::{Serialize, SerializeMap, Serializer};
+use serde::Deserialize;
 use tarantool::tlua::{self, LuaRead};
 
 use crate::errors::QueryPlannerError;
@@ -65,6 +67,76 @@ impl Serialize for Value {
     }
 }
 
+struct ValueVistor;
+
+impl<'de> Visitor<'de> for ValueVistor {
+    type Value = Value;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("a tarantool value enum implementation")
+    }
+
+    fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Value::Boolean(value))
+    }
+    fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Value::Integer(value))
+    }
+
+    fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Value::Unsigned(value))
+    }
+
+    fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Value::Number(value))
+    }
+
+    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Value::String(v.to_string()))
+    }
+
+    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Value::String(value))
+    }
+
+    fn visit_unit<E>(self) -> Result<Self::Value, E>
+    where
+        E: serde::de::Error,
+    {
+        Ok(Value::Null(tlua::Null))
+    }
+}
+
+/// Custom Implementation `de::Deserialize`, because if using standard `#derive[Deserialize]`
+/// then each `Value` type record deserialize as Value
+/// and it doesn't correct for result format
+impl<'de> Deserialize<'de> for Value {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        deserializer.deserialize_any(ValueVistor)
+    }
+}
+
 impl Eq for Value {}
 
 type BoxExecuteTuple = Vec<Value>;
diff --git a/src/ir/value.rs b/src/ir/value.rs
index c85679e47662468f379e6bcac9295687aae28be3..f85761f6656292a292beb656d3aef974f90eb069 100644
--- a/src/ir/value.rs
+++ b/src/ir/value.rs
@@ -51,6 +51,29 @@ pub enum Value {
     String(String),
 }
 
+impl From<crate::executor::result::Value> for Value {
+    fn from(value: crate::executor::result::Value) -> Self {
+        match value {
+            crate::executor::result::Value::Boolean(v) => Value::Boolean(v),
+            // Here is absolutely stupid solution!
+            // d128 supports floating point values, and f64 conversion cannot cause errors. 
+            // But in crate d128 there is no implementation of `From<f64>`, and the development 
+            // of the crate is dead.
+            // We have only one way to convert f64 to d128 is a `FromStr` trait, which is also 
+            // not implemented idiomatically. Realization despite the signature with a potential 
+            // error, but always returns a value. For this reason, this trait does not implement 
+            // a Error, since we can be sure that all checks were passed when f64 was received.
+            crate::executor::result::Value::Number(v) => Value::Number(
+                d128::from_str(&v.to_string()).expect("Invalid decimal float literal"),
+            ),
+            crate::executor::result::Value::Integer(v) => Value::Number(d128::from(v)),
+            crate::executor::result::Value::String(v) => Value::String(v),
+            crate::executor::result::Value::Unsigned(v) => Value::Number(d128::from(v)),
+            crate::executor::result::Value::Null(_) => Value::Null,
+        }
+    }
+}
+
 impl Eq for Value {}
 
 impl fmt::Display for Value {
diff --git a/src/parser.rs b/src/parser.rs
index 12041bbc0188003743248bfebb1c983190b5bb31..563ba05a72e214bf7e30e4ad6e2c8a7a15a75930 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -1,3 +1,5 @@
+mod extargs;
+
 use std::cell::RefCell;
 use std::convert::TryInto;
 use std::os::raw::c_int;
@@ -7,9 +9,12 @@ use tarantool::error::TarantoolErrorCode;
 use tarantool::log::{say, SayLevel};
 use tarantool::tuple::{AsTuple, FunctionArgs, FunctionCtx, Tuple};
 
+use crate::errors::QueryPlannerError;
 use crate::executor::engine::{cartridge, Engine};
 use crate::executor::Query;
 
+use self::extargs::{BucketCalcArgs, BucketCalcArgsDict};
+
 thread_local!(static QUERY_ENGINE: RefCell<cartridge::Runtime> = RefCell::new(cartridge::Runtime::new().unwrap()));
 
 #[derive(Serialize, Deserialize)]
@@ -32,25 +37,58 @@ pub extern "C" fn invalidate_caching_schema(ctx: FunctionCtx, _: FunctionArgs) -
     0
 }
 
-#[derive(Serialize, Deserialize)]
-struct BucketCalcArgs {
-    pub val: String,
-}
-
-impl AsTuple for BucketCalcArgs {}
-
 #[no_mangle]
 pub extern "C" fn calculate_bucket_id(ctx: FunctionCtx, args: FunctionArgs) -> c_int {
     let args: Tuple = args.into();
     let args = args.into_struct::<BucketCalcArgs>().unwrap();
 
     QUERY_ENGINE.with(|e| {
-        let result = e.clone().into_inner().determine_bucket_id(&args.val);
+        let result = e.clone().into_inner().determine_bucket_id(&args.rec);
         ctx.return_mp(&result).unwrap();
         0
     })
 }
 
+#[no_mangle]
+pub extern "C" fn calculate_bucket_id_by_dict(ctx: FunctionCtx, args: FunctionArgs) -> c_int {
+    QUERY_ENGINE.with(|e| {
+        let mut engine = e.clone().into_inner();
+        // Update cartridge schema after cache invalidation by calling `apply_config()` in lua code.
+        if engine.has_metadata() {
+            match engine.load_metadata() {
+                Ok(_) => *e.borrow_mut() = engine.clone(),
+                Err(e) => {
+                    return tarantool::set_error!(TarantoolErrorCode::ProcC, "{}", e.to_string());
+                }
+            };
+        }
+
+        // Closure for more concise error propagation from calls nested in the bucket calculation
+        let propagate_err = || -> Result<u64, QueryPlannerError> {
+            // Deserialization error
+            let bca = BucketCalcArgsDict::try_from(args)?;
+            // Error in filtering bucket calculation arguments by sharding keys
+            let fk = engine
+                .extract_sharding_keys(bca.space, bca.rec)?
+                .into_iter()
+                .fold(String::new(), |mut acc, v| {
+                    let s: String = v.into();
+                    acc.push_str(s.as_str());
+                    acc
+                });
+            Ok(e.clone().into_inner().determine_bucket_id(fk.as_str()))
+        };
+
+        match propagate_err() {
+            Ok(bucket_id) => {
+                ctx.return_mp(&bucket_id).unwrap();
+                0
+            }
+            Err(e) => tarantool::set_error!(TarantoolErrorCode::ProcC, "{:?}", e),
+        }
+    })
+}
+
 #[derive(Debug, Serialize, Deserialize)]
 struct ExecQueryArgs {
     pub query: String,
diff --git a/src/parser/extargs.rs b/src/parser/extargs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9dc0281ce4c83561a7e745f3ad354b757dfdc11a
--- /dev/null
+++ b/src/parser/extargs.rs
@@ -0,0 +1,71 @@
+use std::{collections::HashMap, convert::TryFrom, ops::Deref};
+
+use serde::{de::Deserializer, Deserialize, Serialize};
+use tarantool::tuple::{AsTuple, FunctionArgs, Tuple};
+
+use crate::{errors::QueryPlannerError, executor::result::Value, ir::value::Value as IrValue};
+
+#[derive(Serialize, Deserialize)]
+pub struct BucketCalcArgs {
+    pub rec: String,
+}
+
+impl AsTuple for BucketCalcArgs {}
+
+#[derive(Debug, Default, Serialize, PartialEq)]
+/// Tuple with space name and `key:value` map of values
+pub struct BucketCalcArgsDict {
+    /// Space name as `String`
+    pub space: String,
+    /// A key:value `HashMap` with key String and custom type IrValue
+    pub rec: HashMap<String, IrValue>,
+}
+
+impl Deref for BucketCalcArgsDict {
+    type Target = HashMap<String, IrValue>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.rec
+    }
+}
+
+impl TryFrom<FunctionArgs> for BucketCalcArgsDict {
+    type Error = QueryPlannerError;
+
+    fn try_from(value: FunctionArgs) -> Result<Self, Self::Error> {
+        Tuple::from(value)
+            .into_struct::<BucketCalcArgsDict>()
+            .map_err(|e| {
+                QueryPlannerError::CustomError(format!(
+                    "Error then deserializing tuple into BucketCalcArgsDict! {:?}",
+                    e
+                ))
+            })
+    }
+}
+
+impl<'de> Deserialize<'de> for BucketCalcArgsDict {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        #[derive(Deserialize)]
+        #[serde(rename = "BucketCalcArgsDict")]
+        struct StructHelper(String, HashMap<String, Value>);
+
+        let struct_helper = StructHelper::deserialize(deserializer)?;
+        Ok(BucketCalcArgsDict {
+            space: struct_helper.0,
+            rec: struct_helper
+                .1
+                .into_iter()
+                .map(|(k, v)| (k, IrValue::from(v)))
+                .collect(),
+        })
+    }
+}
+
+impl AsTuple for BucketCalcArgsDict {}
+
+#[cfg(test)]
+mod tests;
diff --git a/src/parser/extargs/tests.rs b/src/parser/extargs/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..244913695fa0a2a256f776ecb6f3b40a98418d21
--- /dev/null
+++ b/src/parser/extargs/tests.rs
@@ -0,0 +1,59 @@
+use decimal::d128;
+
+use crate::{
+    executor::engine::{mock::EngineMock, Engine},
+    ir::value::Value as IrValue,
+    parser::extargs::BucketCalcArgsDict,
+};
+
+#[test]
+fn bucket_calc_args() {
+    let engine = EngineMock::new();
+    let args = BucketCalcArgsDict {
+        space: "EMPLOYEES".into(),
+        rec: [(String::from("ID"), IrValue::Number(d128!(100.0)))]
+            .iter()
+            .cloned()
+            .collect(),
+    };
+    let filtered_args = engine
+        .extract_sharding_keys(args.space, args.rec)
+        .unwrap()
+        .into_iter()
+        .fold(String::new(), |mut acc, v| {
+            let s: String = v.into();
+            acc.push_str(s.as_str());
+            acc
+        });
+
+    assert_eq!(engine.determine_bucket_id(&filtered_args), 2377);
+
+    let args = BucketCalcArgsDict {
+        space: "hash_testing".into(),
+        rec: [
+            (
+                String::from("identification_number"),
+                IrValue::Number(d128!(93312)),
+            ),
+            (
+                String::from("product_code"),
+                IrValue::String("fff100af".into()),
+            ),
+            (String::from("product_units"), IrValue::Number(d128!(10))),
+            (String::from("sys_op"), IrValue::Number(d128!(981.945))),
+        ]
+        .iter()
+        .cloned()
+        .collect(),
+    };
+    let filtered_args = engine
+        .extract_sharding_keys(args.space, args.rec)
+        .unwrap()
+        .into_iter()
+        .fold(String::new(), |mut acc, v| {
+            let s: String = v.into();
+            acc.push_str(s.as_str());
+            acc
+        });
+    assert_eq!(engine.determine_bucket_id(&filtered_args), 7704);
+}
diff --git a/test_app/app/roles/api.lua b/test_app/app/roles/api.lua
index d32c823f036b183ffa7008592e064e6fa6a857b6..24357557115da6ef209a22b88eeee82e22d8e54b 100644
--- a/test_app/app/roles/api.lua
+++ b/test_app/app/roles/api.lua
@@ -2,9 +2,12 @@ local vshard = require('vshard')
 local cartridge = require('cartridge')
 
 _G.query = nil
+_G.calculate_bucket_id = nil
+_G.calculate_bucket_id_by_dict = nil
 _G.insert_record = nil
 _G.sql_execute = nil
 
+
 local function query(q)
     local has_err, parser_res = pcall(
         function()
@@ -19,7 +22,8 @@ local function query(q)
     return parser_res
 end
 
-local function insert_record(space_name, values)
+local function calculate_bucket_id(space_name, values)
+    checks('string', 'table')
     local shard_key = cartridge.config_get_deepcopy().schema.spaces[space_name].sharding_key
 
     local shard_val = ''
@@ -27,7 +31,28 @@ local function insert_record(space_name, values)
         shard_val = shard_val .. tostring(values[key])
     end
 
-    values['bucket_id'] = box.func["sbroad.calculate_bucket_id"]:call({ shard_val })
+    local bucket_id = box.func["sbroad.calculate_bucket_id"]:call({ shard_val })
+    return bucket_id
+end
+
+local function calculate_bucket_id_by_dict(space_name, values) -- luacheck: no unused args
+    checks('string', 'table')
+
+    local has_err, calc_err = pcall(
+        function()
+            return box.func["sbroad.calculate_bucket_id_by_dict"]:call({ space_name, values })
+        end
+    )
+
+    if has_err == false then
+        return nil, calc_err
+    end
+
+    return calc_err
+end
+
+local function insert_record(space_name, values)
+    values['bucket_id'] = calculate_bucket_id(space_name, values)
 	local res = vshard.router.call(
         values['bucket_id'],
          "write",
@@ -41,13 +66,27 @@ local function init(opts) -- luacheck: no unused args
     -- if opts.is_master then
     -- end
     _G.query = query
+    _G.calculate_bucket_id = calculate_bucket_id
+    _G.calculate_bucket_id_by_dict = calculate_bucket_id_by_dict
     _G.insert_record = insert_record
     _G.sql_execute = sql_execute
 
-    box.schema.func.create('sbroad.parse_sql', { if_not_exists = true, language = 'C' })
-    box.schema.func.create('sbroad.invalidate_caching_schema', { if_not_exists = true, language = 'C' })
-    box.schema.func.create('sbroad.calculate_bucket_id', { if_not_exists = true, language = 'C' })
-    box.schema.func.create('sbroad.execute_query', { if_not_exists = true, language = 'C' })
+
+    box.schema.func.create('sbroad.parse_sql', { 
+            if_not_exists = true, language = 'C' 
+    })
+    box.schema.func.create('sbroad.invalidate_caching_schema', { 
+            if_not_exists = true, language = 'C' 
+    })
+    box.schema.func.create('sbroad.calculate_bucket_id', { 
+            if_not_exists = true, language = 'C' 
+    })
+    box.schema.func.create('sbroad.calculate_bucket_id_by_dict', { 
+            if_not_exists = true, language = 'C' 
+    })
+    box.schema.func.create('sbroad.execute_query', { 
+            if_not_exists = true, language = 'C' 
+    })
 
     return true
 end
diff --git a/test_app/test/integration/api_test.lua b/test_app/test/integration/api_test.lua
index c197ab5f18b936cdf05d8a9477a8aa5954174003..c0a333589d173f2feb75a26fb0313811daa3d23a 100644
--- a/test_app/test/integration/api_test.lua
+++ b/test_app/test/integration/api_test.lua
@@ -43,6 +43,21 @@ g.after_each(
     end
 )
 
+g.test_bucket_id_calculation = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("calculate_bucket_id", { "testing_space", { id = 1, name = "123", product_units = 1 } })
+    t.assert_equals(err, nil)
+    t.assert_equals(r, 360)
+
+    r, err = api:call("calculate_bucket_id_by_dict", { "testing_space", { id = 1, name = "123", product_units = 1 } })
+    t.assert_equals(err, nil)
+    t.assert_equals(r, 360)
+
+    _, err = api:call("calculate_bucket_id_by_dict", { "testing_space", { id = 1 }})
+    t.assert_equals(err, "CustomError(\"The dict of args missed key/value to calculate bucket_id. Column: name\")")
+end
+
 g.test_incorrect_query = function()
     local api = cluster:server("api-1").net_box
 
@@ -332,4 +347,4 @@ g.test_empty_motion_result = function()
             {1, "123", 1, 360}
         },
     })
-end
\ No newline at end of file
+end
diff --git a/test_app/test_app-scm-1.rockspec b/test_app/test_app-scm-1.rockspec
index fe2414906ee7e66cdad0f0a9bf08e22b595e284f..7cc9972857e5ad5c43d147c2458abcd192eb2085 100644
--- a/test_app/test_app-scm-1.rockspec
+++ b/test_app/test_app-scm-1.rockspec
@@ -8,8 +8,8 @@ dependencies = {
     'tarantool',
     'lua >= 5.1',
     'checks == 3.1.0-1',
-    'cartridge == 2.6.0-1',
-    'metrics == 0.8.0-1',
+    'cartridge == 2.7.3-1',
+    'metrics == 0.12.0-1',
     'cartridge-cli-extensions == 1.1.1-1',
     'luatest',
     'luacov'