From 5a0f93e05538aa82d6edc5aca2e43e91dfe4e11b Mon Sep 17 00:00:00 2001
From: Denis Smirnov <sd@picodata.io>
Date: Wed, 20 Apr 2022 12:03:16 +0700
Subject: [PATCH] fix: make engine reference immutable while query execution

---
 benches/engine.rs                             |  4 ++
 benches/parse.rs                              |  4 +-
 src/executor.rs                               |  4 +-
 src/executor/bucket/tests.rs                  | 20 +++++-----
 src/executor/engine.rs                        |  3 ++
 src/executor/engine/cartridge.rs              |  7 ++--
 .../engine/cartridge/cache/lru/tests.rs       |  0
 src/executor/engine/mock.rs                   | 15 ++++---
 src/executor/tests.rs                         | 40 +++++++++----------
 src/parser.rs                                 | 29 +++++++++-----
 10 files changed, 74 insertions(+), 52 deletions(-)
 create mode 100644 src/executor/engine/cartridge/cache/lru/tests.rs

diff --git a/benches/engine.rs b/benches/engine.rs
index 96fc6f96e2..e9f38b7164 100644
--- a/benches/engine.rs
+++ b/benches/engine.rs
@@ -221,6 +221,10 @@ impl Engine for EngineMock {
         self.metadata.tables.clear();
     }
 
+    fn is_metadata_empty(&self) -> bool {
+        self.metadata.tables.is_empty()
+    }
+
     fn get_schema(&self) -> Result<Option<String>, QueryPlannerError> {
         Ok(Some("".to_string()))
     }
diff --git a/benches/parse.rs b/benches/parse.rs
index d3b863c61f..557914c503 100644
--- a/benches/parse.rs
+++ b/benches/parse.rs
@@ -227,8 +227,8 @@ fn query1() {
             ) AS "t3"
         WHERE
             "reestrid" = 452842574"#;
-    let mut engine = EngineMock::new();
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let engine = EngineMock::new();
+    let mut query = Query::new(&engine, sql).unwrap();
     let top_id = query.get_exec_plan().get_ir_plan().get_top().unwrap();
     query.bucket_discovery(top_id).unwrap();
     query.get_exec_plan().subtree_as_sql(top_id).unwrap();
diff --git a/src/executor.rs b/src/executor.rs
index b2644c0b72..0b9bb35b5d 100644
--- a/src/executor.rs
+++ b/src/executor.rs
@@ -62,7 +62,7 @@ where
     /// Execution plan
     exec_plan: ExecutionPlan,
     /// Execution engine
-    engine: &'a mut T,
+    engine: &'a T,
     /// Bucket map
     bucket_map: HashMap<usize, Buckets>,
 }
@@ -78,7 +78,7 @@ where
     /// - Failed to build AST.
     /// - Failed to build IR plan.
     /// - Failed to apply optimizing transformations to IR plan.
