From 2c31c6888c6bf8b9ce7f06f6745fb90993fb41cb Mon Sep 17 00:00:00 2001
From: Denis Smirnov <sd@picodata.io>
Date: Tue, 20 Jun 2023 15:29:36 +0700
Subject: [PATCH] feat: reduce memory allocations on msgpack serialization

---
 .../src/api/calculate_bucket_id.rs            |   6 +-
 .../test_app/test/integration/api_test.lua    |   2 +-
 sbroad-core/src/backend/sql/ir.rs             |   7 +-
 sbroad-core/src/backend/sql/space.rs          |  21 +-
 sbroad-core/src/executor/engine.rs            |   4 +
 sbroad-core/src/executor/engine/helpers.rs    |  99 ++++-----
 sbroad-core/src/executor/engine/mock.rs       |  10 +-
 sbroad-core/src/executor/result.rs            |   4 +-
 sbroad-core/src/executor/result/tests.rs      |  22 +-
 sbroad-core/src/executor/tests.rs             | 110 ++++-----
 sbroad-core/src/executor/tests/between.rs     |  10 +-
 sbroad-core/src/executor/tests/bucket_id.rs   |  14 +-
 .../src/executor/tests/empty_motion.rs        |   6 +-
 sbroad-core/src/executor/tests/not_eq.rs      |  10 +-
 sbroad-core/src/executor/tests/not_in.rs      |   6 +-
 sbroad-core/src/ir/value.rs                   | 208 ++++++++++++------
 16 files changed, 291 insertions(+), 248 deletions(-)

diff --git a/sbroad-cartridge/src/api/calculate_bucket_id.rs b/sbroad-cartridge/src/api/calculate_bucket_id.rs
index dbf2c8dffa..40e1f2f61e 100644
--- a/sbroad-cartridge/src/api/calculate_bucket_id.rs
+++ b/sbroad-cartridge/src/api/calculate_bucket_id.rs
@@ -9,7 +9,7 @@ use serde::{de::Deserializer, Deserialize, Serialize};
 use crate::api::helper::load_config;
 use crate::api::COORDINATOR_ENGINE;
 use sbroad::executor::engine::{Router, Vshard};
-use sbroad::ir::value::{EncodedValue, Value};
+use sbroad::ir::value::{LuaValue, Value};
 use sbroad::log::tarantool_error;
 
 #[derive(Debug, Default, PartialEq, Eq)]