-    pub fn new(engine: &'a mut T, sql: &str) -> Result<Self, QueryPlannerError>
+    pub fn new(engine: &'a T, sql: &str) -> Result<Self, QueryPlannerError>
     where
         T::Metadata: Metadata,
     {
diff --git a/src/executor/bucket/tests.rs b/src/executor/bucket/tests.rs
index 8166cd2e20..53d5a1c9af 100644
--- a/src/executor/bucket/tests.rs
+++ b/src/executor/bucket/tests.rs
@@ -14,8 +14,8 @@ fn simple_union_query() {
     ) as "t3"
     WHERE "id" = 1"#;
 
-    let mut engine = EngineMock::new();
-    let mut query = Query::new(&mut engine, query).unwrap();
+    let engine = EngineMock::new();
+    let mut query = Query::new(&engine, query).unwrap();
     let plan = query.exec_plan.get_ir_plan();
     let top = plan.get_top().unwrap();
     let buckets = query.bucket_discovery(top).unwrap();
@@ -35,8 +35,8 @@ fn simple_disjunction_in_union_query() {
     ) as "t3"
     WHERE ("id" = 1) OR ("id" = 100)"#;
 
-    let mut engine = EngineMock::new();
-    let mut query = Query::new(&mut engine, query).unwrap();
+    let engine = EngineMock::new();
+    let mut query = Query::new(&engine, query).unwrap();
     let plan = query.exec_plan.get_ir_plan();
     let top = plan.get_top().unwrap();
     let buckets = query.bucket_discovery(top).unwrap();
@@ -61,8 +61,8 @@ fn complex_shard_key_union_query() {
         WHERE "sys_op" > 1) AS "t3"
     WHERE "identification_number" = 1 AND "product_code" = '222'"#;
 
-    let mut engine = EngineMock::new();
-    let mut query = Query::new(&mut engine, query).unwrap();
+    let engine = EngineMock::new();
+    let mut query = Query::new(&engine, query).unwrap();
     let plan = query.exec_plan.get_ir_plan();
     let top = plan.get_top().unwrap();
     let buckets = query.bucket_discovery(top).unwrap();
@@ -90,8 +90,8 @@ fn union_complex_cond_query() {
         AND ("product_code" = '222'
         OR "product_code" = '111')"#;
 
-    let mut engine = EngineMock::new();
-    let mut query = Query::new(&mut engine, query).unwrap();
+    let engine = EngineMock::new();
+    let mut query = Query::new(&engine, query).unwrap();
     let plan = query.exec_plan.get_ir_plan();
     let top = plan.get_top().unwrap();
     let buckets = query.bucket_discovery(top).unwrap();
@@ -123,8 +123,8 @@ fn union_query_conjunction() {
     UNION ALL
     SELECT * FROM "test_space_hist" WHERE "id" = 2"#;
 
-    let mut engine = EngineMock::new();
-    let mut query = Query::new(&mut engine, query).unwrap();
+    let engine = EngineMock::new();
+    let mut query = Query::new(&engine, query).unwrap();
     let plan = query.exec_plan.get_ir_plan();
     let top = plan.get_top().unwrap();
     let buckets = query.bucket_discovery(top).unwrap();
diff --git a/src/executor/engine.rs b/src/executor/engine.rs
index d045c7646d..69ff881970 100644
--- a/src/executor/engine.rs
+++ b/src/executor/engine.rs
@@ -57,6 +57,9 @@ pub trait Engine {
     /// Clear metadata information
     fn clear_metadata(&mut self);
 
+    /// Check if the cache is empty.
+    fn is_metadata_empty(&self) -> bool;
+
     /// Retrieve cluster schema.
     ///
     /// # Errors
diff --git a/src/executor/engine/cartridge.rs b/src/executor/engine/cartridge.rs
index b4f3cd3005..badfa234a5 100644
--- a/src/executor/engine/cartridge.rs
+++ b/src/executor/engine/cartridge.rs
@@ -40,6 +40,10 @@ impl Engine for Runtime {
         self.metadata = ClusterAppConfig::new();
     }
 
+    fn is_metadata_empty(&self) -> bool {
+        self.metadata.is_empty()
+    }
+
     fn get_schema(&self) -> Result<Option<String>, QueryPlannerError> {
         if self.metadata.is_empty() {
             let lua = tarantool::lua_state();
@@ -88,9 +92,6 @@ impl Engine for Runtime {
     }
 
     fn update_metadata(&mut self, schema: String, timeout: u64) -> Result<(), QueryPlannerError> {
-        if !&self.metadata.is_empty() {
-            return Ok(());
-        }
         self.metadata.load_schema(&schema)?;
         self.metadata.set_exec_waiting_timeout(timeout);
         Ok(())
diff --git a/src/executor/engine/cartridge/cache/lru/tests.rs b/src/executor/engine/cartridge/cache/lru/tests.rs
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/executor/engine/mock.rs b/src/executor/engine/mock.rs
index cccf7db143..7578566e08 100644
--- a/src/executor/engine/mock.rs
+++ b/src/executor/engine/mock.rs
@@ -1,3 +1,4 @@
+use std::cell::RefCell;
 use std::collections::HashMap;
 
 use crate::errors::QueryPlannerError;
@@ -144,7 +145,7 @@ impl MetadataMock {
 #[derive(Debug, Clone)]
 pub struct EngineMock {
     metadata: MetadataMock,
-    virtual_tables: HashMap<usize, VirtualTable>,
+    virtual_tables: RefCell<HashMap<usize, VirtualTable>>,
 }
 
 impl Engine for EngineMock {
@@ -161,6 +162,10 @@ impl Engine for EngineMock {
         self.metadata.tables.clear();
     }
 
+    fn is_metadata_empty(&self) -> bool {
+        self.metadata.tables.is_empty()
+    }
+
     fn get_schema(&self) -> Result<Option<String>, QueryPlannerError> {
         Ok(Some("".to_string()))
     }
@@ -180,7 +185,7 @@ impl Engine for EngineMock {
         motion_node_id: usize,
         _buckets: &Buckets,
     ) -> Result<VirtualTable, QueryPlannerError> {
-        if let Some(virtual_table) = self.virtual_tables.get(&motion_node_id) {
+        if let Some(virtual_table) = self.virtual_tables.borrow().get(&motion_node_id) {
             Ok(virtual_table.clone())
         } else {
             Err(QueryPlannerError::CustomError(
@@ -248,13 +253,13 @@ impl EngineMock {
     pub fn new() -> Self {
         EngineMock {
             metadata: MetadataMock::new(),
-            virtual_tables: HashMap::new(),
+            virtual_tables: RefCell::new(HashMap::new()),
         }
     }
 
     #[allow(dead_code)]
-    pub fn add_virtual_table(&mut self, id: usize, table: VirtualTable) {
-        self.virtual_tables.insert(id, table);
+    pub fn add_virtual_table(&self, id: usize, table: VirtualTable) {
+        self.virtual_tables.borrow_mut().insert(id, table);
     }
 }
 
diff --git a/src/executor/tests.rs b/src/executor/tests.rs
index 89cac9cedf..4bb20345de 100644
--- a/src/executor/tests.rs
+++ b/src/executor/tests.rs
@@ -11,9 +11,9 @@ use super::*;
 #[test]
 fn shard_query() {
     let sql = r#"SELECT "FIRST_NAME" FROM "test_space" where "id" = 1"#;
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
 
     let mut expected = BoxExecuteFormat::new();
     let bucket = query.engine.determine_bucket_id("1");
@@ -39,9 +39,9 @@ fn shard_union_query() {
         WHERE "sys_op" > 1) AS "t3"
     WHERE "id" = 1"#;
 
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
 
     let mut expected = BoxExecuteFormat::new();
     let bucket = query.engine.determine_bucket_id("1");
@@ -69,9 +69,9 @@ fn shard_union_query() {
 #[test]
 fn map_reduce_query() {
     let sql = r#"SELECT "product_code" FROM "hash_testing" where "identification_number" = 1 and "product_code" = '457'"#;
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
 
     let mut expected = BoxExecuteFormat::new();
     let bucket = query.engine.determine_bucket_id(&["1", "457"].join(""));
@@ -94,9 +94,9 @@ fn map_reduce_query() {
 fn linker_test() {
     let sql = r#"SELECT "FIRST_NAME" FROM "test_space" where "id" in
     (SELECT "identification_number" FROM "hash_testing" where "identification_number" > 1)"#;
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
     let motion_id = query.exec_plan.get_ir_plan().get_slices().unwrap()[0][0];
     let virtual_table = virtual_table_23();
     query.engine.add_virtual_table(motion_id, virtual_table);
@@ -144,9 +144,9 @@ fn union_linker_test() {
         ) as "t2"
         WHERE "product_code" = '123')"#;
 
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
     let motion_id = query.exec_plan.get_ir_plan().get_slices().unwrap()[0][0];
     let virtual_table = virtual_table_23();
     query.engine.add_virtual_table(motion_id, virtual_table);
@@ -223,9 +223,9 @@ INNER JOIN
     ON "t3"."id" = "t8"."identification_number"
 WHERE "t3"."id" = 2 AND "t8"."identification_number" = 2"#;
 
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
     let motion_id = query.exec_plan.get_ir_plan().get_slices().unwrap()[0][0];
     let mut virtual_table = virtual_table_23();
     virtual_table.set_alias("\"t8\"").unwrap();
@@ -273,9 +273,9 @@ fn join_linker2_test() {
         select "id" as "id1", "id" as "id2" from "test_space_hist"
     ) as "t2" on "t1"."id" = 1"#;
 
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
     let motion_id = query.exec_plan.get_ir_plan().get_slices().unwrap()[0][0];
 
     let mut virtual_table = VirtualTable::new();
@@ -325,9 +325,9 @@ fn join_linker3_test() {
     (SELECT "id" as "id1", "FIRST_NAME" FROM "test_space") AS "t2"
     ON "t2"."id1" = 1"#;
 
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
     let motion_id = query.exec_plan.get_ir_plan().get_slices().unwrap()[0][0];
 
     let mut virtual_table = VirtualTable::new();
@@ -377,9 +377,9 @@ fn join_linker4_test() {
     on t1."id" = t2."r_id" and
     t1."FIRST_NAME" = (SELECT "FIRST_NAME" as "fn" FROM "test_space" WHERE "id" = 1)"#;
 
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
 
     let motion_t2_id = query.exec_plan.get_ir_plan().get_slices().unwrap()[0][0];
     let mut virtual_t2 = VirtualTable::new();
@@ -442,9 +442,9 @@ fn anonymous_col_index_test() {
     WHERE "id" in (SELECT "identification_number" FROM "hash_testing" WHERE "product_units" < 3)
         OR "id" in (SELECT "identification_number" FROM "hash_testing" WHERE "product_units" > 5)"#;
 
-    let mut engine = EngineMock::new();
+    let engine = EngineMock::new();
 
-    let mut query = Query::new(&mut engine, sql).unwrap();
+    let mut query = Query::new(&engine, sql).unwrap();
     let motion1_id = query.exec_plan.get_ir_plan().get_slices().unwrap()[0][0];
     query
         .engine
diff --git a/src/parser.rs b/src/parser.rs
index 1b84a86229..944599e716 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -72,7 +72,7 @@ pub extern "C" fn calculate_bucket_id_by_dict(ctx: FunctionCtx, args: FunctionAr
         return ret_code;
     }
     QUERY_ENGINE.with(|e| {
-        let engine = &mut *e.borrow_mut();
+        let engine = &*e.borrow();
 
         // Closure for more concise error propagation from calls nested in the bucket calculation
         let propagate_err = || -> Result<u64, QueryPlannerError> {
@@ -117,7 +117,7 @@ pub extern "C" fn execute_query(ctx: FunctionCtx, args: FunctionArgs) -> c_int {
         return ret_code;
     }
     QUERY_ENGINE.with(|e| {
-        let engine = &mut *e.borrow_mut();
+        let engine = &*e.borrow();
         let mut query = match Query::new(engine, args.query.as_str()) {
             Ok(q) => q,
             Err(e) => {
@@ -162,16 +162,25 @@ fn load_metadata() -> c_int {
         }
     });
 
+    let mut is_metadata_empty = false;
+    QUERY_ENGINE.with(|e| {
+        let engine = &*e.borrow();
+        if engine.is_metadata_empty() {
+            is_metadata_empty = true;
+        }
+    });
     // Tarantool never yields here, so it is possible to hold
     // a mutable reference to the engine.
-    if let (Some(schema), Some(timeout)) = (schema, timeout) {
-        QUERY_ENGINE.with(|e| {
-            let engine = &mut *e.borrow_mut();
-            if let Err(e) = engine.update_metadata(schema, timeout) {
-                return tarantool::set_error!(TarantoolErrorCode::ProcC, "{}", e.to_string());
-            }
-            0
-        });
+    if is_metadata_empty {
+        if let (Some(schema), Some(timeout)) = (schema, timeout) {
+            QUERY_ENGINE.with(|e| {
+                let engine = &mut *e.borrow_mut();
+                if let Err(e) = engine.update_metadata(schema, timeout) {
+                    return tarantool::set_error!(TarantoolErrorCode::ProcC, "{}", e.to_string());
+                }
+                0
+            });
+        }
     }
 
     0
-- 
GitLab