@@ -29,7 +29,7 @@ impl<'de> Deserialize<'de> for ArgsMap {
     {
         #[derive(Deserialize)]
         #[serde(rename = "FunctionArgs")]
-        struct StructHelper(HashMap<String, EncodedValue>, String);
+        struct StructHelper(HashMap<String, LuaValue>, String);
 
         let mut struct_helper = StructHelper::deserialize(deserializer)?;
         let rec: HashMap<String, Value> = struct_helper
@@ -62,7 +62,7 @@ impl<'de> Deserialize<'de> for ArgsTuple {
     {
         #[derive(Deserialize)]
         #[serde(rename = "FunctionArgs")]
-        struct StructHelper(Vec<EncodedValue>, String);
+        struct StructHelper(Vec<LuaValue>, String);
 
         let mut struct_helper = StructHelper::deserialize(deserializer)?;
         let rec: Vec<Value> = struct_helper.0.drain(..).map(Value::from).collect();
diff --git a/sbroad-cartridge/test_app/test/integration/api_test.lua b/sbroad-cartridge/test_app/test/integration/api_test.lua
index 25eff7b808..f5da5c5e45 100644
--- a/sbroad-cartridge/test_app/test/integration/api_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/api_test.lua
@@ -214,7 +214,7 @@ g.test_query_errored = function()
     local _, err = api:call("sbroad.execute", { [[SELECT * FROM "testing_space" where "id" = ?]], {invalid_type_param} })
     t.assert_equals(
         tostring(err), "Sbroad Error: pattern with parameters parsing error: " ..
-        [[Decode(Syntax("data did not match any variant of untagged enum EncodedValue"))]]
+        [[Decode(Syntax("data did not match any variant of untagged enum LuaValue"))]]
     )
 
     -- check err when params lenght is less then amount of sign `?`
diff --git a/sbroad-core/src/backend/sql/ir.rs b/sbroad-core/src/backend/sql/ir.rs
index 207de27a66..19e9cc1bcd 100644
--- a/sbroad-core/src/backend/sql/ir.rs
+++ b/sbroad-core/src/backend/sql/ir.rs
@@ -13,7 +13,7 @@ use crate::executor::bucket::Buckets;
 use crate::executor::ir::ExecutionPlan;
 use crate::ir::expression::Expression;
 use crate::ir::operator::Relational;
-use crate::ir::value::{EncodedValue, Value};
+use crate::ir::value::{LuaValue, Value};
 use crate::ir::Node;
 use crate::otm::{
     child_span, current_id, deserialize_context, force_trace, get_tracer, inject_context, query_id,
@@ -59,7 +59,7 @@ impl TryFrom<FunctionArgs> for PatternWithParams {
 #[derive(Deserialize, Serialize)]
 pub struct EncodedPatternWithParams(
     String,
-    Option<Vec<EncodedValue>>,
+    Option<Vec<LuaValue>>,
     Option<HashMap<String, String>>,
     Option<String>,
     bool,
@@ -67,8 +67,7 @@ pub struct EncodedPatternWithParams(
 
 impl From<PatternWithParams> for EncodedPatternWithParams {
     fn from(mut value: PatternWithParams) -> Self {
-        let encoded_params: Vec<EncodedValue> =
-            value.params.drain(..).map(EncodedValue::from).collect();
+        let encoded_params: Vec<LuaValue> = value.params.drain(..).map(LuaValue::from).collect();
         EncodedPatternWithParams(
             value.pattern,
             Some(encoded_params),
diff --git a/sbroad-core/src/backend/sql/space.rs b/sbroad-core/src/backend/sql/space.rs
index 65180fe2dc..9b76e077f3 100644
--- a/sbroad-core/src/backend/sql/space.rs
+++ b/sbroad-core/src/backend/sql/space.rs
@@ -8,7 +8,7 @@ mod prod_imports {
     pub use crate::error;
     pub use crate::errors::{Action, Entity};
     pub use crate::ir::relation::Column;
-    pub use crate::ir::value::EncodedValue;
+    pub use crate::ir::value::{EncodedValue, Value};
     pub use tarantool::index::{FieldType, IndexOptions, IndexType, Part};
     pub use tarantool::space::{Field, Space, SpaceCreateOptions};
     pub use tarantool::tuple::Tuple;
@@ -97,24 +97,13 @@ impl TmpSpace {
                 }
             }
             for (idx, tuples) in vtable.get_tuples_with_buckets(buckets).iter().enumerate() {
-                let mut extended_value: Vec<EncodedValue> = Vec::with_capacity(tuples.len() + 1);
+                let mut data: Vec<EncodedValue> = Vec::with_capacity(tuples.len() + 1);
                 for (v, c) in tuples.iter().zip(vtable.get_columns().iter()) {
                     let casted_value = v.cast(&c.r#type)?;
-                    extended_value.push(EncodedValue::from(casted_value));
+                    data.push(casted_value);
                 }
-                extended_value.push(EncodedValue::Unsigned(idx as u64));
-                let data = match rmp_serde::to_vec(&extended_value) {
-                    Ok(data) => data,
-                    Err(e) => {
-                        cleanup(space);
-                        return Err(SbroadError::FailedTo(
-                            Action::Serialize,
-                            Some(Entity::Value),
-                            format!("to tuple buffer (msgpack bytes): {e}"),
-                        ));
-                    }
-                };
-                let tuple = match Tuple::try_from_slice(data.as_slice()) {
+                data.push(EncodedValue::from(Value::Unsigned(idx as u64)));
+                let tuple = match Tuple::new(&data) {
                     Ok(tuple) => tuple,
                     Err(e) => {
                         cleanup(space);
diff --git a/sbroad-core/src/executor/engine.rs b/sbroad-core/src/executor/engine.rs
index ed1de01764..d66d3115d8 100644
--- a/sbroad-core/src/executor/engine.rs
+++ b/sbroad-core/src/executor/engine.rs
@@ -259,5 +259,9 @@ pub trait Vshard {
     fn get_random_bucket(&self) -> Buckets;
 
     /// Determine shard for query execution by sharding key value
+    ///
+    /// # Errors
+    /// - Internal error. Under normal conditions we should always return
+    ///   bucket id successfully.
     fn determine_bucket_id(&self, s: &[&Value]) -> u64;
 }
diff --git a/sbroad-core/src/executor/engine/helpers.rs b/sbroad-core/src/executor/engine/helpers.rs
index 2eed3b9457..8f249cd0c3 100644
--- a/sbroad-core/src/executor/engine/helpers.rs
+++ b/sbroad-core/src/executor/engine/helpers.rs
@@ -38,7 +38,7 @@ use crate::{
         relation::{Column, ColumnRole, Type},
         transformation::redistribution::{MotionKey, MotionPolicy},
         tree::Snapshot,
-        value::{EncodedValue, Value},
+        value::{MsgPackValue, Value},
         Node, Plan,
     },
 };
@@ -343,73 +343,54 @@ pub fn execute_dml(
     }
 
     // Check if the virtual table have been dispatched (case 2) or built locally (case 1).
-    if let Some(vtables) = &mut optional.exec_plan.vtables {
-        if let Some(mut vtable) = vtables.mut_map().remove(&insert_child_id) {
-            let space = Space::find(&space_name).ok_or_else(|| {
-                SbroadError::Invalid(Entity::Space, Some(format!("space {space_name} not found")))
-            })?;
-            // There are no long-living references to the virtual table on the storage
-            // (we can have the ones only on the router while dispatching subplans).
-            // So we should never produce any memory copy here with `Rc::make_mut()`.
-            let vtable = Rc::make_mut(&mut vtable);
-            let mut tuples = std::mem::take(vtable.get_mut_tuples());
-            start_transaction(|| -> Result<(), SbroadError> {
-                for (bucket_id, positions) in vtable.get_mut_index().drain() {
-                    for pos in positions {
-                        let mut vt_tuple = {
-                            let tuple = tuples.get_mut(pos).ok_or_else(|| {
+    let vtable = optional.exec_plan.get_motion_vtable(insert_child_id)?;
+    let space = Space::find(&space_name).ok_or_else(|| {
+        SbroadError::Invalid(Entity::Space, Some(format!("space {space_name} not found")))
+    })?;
+    start_transaction(|| -> Result<(), SbroadError> {
+        for (bucket_id, positions) in vtable.get_index().iter() {
+            for pos in positions {
+                let vt_tuple = vtable.get_tuples().get(*pos).ok_or_else(|| {
+                    SbroadError::Invalid(
+                        Entity::VirtualTable,
+                        Some(format!(
+                            "tuple at position {pos} not found in virtual table"
+                        )),
+                    )
+                })?;
+                let mut insert_tuple = Vec::with_capacity(builder.len());
+                for command in &builder {
+                    // We don't produce any additional allocations as `MsgPackValue` keeps
+                    // a reference to the original value. The only allocation is for message
+                    // pack serialization, but it is unavoidable.
+                    match command {
+                        TupleBuilderCommands::TakePosition(pos) => {
+                            let value = vt_tuple.get(*pos).ok_or_else(|| {
                                 SbroadError::Invalid(
-                                    Entity::VirtualTable,
+                                    Entity::Tuple,
                                     Some(format!(
-                                        "tuple at position {pos} not found in virtual table"
+                                        "column at position {pos} not found in virtual table"
                                     )),
                                 )
                             })?;
-                            std::mem::take(tuple)
-                        };
-                        let mut insert_tuple = Vec::with_capacity(builder.len());
-                        for command in &builder {
-                            match command {
-                                TupleBuilderCommands::TakePosition(pos) => {
-                                    let value = {
-                                        let value = vt_tuple.get_mut(*pos).ok_or_else(||
-                                            SbroadError::Invalid(
-                                                Entity::Tuple,
-                                                Some(format!(
-                                                    "column at position {pos} not found in virtual table"
-                                                ))
-                                            ))?;
-                                        std::mem::take(value)
-                                    };
-                                    insert_tuple.push(EncodedValue::from(value));
-                                }
-                                TupleBuilderCommands::SetValue(value) => {
-                                    insert_tuple.push(EncodedValue::from(value.clone()));
-                                }
-                                TupleBuilderCommands::CalculateBucketId(_) => {
-                                    insert_tuple.push(EncodedValue::Unsigned(bucket_id));
-                                }
-                            }
+                            insert_tuple.push(MsgPackValue::from(value));
+                        }
+                        TupleBuilderCommands::SetValue(value) => {
+                            insert_tuple.push(MsgPackValue::from(value));
+                        }
+                        TupleBuilderCommands::CalculateBucketId(_) => {
+                            insert_tuple.push(MsgPackValue::Unsigned(bucket_id));
                         }
-                        space.insert(&insert_tuple).map_err(|e| {
-                            SbroadError::FailedTo(
-                                Action::Insert,
-                                Some(Entity::Space),
-                                format!("{e}"),
-                            )
-                        })?;
-                        result.row_count += 1;
                     }
                 }
-                Ok(())
-            })?;
+                space.insert(&insert_tuple).map_err(|e| {
+                    SbroadError::FailedTo(Action::Insert, Some(Entity::Space), format!("{e}"))
+                })?;
+                result.row_count += 1;
+            }
         }
-    } else {
-        return Err(SbroadError::NotFound(
-            Entity::VirtualTable,
-            "in the execution plan while executiong DML on the storage".into(),
-        ));
-    }
+        Ok(())
+    })?;
 
     Ok(result)
 }
diff --git a/sbroad-core/src/executor/engine/mock.rs b/sbroad-core/src/executor/engine/mock.rs
index 466b929001..1c57083619 100644
--- a/sbroad-core/src/executor/engine/mock.rs
+++ b/sbroad-core/src/executor/engine/mock.rs
@@ -25,7 +25,7 @@ use crate::ir::function::Function;
 use crate::ir::helpers::RepeatableState;
 use crate::ir::relation::{Column, ColumnRole, SpaceEngine, Table, Type};
 use crate::ir::tree::Snapshot;
-use crate::ir::value::{EncodedValue, Value};
+use crate::ir::value::{LuaValue, Value};
 use crate::ir::Plan;
 
 use super::helpers::normalize_name_from_sql;
@@ -832,8 +832,8 @@ fn exec_on_some(bucket: u64, query: &str) -> ProducerResult {
     let mut result = ProducerResult::new();
 
     result.rows.push(vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(String::from(query)),
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(String::from(query)),
     ]);
 
     result
@@ -843,8 +843,8 @@ fn exec_on_all(query: &str) -> ProducerResult {
     let mut result = ProducerResult::new();
 
     result.rows.push(vec![
-        EncodedValue::String(String::from("Execute query on all buckets")),
-        EncodedValue::String(String::from(query)),
+        LuaValue::String(String::from("Execute query on all buckets")),
+        LuaValue::String(String::from(query)),
     ]);
 
     result
diff --git a/sbroad-core/src/executor/result.rs b/sbroad-core/src/executor/result.rs
index 7cab30d638..7798555d22 100644
--- a/sbroad-core/src/executor/result.rs
+++ b/sbroad-core/src/executor/result.rs
@@ -10,10 +10,10 @@ use crate::executor::vtable::VirtualTable;
 use crate::ir::operator::Relational;
 use crate::ir::relation::{Column, ColumnRole, Type};
 use crate::ir::tree::traversal::{PostOrder, REL_CAPACITY};
-use crate::ir::value::{EncodedValue, Value};
+use crate::ir::value::{LuaValue, Value};
 use crate::ir::Plan;
 
-type ExecutorTuple = Vec<EncodedValue>;
+type ExecutorTuple = Vec<LuaValue>;
 
 #[derive(LuaRead, Debug, Deserialize, PartialEq, Eq, Clone)]
 pub struct MetadataColumn {
diff --git a/sbroad-core/src/executor/result/tests.rs b/sbroad-core/src/executor/result/tests.rs
index 2bb10a9ea9..326aaa6a84 100644
--- a/sbroad-core/src/executor/result/tests.rs
+++ b/sbroad-core/src/executor/result/tests.rs
@@ -13,9 +13,9 @@ fn box_execute_result_serialize() {
             MetadataColumn::new("count".into(), "unsigned".into()),
         ],
         rows: vec![vec![
-            EncodedValue::Integer(1),
-            EncodedValue::String("тест".into()),
-            EncodedValue::Unsigned(1),
+            LuaValue::Integer(1),
+            LuaValue::String("тест".into()),
+            LuaValue::Unsigned(1),
         ]],
     };
 
@@ -58,16 +58,16 @@ fn convert_to_vtable() {
         ],
         rows: vec![
             vec![
-                EncodedValue::Integer(1),
-                EncodedValue::String("тест".into()),
-                EncodedValue::Unsigned(1),
-                EncodedValue::Decimal(decimal!(1.5)),
+                LuaValue::Integer(1),
+                LuaValue::String("тест".into()),
+                LuaValue::Unsigned(1),
+                LuaValue::Decimal(decimal!(1.5)),
             ],
             vec![
-                EncodedValue::Integer(2),
-                EncodedValue::String("тест2".into()),
-                EncodedValue::Unsigned(5),
-                EncodedValue::Decimal(decimal!(2.0)),
+                LuaValue::Integer(2),
+                LuaValue::String("тест2".into()),
+                LuaValue::Unsigned(5),
+                LuaValue::Decimal(decimal!(2.0)),
             ],
         ],
     };
diff --git a/sbroad-core/src/executor/tests.rs b/sbroad-core/src/executor/tests.rs
index 0a8d3e5100..5b663afd15 100644
--- a/sbroad-core/src/executor/tests.rs
+++ b/sbroad-core/src/executor/tests.rs
@@ -9,7 +9,7 @@ use crate::ir::operator::Relational;
 use crate::ir::relation::{Column, ColumnRole, Type};
 use crate::ir::transformation::redistribution::MotionPolicy;
 
-use crate::ir::value::{EncodedValue, Value};
+use crate::ir::value::{LuaValue, Value};
 
 use super::*;
 
@@ -30,8 +30,8 @@ fn shard_query() {
     let param1 = Value::from(1_u64);
     let bucket = coordinator.determine_bucket_id(&[&param1]);
     expected.rows.push(vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {}",
                 r#"SELECT "test_space"."FIRST_NAME" FROM "test_space""#,
@@ -69,8 +69,8 @@ fn shard_union_query() {
     let param1 = Value::from(1_u64);
     let bucket = query.coordinator.determine_bucket_id(&[&param1]);
     expected.rows.push(vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {}{} {} {}{} {}",
                 r#"SELECT "t3"."id""#,
@@ -108,8 +108,8 @@ fn map_reduce_query() {
     let bucket = query.coordinator.determine_bucket_id(&[&param1, &param457]);
 
     expected.rows.push(vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(
             String::from(
                 PatternWithParams::new(
                     format!(
@@ -166,8 +166,8 @@ fn linker_test() {
 
     expected.rows.extend(vec![
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket3}]")),
-            EncodedValue::String(
+            LuaValue::String(format!("Execute query on a bucket [{bucket3}]")),
+            LuaValue::String(
                 String::from(
                     PatternWithParams::new(
                         format!(
@@ -181,8 +181,8 @@ fn linker_test() {
             ),
         ],
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket2}]")),
-            EncodedValue::String(
+            LuaValue::String(format!("Execute query on a bucket [{bucket2}]")),
+            LuaValue::String(
                 String::from(
                     PatternWithParams::new(
                         format!(
@@ -250,8 +250,8 @@ fn union_linker_test() {
 
     expected.rows.extend(vec![
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket3}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket3}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {}{} {} {} {} {} {} {}{} {}",
                     r#"SELECT "t1"."id", "t1"."FIRST_NAME""#,
@@ -270,8 +270,8 @@ fn union_linker_test() {
             ))),
         ],
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket2}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket2}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {}{} {} {} {} {} {} {}{} {}",
                     r#"SELECT "t1"."id", "t1"."FIRST_NAME""#,
@@ -349,8 +349,8 @@ WHERE "t3"."id" = 2 AND "t8"."identification_number" = 2"#;
     let bucket2 = query.coordinator.determine_bucket_id(&[&param2]);
 
     expected.rows.extend(vec![vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket2}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket2}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{}, {}, {} {}{} {} {} {} {} {} {}{} {} {}{} {} {}",
                 r#"SELECT "t3"."id""#,
@@ -437,8 +437,8 @@ fn join_linker2_test() {
     let bucket1 = query.coordinator.determine_bucket_id(&[&param1]);
 
     expected.rows.extend(vec![vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket1}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket1}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {} {} {} {} {}",
                 r#"SELECT "t1"."id" FROM (SELECT"#,
@@ -509,8 +509,8 @@ fn join_linker3_test() {
     let bucket1 = query.coordinator.determine_bucket_id(&[&param1]);
 
     expected.rows.extend(vec![vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket1}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket1}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {} {} {} {}",
                 r#"SELECT "t2"."id1" FROM"#,
@@ -604,8 +604,8 @@ fn join_linker4_test() {
 
     expected.rows.extend(vec![
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket2}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket2}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {} {} {} {} {} {}",
                     r#"SELECT "T1"."id" FROM (SELECT"#,
@@ -620,8 +620,8 @@ fn join_linker4_test() {
             ))),
         ],
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket1}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket1}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {} {} {} {} {} {}",
                     r#"SELECT "T1"."id" FROM (SELECT"#,
@@ -712,8 +712,8 @@ on q."f" = "t1"."a""#;
     let mut expected = ProducerResult::new();
 
     expected.rows.extend(vec![vec![
-        EncodedValue::String("Execute query on all buckets".to_string()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".to_string()),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {} {} {}",
                 r#"SELECT "t1"."a", "t1"."b", "Q"."f", "Q"."B" FROM"#,
@@ -783,8 +783,8 @@ fn anonymous_col_index_test() {
     let bucket3 = query.coordinator.determine_bucket_id(&[&param3]);
     expected.rows.extend(vec![
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket3}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket3}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {} {} {} {} {} {} {} {} {}",
                     "SELECT",
@@ -802,8 +802,8 @@ fn anonymous_col_index_test() {
             ))),
         ],
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket2}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket2}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {} {} {} {} {} {} {} {} {}",
                     "SELECT",
@@ -842,8 +842,8 @@ fn sharding_column1_test() {
     let param1 = Value::from(1_u64);
     let bucket = query.coordinator.determine_bucket_id(&[&param1]);
     expected.rows.push(vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {} {}",
                 r#"SELECT "test_space"."id", "test_space"."sysFrom","#,
@@ -873,8 +873,8 @@ fn sharding_column2_test() {
     let param1 = Value::from(1_u64);
     let bucket = query.coordinator.determine_bucket_id(&[&param1]);
     expected.rows.push(vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {} {}",
                 r#"SELECT "test_space"."id", "test_space"."sysFrom","#,
@@ -936,8 +936,8 @@ fn insert1_test() {
 
     expected.rows.extend(vec![
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket1}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket1}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {}",
                     r#"INSERT INTO "t" ("b")"#,
@@ -949,8 +949,8 @@ fn insert1_test() {
             ))),
         ],
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket2}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket2}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {}",
                     r#"INSERT INTO "t" ("b")"#,
@@ -986,8 +986,8 @@ fn insert2_test() {
     let bucket = query.coordinator.determine_bucket_id(&[&param1, &param2]);
 
     expected.rows.extend(vec![vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {}",
                 // We don't generate SQL for insertion, so we just check a storage plan
@@ -1055,8 +1055,8 @@ fn insert3_test() {
 
     expected.rows.extend(vec![
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket1}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket1}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {}",
                     r#"INSERT INTO "t" ("b", "a")"#,
@@ -1068,8 +1068,8 @@ fn insert3_test() {
             ))),
         ],
         vec![
-            EncodedValue::String(format!("Execute query on a bucket [{bucket2}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{bucket2}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {}",
                     r#"INSERT INTO "t" ("b", "a")"#,
@@ -1106,8 +1106,8 @@ fn insert4_test() {
     let bucket = query.coordinator.determine_bucket_id(&[&param1, &param2]);
 
     expected.rows.extend(vec![vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {}",
                 // We don't generate SQL for insertion, so we just check a storage plan
@@ -1170,8 +1170,8 @@ fn insert5_test() {
     let bucket = query.coordinator.determine_bucket_id(&[&param1, &param2]);
 
     expected.rows.extend(vec![vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {}",
                 r#"INSERT INTO "t" ("b", "a")"#,
@@ -1278,8 +1278,8 @@ fn insert8_test() {
     let bucket = query.coordinator.determine_bucket_id(&[&param1, &param2]);
 
     expected.rows.extend(vec![vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(
             String::from(
                 PatternWithParams::new(
                     format!(
@@ -1365,8 +1365,8 @@ pub(crate) fn broadcast_check(sql: &str, pattern: &str, params: Vec<Value>) {
     let mut expected = ProducerResult::new();
 
     expected.rows.push(vec![
-        EncodedValue::String("Execute query on all buckets".to_string()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".to_string()),
+        LuaValue::String(String::from(PatternWithParams::new(
             pattern.to_string(),
             params,
         ))),
@@ -1424,8 +1424,8 @@ fn groupby_linker_test() {
     let mut expected = ProducerResult::new();
     for buc in buckets {
         expected.rows.extend(vec![vec![
-            EncodedValue::String(format!("Execute query on a bucket [{buc}]")),
-            EncodedValue::String(String::from(PatternWithParams::new(
+            LuaValue::String(format!("Execute query on a bucket [{buc}]")),
+            LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {} {}",
                     r#"SELECT "column_12" as "ii" FROM"#,
diff --git a/sbroad-core/src/executor/tests/between.rs b/sbroad-core/src/executor/tests/between.rs
index 95a4c78019..a6b689fb9d 100644
--- a/sbroad-core/src/executor/tests/between.rs
+++ b/sbroad-core/src/executor/tests/between.rs
@@ -7,7 +7,7 @@ use crate::executor::vtable::VirtualTable;
 use crate::ir::relation::{Column, ColumnRole, Type};
 use crate::ir::transformation::redistribution::tests::get_motion_id;
 use crate::ir::transformation::redistribution::MotionPolicy;
-use crate::ir::value::{EncodedValue, Value};
+use crate::ir::value::{LuaValue, Value};
 
 use super::*;
 
@@ -52,8 +52,8 @@ fn between1_test() {
     // Validate the result.
     let mut expected = ProducerResult::new();
     expected.rows.extend(vec![vec![
-        EncodedValue::String("Execute query on all buckets".to_string()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".to_string()),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {} {}",
                 r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#,
@@ -110,8 +110,8 @@ fn between2_test() {
     // Validate the result.
     let mut expected = ProducerResult::new();
     expected.rows.extend(vec![vec![
-        EncodedValue::String("Execute query on all buckets".to_string()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".to_string()),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {} {}",
                 r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#,
diff --git a/sbroad-core/src/executor/tests/bucket_id.rs b/sbroad-core/src/executor/tests/bucket_id.rs
index ef19271f03..22658dcdb7 100644
--- a/sbroad-core/src/executor/tests/bucket_id.rs
+++ b/sbroad-core/src/executor/tests/bucket_id.rs
@@ -3,7 +3,7 @@ use pretty_assertions::assert_eq;
 use crate::backend::sql::ir::PatternWithParams;
 use crate::executor::engine::mock::RouterRuntimeMock;
 use crate::executor::result::ProducerResult;
-use crate::ir::value::{EncodedValue, Value};
+use crate::ir::value::{LuaValue, Value};
 
 use super::*;
 
@@ -22,8 +22,8 @@ fn bucket1_test() {
     let mut expected = ProducerResult::new();
 
     expected.rows.push(vec![
-        EncodedValue::String("Execute query on all buckets".to_string()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".to_string()),
+        LuaValue::String(String::from(PatternWithParams::new(
             r#"SELECT "t1"."a", "t1"."b", "t1"."bucket_id" FROM "t1""#.to_string(),
             vec![],
         ))),
@@ -50,8 +50,8 @@ fn bucket2_test() {
     let bucket = query.coordinator.determine_bucket_id(&[&param1, &param2]);
 
     expected.rows.push(vec![
-        EncodedValue::String(format!("Execute query on a bucket [{bucket}]")),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String(format!("Execute query on a bucket [{bucket}]")),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {}",
                 r#"SELECT "t1"."a", "t1"."bucket_id", "t1"."b" FROM "t1""#,
@@ -78,8 +78,8 @@ fn bucket3_test() {
     let mut expected = ProducerResult::new();
 
     expected.rows.push(vec![
-        EncodedValue::String("Execute query on all buckets".to_string()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".to_string()),
+        LuaValue::String(String::from(PatternWithParams::new(
             r#"SELECT "t1"."a", "t1"."b", "FUNC" (?) as "COL_1" FROM "t1""#.to_string(),
             vec![Value::from("111".to_string())],
         ))),
diff --git a/sbroad-core/src/executor/tests/empty_motion.rs b/sbroad-core/src/executor/tests/empty_motion.rs
index 69fb287258..7524aa0af6 100644
--- a/sbroad-core/src/executor/tests/empty_motion.rs
+++ b/sbroad-core/src/executor/tests/empty_motion.rs
@@ -6,7 +6,7 @@ use crate::executor::result::ProducerResult;
 use crate::executor::vtable::VirtualTable;
 use crate::ir::relation::{Column, ColumnRole, Type};
 use crate::ir::transformation::redistribution::MotionPolicy;
-use crate::ir::value::{EncodedValue, Value};
+use crate::ir::value::{LuaValue, Value};
 
 use super::*;
 
@@ -60,8 +60,8 @@ fn empty_motion1_test() {
 
     let mut expected = ProducerResult::new();
     expected.rows.extend(vec![vec![
-        EncodedValue::String("Execute query on all buckets".into()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".into()),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {} {} {} {} {} {} {} {} {} {} {} {} {}",
                 r#"SELECT "Q"."a", "Q"."b" FROM"#,
diff --git a/sbroad-core/src/executor/tests/not_eq.rs b/sbroad-core/src/executor/tests/not_eq.rs
index ee3c9e67ea..ca1ed7ea58 100644
--- a/sbroad-core/src/executor/tests/not_eq.rs
+++ b/sbroad-core/src/executor/tests/not_eq.rs
@@ -7,7 +7,7 @@ use crate::executor::vtable::VirtualTable;
 use crate::ir::relation::{Column, ColumnRole, Type};
 use crate::ir::transformation::redistribution::tests::get_motion_id;
 use crate::ir::transformation::redistribution::MotionPolicy;
-use crate::ir::value::{EncodedValue, Value};
+use crate::ir::value::{LuaValue, Value};
 
 use super::*;
 
@@ -36,8 +36,8 @@ fn not_eq1_test() {
     // Validate the result.
     let mut expected = ProducerResult::new();
     expected.rows.extend(vec![vec![
-        EncodedValue::String("Execute query on all buckets".to_string()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".to_string()),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {}",
                 r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#,
@@ -91,8 +91,8 @@ fn not_eq2_test() {
     // Validate the result.
     let mut expected = ProducerResult::new();
     expected.rows.extend(vec![vec![
-        EncodedValue::String("Execute query on all buckets".to_string()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".to_string()),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {}",
                 r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#,
diff --git a/sbroad-core/src/executor/tests/not_in.rs b/sbroad-core/src/executor/tests/not_in.rs
index eedbd3ba33..e35128fec3 100644
--- a/sbroad-core/src/executor/tests/not_in.rs
+++ b/sbroad-core/src/executor/tests/not_in.rs
@@ -7,7 +7,7 @@ use crate::executor::vtable::VirtualTable;
 use crate::ir::relation::{Column, ColumnRole, Type};
 use crate::ir::transformation::redistribution::tests::get_motion_id;
 use crate::ir::transformation::redistribution::MotionPolicy;
-use crate::ir::value::{EncodedValue, Value};
+use crate::ir::value::{LuaValue, Value};
 
 use super::*;
 
@@ -53,8 +53,8 @@ fn not_in1_test() {
     // Validate the result.
     let mut expected = ProducerResult::new();
     expected.rows.extend(vec![vec![
-        EncodedValue::String("Execute query on all buckets".to_string()),
-        EncodedValue::String(String::from(PatternWithParams::new(
+        LuaValue::String("Execute query on all buckets".to_string()),
+        LuaValue::String(String::from(PatternWithParams::new(
             format!(
                 "{} {}",
                 r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#,
diff --git a/sbroad-core/src/ir/value.rs b/sbroad-core/src/ir/value.rs
index 3349fb28f6..a1e5a625a0 100644
--- a/sbroad-core/src/ir/value.rs
+++ b/sbroad-core/src/ir/value.rs
@@ -634,15 +634,17 @@ impl Value {
         }
     }
 
-    /// Cast a value to a different type.
+    /// Cast a value to a different type and wrap into encoded value.
+    /// If the target type is the same as the current type, the value
+    /// is returned by reference. Otherwise, the value is cloned.
     ///
     /// # Errors
     /// - the value cannot be cast to the given type.
     #[allow(clippy::too_many_lines)]
-    pub fn cast(&self, column_type: &Type) -> Result<Value, SbroadError> {
+    pub fn cast(&self, column_type: &Type) -> Result<EncodedValue, SbroadError> {
         match column_type {
             Type::Array => match self {
-                Value::Null => Ok(Value::Null),
+                Value::Null => Ok(Value::Null.into()),
                 _ => Err(SbroadError::FailedTo(
                     Action::Serialize,
                     Some(Entity::Value),
@@ -650,8 +652,8 @@ impl Value {
                 )),
             },
             Type::Boolean => match self {
-                Value::Boolean(_) => Ok(self.clone()),
-                Value::Null => Ok(Value::Null),
+                Value::Boolean(_) => Ok(self.into()),
+                Value::Null => Ok(Value::Null.into()),
                 _ => Err(SbroadError::FailedTo(
                     Action::Serialize,
                     Some(Entity::Value),
@@ -659,7 +661,7 @@ impl Value {
                 )),
             },
             Type::Decimal => match self {
-                Value::Decimal(_) => Ok(self.clone()),
+                Value::Decimal(_) => Ok(self.into()),
                 Value::Double(v) => Ok(Value::Decimal(
                     Decimal::from_str(&format!("{v}")).map_err(|e| {
                         SbroadError::FailedTo(
@@ -668,10 +670,11 @@ impl Value {
                             format!("{e:?}"),
                         )
                     })?,
-                )),
-                Value::Integer(v) => Ok(Value::Decimal(Decimal::from(*v))),
-                Value::Unsigned(v) => Ok(Value::Decimal(Decimal::from(*v))),
-                Value::Null => Ok(Value::Null),
+                )
+                .into()),
+                Value::Integer(v) => Ok(Value::Decimal(Decimal::from(*v)).into()),
+                Value::Unsigned(v) => Ok(Value::Decimal(Decimal::from(*v)).into()),
+                Value::Null => Ok(Value::Null.into()),
                 _ => Err(SbroadError::FailedTo(
                     Action::Serialize,
                     Some(Entity::Value),
@@ -679,11 +682,11 @@ impl Value {
                 )),
             },
             Type::Double => match self {
-                Value::Double(_) => Ok(self.clone()),
-                Value::Decimal(v) => Ok(Value::Double(Double::from_str(&format!("{v}"))?)),
-                Value::Integer(v) => Ok(Value::Double(Double::from(*v))),
-                Value::Unsigned(v) => Ok(Value::Double(Double::from(*v))),
-                Value::Null => Ok(Value::Null),
+                Value::Double(_) => Ok(self.into()),
+                Value::Decimal(v) => Ok(Value::Double(Double::from_str(&format!("{v}"))?).into()),
+                Value::Integer(v) => Ok(Value::Double(Double::from(*v)).into()),
+                Value::Unsigned(v) => Ok(Value::Double(Double::from(*v)).into()),
+                Value::Null => Ok(Value::Null.into()),
                 _ => Err(SbroadError::FailedTo(
                     Action::Serialize,
                     Some(Entity::Value),
@@ -691,18 +694,20 @@ impl Value {
                 )),
             },
             Type::Integer => match self {
-                Value::Integer(_) => Ok(self.clone()),
+                Value::Integer(_) => Ok(self.into()),
                 Value::Decimal(v) => Ok(Value::Integer(v.to_i64().ok_or_else(|| {
                     SbroadError::FailedTo(
                         Action::Serialize,
                         Some(Entity::Value),
                         format!("{self:?} into integer"),
                     )
-                })?)),
+                })?)
+                .into()),
                 Value::Double(v) => v
                     .to_string()
                     .parse::<i64>()
                     .map(Value::Integer)
+                    .map(EncodedValue::from)
                     .map_err(|e| {
                         SbroadError::FailedTo(Action::Serialize, Some(Entity::Value), e.to_string())
                     }),
@@ -712,8 +717,9 @@ impl Value {
                         Some(Entity::Value),
                         format!("u64 {v} into i64: {e}"),
                     )
-                })?)),
-                Value::Null => Ok(Value::Null),
+                })?)
+                .into()),
+                Value::Null => Ok(Value::Null.into()),
                 _ => Err(SbroadError::FailedTo(
                     Action::Serialize,
                     Some(Entity::Value),
@@ -726,11 +732,11 @@ impl Value {
                     Some(Entity::Value),
                     format!("{self:?} into scalar"),
                 )),
-                _ => Ok(self.clone()),
+                _ => Ok(self.into()),
             },
             Type::String => match self {
-                Value::String(_) => Ok(self.clone()),
-                Value::Null => Ok(Value::Null),
+                Value::String(_) => Ok(self.into()),
+                Value::Null => Ok(Value::Null.into()),
                 _ => Err(SbroadError::FailedTo(
                     Action::Serialize,
                     Some(Entity::Value),
@@ -739,9 +745,9 @@ impl Value {
             },
             Type::Number => match self {
                 Value::Integer(_) | Value::Decimal(_) | Value::Double(_) | Value::Unsigned(_) => {
-                    Ok(self.clone())
+                    Ok(self.into())
                 }
-                Value::Null => Ok(Value::Null),
+                Value::Null => Ok(Value::Null.into()),
                 _ => Err(SbroadError::FailedTo(
                     Action::Serialize,
                     Some(Entity::Value),
@@ -749,34 +755,36 @@ impl Value {
                 )),
             },
             Type::Unsigned => match self {
-                Value::Unsigned(_) => Ok(self.clone()),
+                Value::Unsigned(_) => Ok(self.into()),
                 Value::Integer(v) => Ok(Value::Unsigned(u64::try_from(*v).map_err(|e| {
                     SbroadError::FailedTo(
                         Action::Serialize,
                         Some(Entity::Value),
                         format!("i64 {v} into u64: {e}"),
                     )
-                })?)),
+                })?)
+                .into()),
                 Value::Decimal(v) => Ok(Value::Unsigned(v.to_u64().ok_or_else(|| {
                     SbroadError::FailedTo(
                         Action::Serialize,
                         Some(Entity::Value),
                         format!("{self:?} into unsigned"),
                     )
-                })?)),
-                Value::Double(v) => {
-                    v.to_string()
-                        .parse::<u64>()
-                        .map(Value::Unsigned)
-                        .map_err(|_| {
-                            SbroadError::FailedTo(
-                                Action::Serialize,
-                                Some(Entity::Value),
-                                format!("{self:?} into unsigned"),
-                            )
-                        })
-                }
-                Value::Null => Ok(Value::Null),
+                })?)
+                .into()),
+                Value::Double(v) => v
+                    .to_string()
+                    .parse::<u64>()
+                    .map(Value::Unsigned)
+                    .map(EncodedValue::from)
+                    .map_err(|_| {
+                        SbroadError::FailedTo(
+                            Action::Serialize,
+                            Some(Entity::Value),
+                            format!("{self:?} into unsigned"),
+                        )
+                    }),
+                Value::Null => Ok(Value::Null.into()),
                 _ => Err(SbroadError::FailedTo(
                     Action::Serialize,
                     Some(Entity::Value),
@@ -807,10 +815,72 @@ impl ToHashString for Value {
     }
 }
 
+/// A helper enum to encode values into `MessagePack`.
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, Serialize)]
+#[serde(untagged)]
+pub enum EncodedValue<'v> {
+    Ref(MsgPackValue<'v>),
+    Owned(LuaValue),
+}
+
+impl<'v> From<MsgPackValue<'v>> for EncodedValue<'v> {
+    fn from(value: MsgPackValue<'v>) -> Self {
+        EncodedValue::Ref(value)
+    }
+}
+
+impl From<LuaValue> for EncodedValue<'_> {
+    fn from(value: LuaValue) -> Self {
+        EncodedValue::Owned(value)
+    }
+}
+
+impl<'v> From<&'v Value> for EncodedValue<'v> {
+    fn from(value: &'v Value) -> Self {
+        EncodedValue::from(MsgPackValue::from(value))
+    }
+}
+
+impl From<Value> for EncodedValue<'_> {
+    fn from(value: Value) -> Self {
+        EncodedValue::from(LuaValue::from(value))
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, Serialize)]
+#[serde(untagged)]
+pub enum MsgPackValue<'v> {
+    Boolean(&'v bool),
+    Decimal(&'v Decimal),
+    Double(&'v f64),
+    Integer(&'v i64),
+    Unsigned(&'v u64),
+    String(&'v String),
+    Tuple(&'v Tuple),
+    Null(()),
+}
+
+impl<'v> From<&'v Value> for MsgPackValue<'v> {
+    fn from(value: &'v Value) -> Self {
+        match value {
+            Value::Boolean(v) => MsgPackValue::Boolean(v),
+            Value::Decimal(v) => MsgPackValue::Decimal(v),
+            Value::Double(v) => MsgPackValue::Double(&v.value),
+            Value::Integer(v) => MsgPackValue::Integer(v),
+            Value::Null => MsgPackValue::Null(()),
+            Value::String(v) => MsgPackValue::String(v),
+            Value::Tuple(v) => MsgPackValue::Tuple(v),
+            Value::Unsigned(v) => MsgPackValue::Unsigned(v),
+        }
+    }
+}
+
 #[allow(clippy::module_name_repetitions)]
 #[derive(Clone, LuaRead, PartialEq, Debug, Deserialize, Serialize)]
 #[serde(untagged)]
-pub enum EncodedValue {
+pub enum LuaValue {
     Boolean(bool),
     Decimal(Decimal),
     Double(f64),
@@ -821,44 +891,44 @@ pub enum EncodedValue {
     Null(()),
 }
 
-impl fmt::Display for EncodedValue {
+impl fmt::Display for LuaValue {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
-            EncodedValue::Boolean(v) => write!(f, "{v}"),
-            EncodedValue::Decimal(v) => fmt::Display::fmt(v, f),
-            EncodedValue::Double(v) => write!(f, "{v}"),
-            EncodedValue::Integer(v) => write!(f, "{v}"),
-            EncodedValue::Unsigned(v) => write!(f, "{v}"),
-            EncodedValue::String(v) => write!(f, "'{v}'"),
-            EncodedValue::Tuple(v) => write!(f, "{v}"),
-            EncodedValue::Null(_) => write!(f, "NULL"),
+            LuaValue::Boolean(v) => write!(f, "{v}"),
+            LuaValue::Decimal(v) => fmt::Display::fmt(v, f),
+            LuaValue::Double(v) => write!(f, "{v}"),
+            LuaValue::Integer(v) => write!(f, "{v}"),
+            LuaValue::Unsigned(v) => write!(f, "{v}"),
+            LuaValue::String(v) => write!(f, "'{v}'"),
+            LuaValue::Tuple(v) => write!(f, "{v}"),
+            LuaValue::Null(_) => write!(f, "NULL"),
         }
     }
 }
 
-impl From<Value> for EncodedValue {
+impl From<Value> for LuaValue {
     fn from(value: Value) -> Self {
         match value {
-            Value::Boolean(v) => EncodedValue::Boolean(v),
-            Value::Decimal(v) => EncodedValue::Decimal(v),
-            Value::Double(v) => EncodedValue::Double(v.value),
-            Value::Integer(v) => EncodedValue::Integer(v),
-            Value::Null => EncodedValue::Null(()),
-            Value::String(v) => EncodedValue::String(v),
-            Value::Tuple(v) => EncodedValue::Tuple(v),
-            Value::Unsigned(v) => EncodedValue::Unsigned(v),
+            Value::Boolean(v) => LuaValue::Boolean(v),
+            Value::Decimal(v) => LuaValue::Decimal(v),
+            Value::Double(v) => LuaValue::Double(v.value),
+            Value::Integer(v) => LuaValue::Integer(v),
+            Value::Null => LuaValue::Null(()),
+            Value::String(v) => LuaValue::String(v),
+            Value::Tuple(v) => LuaValue::Tuple(v),
+            Value::Unsigned(v) => LuaValue::Unsigned(v),
         }
     }
 }
 
-impl From<EncodedValue> for Value {
+impl From<LuaValue> for Value {
     #[allow(clippy::cast_possible_truncation)]
-    fn from(value: EncodedValue) -> Self {
+    fn from(value: LuaValue) -> Self {
         match value {
-            EncodedValue::Unsigned(v) => Value::Unsigned(v),
-            EncodedValue::Integer(v) => Value::Integer(v),
-            EncodedValue::Decimal(v) => Value::Decimal(v),
-            EncodedValue::Double(v) => {
+            LuaValue::Unsigned(v) => Value::Unsigned(v),
+            LuaValue::Integer(v) => Value::Integer(v),
+            LuaValue::Decimal(v) => Value::Decimal(v),
+            LuaValue::Double(v) => {
                 if v.is_nan() {
                     Value::Null
                 } else if v.is_subnormal()
@@ -870,10 +940,10 @@ impl From<EncodedValue> for Value {
                     Value::Integer(v as i64)
                 }
             }
-            EncodedValue::Boolean(v) => Value::Boolean(v),
-            EncodedValue::String(v) => Value::String(v),
-            EncodedValue::Tuple(v) => Value::Tuple(v),
-            EncodedValue::Null(_) => Value::Null,
+            LuaValue::Boolean(v) => Value::Boolean(v),
+            LuaValue::String(v) => Value::String(v),
+            LuaValue::Tuple(v) => Value::Tuple(v),
+            LuaValue::Null(_) => Value::Null,
         }
     }
 }
-- 
GitLab