From 54eb688b6bfb2c9c2a8c9a44cfa9438db616b2e7 Mon Sep 17 00:00:00 2001
From: "ms.evilhat" <ms.evilhat@gmail.com>
Date: Mon, 9 Jan 2023 14:06:52 +0300
Subject: [PATCH] refactor(errors): refactor errors module to have a clear
 classification instead of custom error

---
 sbroad-benches/src/engine.rs                  |  47 +-
 .../src/api/calculate_bucket_id.rs            |  19 +-
 sbroad-cartridge/src/api/exec_query.rs        |  58 ++-
 .../src/api/exec_query/protocol.rs            |  67 +--
 sbroad-cartridge/src/cartridge.rs             |  13 +-
 sbroad-cartridge/src/cartridge/config.rs      |  69 +--
 .../src/cartridge/config/tests.rs             |   4 +-
 sbroad-cartridge/src/cartridge/router.rs      | 189 ++++----
 sbroad-cartridge/src/cartridge/storage.rs     |  90 ++--
 .../test_app/test/data/config.yml             |   1 +
 .../test_app/test/integration/api_test.lua    |  90 +++-
 .../test/integration/validate_schema_test.lua |   2 +-
 sbroad-core/src/backend/sql/ir.rs             |  77 +--
 sbroad-core/src/backend/sql/tree.rs           | 183 +++++---
 sbroad-core/src/errors.rs                     | 344 +++++++++-----
 sbroad-core/src/executor.rs                   |  30 +-
 sbroad-core/src/executor/bucket.rs            | 146 +++---
 sbroad-core/src/executor/engine.rs            |  88 ++--
 sbroad-core/src/executor/engine/mock.rs       |  53 +--
 sbroad-core/src/executor/ir.rs                | 189 ++++----
 sbroad-core/src/executor/lru.rs               |  65 ++-
 sbroad-core/src/executor/lru/tests.rs         |  10 +-
 sbroad-core/src/executor/result.rs            |  14 +-
 sbroad-core/src/executor/shard.rs             |  14 +-
 sbroad-core/src/executor/tests.rs             |   9 +-
 sbroad-core/src/executor/tests/frontend.rs    |  16 +-
 sbroad-core/src/executor/vtable.rs            |  24 +-
 sbroad-core/src/frontend.rs                   |   6 +-
 sbroad-core/src/frontend/sql.rs               | 369 ++++++++-------
 sbroad-core/src/frontend/sql/ast.rs           | 439 +++++++++++-------
 sbroad-core/src/frontend/sql/ast/tests.rs     |  12 +-
 sbroad-core/src/frontend/sql/ir.rs            | 118 ++---
 sbroad-core/src/ir.rs                         | 209 +++++----
 sbroad-core/src/ir/api/constant.rs            |  31 +-
 sbroad-core/src/ir/api/parameter.rs           |  15 +-
 sbroad-core/src/ir/distribution.rs            |  96 ++--
 sbroad-core/src/ir/explain.rs                 | 104 +++--
 sbroad-core/src/ir/expression.rs              | 262 ++++++-----
 sbroad-core/src/ir/expression/cast.rs         |  14 +-
 sbroad-core/src/ir/expression/concat.rs       |   8 +-
 sbroad-core/src/ir/expression/tests.rs        |   6 +-
 sbroad-core/src/ir/function.rs                |  12 +-
 sbroad-core/src/ir/operator.rs                | 265 +++++------
 sbroad-core/src/ir/operator/tests.rs          | 138 +++++-
 sbroad-core/src/ir/relation.rs                |  61 +--
 sbroad-core/src/ir/relation/tests.rs          |  62 ++-
 sbroad-core/src/ir/tests.rs                   |   6 +-
 sbroad-core/src/ir/transformation.rs          |  58 ++-
 sbroad-core/src/ir/transformation/bool_in.rs  |  18 +-
 sbroad-core/src/ir/transformation/dnf.rs      |  30 +-
 .../ir/transformation/equality_propagation.rs |  40 +-
 .../src/ir/transformation/merge_tuples.rs     |  70 +--
 .../src/ir/transformation/redistribution.rs   | 200 ++++----
 .../ir/transformation/redistribution/tests.rs |   7 +-
 .../src/ir/transformation/split_columns.rs    |  25 +-
 .../ir/transformation/split_columns/tests.rs  |   5 +-
 sbroad-core/src/ir/value/double.rs            |  10 +-
 sbroad-core/src/otm/statistics.rs             |   4 +-
 sbroad-core/src/otm/statistics/eviction.rs    |   8 +-
 59 files changed, 2685 insertions(+), 1934 deletions(-)

diff --git a/sbroad-benches/src/engine.rs b/sbroad-benches/src/engine.rs
index e87a52b35e..f768da0a7e 100644
--- a/sbroad-benches/src/engine.rs
+++ b/sbroad-benches/src/engine.rs
@@ -3,7 +3,7 @@ use std::cell::RefCell;
 use std::collections::HashMap;
 
 use sbroad::backend::sql::tree::{OrderedSyntaxNodes, SyntaxPlan};
-use sbroad::errors::QueryPlannerError;
+use sbroad::errors::{Entity, SbroadError};
 use sbroad::executor::bucket::Buckets;
 use sbroad::executor::engine::{
     normalize_name_from_sql, sharding_keys_from_map, sharding_keys_from_tuple, Configuration,
@@ -38,25 +38,22 @@ impl CoordinatorMetadata for RouterConfigurationMock {
     ///
     /// # Errors
     /// - Failed to get table by name from the metadata.
-    fn get_table_segment(&self, table_name: &str) -> Result<Table, QueryPlannerError> {
+    fn get_table_segment(&self, table_name: &str) -> Result<Table, SbroadError> {
         let name = normalize_name_from_sql(table_name);
         match self.tables.get(&name) {
             Some(v) => Ok(v.clone()),
-            None => Err(QueryPlannerError::CustomError(format!(
-                "Space {} not found",
-                table_name
-            ))),
+            None => Err(SbroadError::NotFound(
+                Entity::Space,
+                String::from(table_name),
+            )),
         }
     }
 
-    fn get_function(&self, fn_name: &str) -> Result<&Function, QueryPlannerError> {
+    fn get_function(&self, fn_name: &str) -> Result<&Function, SbroadError> {
         let name = normalize_name_from_sql(fn_name);
         match self.functions.get(&name) {
             Some(v) => Ok(v),
-            None => Err(QueryPlannerError::CustomError(format!(
-                "Function {} not found",
-                name
-            ))),
+            None => Err(SbroadError::NotFound(Entity::SQLFunction, name)),
         }
     }
 
@@ -68,20 +65,17 @@ impl CoordinatorMetadata for RouterConfigurationMock {
         self.sharding_column.as_str()
     }
 
-    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<String>, QueryPlannerError> {
+    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<String>, SbroadError> {
         let table = self.get_table_segment(space)?;
         table.get_sharding_column_names()
     }
 
-    fn get_sharding_positions_by_space(
-        &self,
-        space: &str,
-    ) -> Result<Vec<usize>, QueryPlannerError> {
+    fn get_sharding_positions_by_space(&self, space: &str) -> Result<Vec<usize>, SbroadError> {
         let table = self.get_table_segment(space)?;
         Ok(table.get_sharding_positions().to_vec())
     }
 
-    fn get_fields_amount_by_space(&self, space: &str) -> Result<usize, QueryPlannerError> {
+    fn get_fields_amount_by_space(&self, space: &str) -> Result<usize, SbroadError> {
         let table = self.get_table_segment(space)?;
         Ok(table.columns.len())
     }
@@ -387,7 +381,7 @@ impl Configuration for RouterRuntimeMock {
         self.metadata.tables.is_empty()
     }
 
-    fn get_config(&self) -> Result<Option<Self::Configuration>, QueryPlannerError> {
+    fn get_config(&self) -> Result<Option<Self::Configuration>, SbroadError> {
         let config = RouterConfigurationMock::new();
         Ok(Some(config))
     }
@@ -401,7 +395,7 @@ impl Coordinator for RouterRuntimeMock {
     type ParseTree = AbstractSyntaxTree;
     type Cache = LRUCache<String, Plan>;
 
-    fn clear_ir_cache(&self) -> Result<(), QueryPlannerError> {
+    fn clear_ir_cache(&self) -> Result<(), SbroadError> {
         *self.ir_cache.borrow_mut() = Self::Cache::new(DEFAULT_CAPACITY, None)?;
         Ok(())
     }
@@ -415,12 +409,13 @@ impl Coordinator for RouterRuntimeMock {
         _plan: &mut ExecutionPlan,
         motion_node_id: usize,
         _buckets: &Buckets,
-    ) -> Result<VirtualTable, QueryPlannerError> {
+    ) -> Result<VirtualTable, SbroadError> {
         if let Some(virtual_table) = self.virtual_tables.get(&motion_node_id) {
             Ok(virtual_table.clone())
         } else {
-            Err(QueryPlannerError::CustomError(
-                "No virtual table found for motion node".to_string(),
+            Err(SbroadError::NotFound(
+                Entity::VirtualTable,
+                format!("for motion node {}", motion_node_id),
             ))
         }
     }
@@ -430,7 +425,7 @@ impl Coordinator for RouterRuntimeMock {
         plan: &mut ExecutionPlan,
         top_id: usize,
         buckets: &Buckets,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         let result = ProducerResult::new();
         let sp = SyntaxPlan::new(plan, top_id, Snapshot::Oldest)?;
         let ordered = OrderedSyntaxNodes::try_from(sp)?;
@@ -440,7 +435,7 @@ impl Coordinator for RouterRuntimeMock {
         Ok(Box::new(result))
     }
 
-    fn explain_format(&self, explain: String) -> Result<Box<dyn Any>, QueryPlannerError> {
+    fn explain_format(&self, explain: String) -> Result<Box<dyn Any>, SbroadError> {
         Ok(Box::new(explain))
     }
 
@@ -448,7 +443,7 @@ impl Coordinator for RouterRuntimeMock {
         &'engine self,
         space: String,
         args: &'rec HashMap<String, Value>,
-    ) -> Result<Vec<&'rec Value>, QueryPlannerError> {
+    ) -> Result<Vec<&'rec Value>, SbroadError> {
         sharding_keys_from_map(self.cached_config(), &space, args)
     }
 
@@ -456,7 +451,7 @@ impl Coordinator for RouterRuntimeMock {
         &'engine self,
         space: String,
         rec: &'rec [Value],
-    ) -> Result<Vec<&'rec Value>, QueryPlannerError> {
+    ) -> Result<Vec<&'rec Value>, SbroadError> {
         sharding_keys_from_tuple(self.cached_config(), &space, rec)
     }
 
diff --git a/sbroad-cartridge/src/api/calculate_bucket_id.rs b/sbroad-cartridge/src/api/calculate_bucket_id.rs
index 3e7e11f02a..7103c62403 100644
--- a/sbroad-cartridge/src/api/calculate_bucket_id.rs
+++ b/sbroad-cartridge/src/api/calculate_bucket_id.rs
@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 use std::os::raw::c_int;
 
-use sbroad::errors::QueryPlannerError;
+use sbroad::errors::{Entity, SbroadError};
 use tarantool::tuple::{FunctionArgs, FunctionCtx, Tuple};
 
 use serde::{de::Deserializer, Deserialize, Serialize};
@@ -88,7 +88,7 @@ enum Args {
 }
 
 impl TryFrom<FunctionArgs> for Args {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: FunctionArgs) -> Result<Self, Self::Error> {
         if let Ok(args) = Tuple::from(&value).decode::<ArgsString>() {
@@ -101,11 +101,14 @@ impl TryFrom<FunctionArgs> for Args {
             return Ok(Self::Map(args));
         }
 
-        Err(QueryPlannerError::CustomError(format!(
-            "Parsing args {:?} error, \
-            expected string, tuple with a space name, or map with a space name as an argument",
-            &value
-        )))
+        Err(SbroadError::ParsingError(
+            Entity::Args,
+            format!(
+                "expected string, tuple with a space name, or map with a space name as an argument, \
+                got args {:?}",
+                &value
+            ),
+        ))
     }
 }
 
@@ -160,7 +163,7 @@ pub extern "C" fn calculate_bucket_id(ctx: FunctionCtx, args: FunctionArgs) -> c
                 ctx.return_mp(&bucket_id).unwrap();
                 0
             }
-            Err(e) => tarantool_error(&format!("{e:?}")),
+            Err(e) => tarantool_error(&format!("{e}")),
         }
     })
 }
diff --git a/sbroad-cartridge/src/api/exec_query.rs b/sbroad-cartridge/src/api/exec_query.rs
index c8f8950c52..bf4ced65ed 100644
--- a/sbroad-cartridge/src/api/exec_query.rs
+++ b/sbroad-cartridge/src/api/exec_query.rs
@@ -6,7 +6,7 @@ use crate::api::helper::load_config;
 use crate::api::{COORDINATOR_ENGINE, SEGMENT_ENGINE};
 use protocol::RequiredData;
 use sbroad::backend::sql::ir::PatternWithParams;
-use sbroad::errors::QueryPlannerError;
+use sbroad::errors::{Action, Entity, SbroadError};
 use sbroad::executor::Query;
 use sbroad::log::tarantool_error;
 use sbroad::otm::{child_span, query_span};
@@ -88,34 +88,54 @@ pub extern "C" fn dispatch_query(f_ctx: FunctionCtx, args: FunctionArgs) -> c_in
     )
 }
 
-fn decode_msgpack(args: FunctionArgs) -> Result<(Vec<u8>, Vec<u8>), QueryPlannerError> {
+fn decode_msgpack(args: FunctionArgs) -> Result<(Vec<u8>, Vec<u8>), SbroadError> {
     debug!(Option::from("decode_msgpack"), &format!("args: {args:?}"));
     let tuple_buf: Vec<u8> = TupleBuffer::from(Tuple::from(args)).into();
     let mut stream = rmp::decode::Bytes::from(tuple_buf.as_slice());
-    let array_len = rmp::decode::read_array_len(&mut stream)
-        .map_err(|e| QueryPlannerError::CustomError(format!("array length: {e:?}")))?
-        as usize;
+    let array_len = rmp::decode::read_array_len(&mut stream).map_err(|e| {
+        SbroadError::FailedTo(
+            Action::Decode,
+            Some(Entity::MsgPack),
+            format!("array length: {e:?}"),
+        )
+    })? as usize;
     if array_len != 2 {
-        return Err(QueryPlannerError::CustomError(format!(
-            "Expected tuple of 2 elements, got {}",
-            array_len
-        )));
+        return Err(SbroadError::Invalid(
+            Entity::Tuple,
+            Some(format!("expected tuple of 2 elements, got {array_len}")),
+        ));
     }
-    let req_len = rmp::decode::read_str_len(&mut stream)
-        .map_err(|e| QueryPlannerError::CustomError(format!("read required data length: {e:?}")))?
-        as usize;
+    let req_len = rmp::decode::read_str_len(&mut stream).map_err(|e| {
+        SbroadError::FailedTo(
+            Action::Decode,
+            Some(Entity::MsgPack),
+            format!("read required data length: {e:?}"),
+        )
+    })? as usize;
     let mut required: Vec<u8> = vec![0_u8; req_len];
-    stream
-        .read_exact_buf(&mut required)
-        .map_err(|e| QueryPlannerError::CustomError(format!("read required data: {e:?}")))?;
+    stream.read_exact_buf(&mut required).map_err(|e| {
+        SbroadError::FailedTo(
+            Action::Decode,
+            Some(Entity::MsgPack),
+            format!("read required data: {e:?}"),
+        )
+    })?;
 
     let opt_len = rmp::decode::read_str_len(&mut stream).map_err(|e| {
-        QueryPlannerError::CustomError(format!("read optional data string length: {e:?}"))
+        SbroadError::FailedTo(
+            Action::Decode,
+            Some(Entity::MsgPack),
+            format!("read optional data string length: {e:?}"),
+        )
     })? as usize;
     let mut optional: Vec<u8> = vec![0_u8; opt_len];
-    stream
-        .read_exact_buf(&mut optional)
-        .map_err(|e| QueryPlannerError::CustomError(format!("read optional data: {e:?}")))?;
+    stream.read_exact_buf(&mut optional).map_err(|e| {
+        SbroadError::FailedTo(
+            Action::Decode,
+            Some(Entity::MsgPack),
+            format!("read optional data: {e:?}"),
+        )
+    })?;
 
     Ok((required, optional))
 }
diff --git a/sbroad-cartridge/src/api/exec_query/protocol.rs b/sbroad-cartridge/src/api/exec_query/protocol.rs
index 1879e3bef6..c63ee88168 100644
--- a/sbroad-cartridge/src/api/exec_query/protocol.rs
+++ b/sbroad-cartridge/src/api/exec_query/protocol.rs
@@ -6,7 +6,7 @@ use tarantool::tlua::{self, AsLua, PushGuard, PushInto, PushOneInto, Void};
 
 use sbroad::backend::sql::tree::OrderedSyntaxNodes;
 use sbroad::debug;
-use sbroad::errors::QueryPlannerError;
+use sbroad::errors::{Action, Entity, SbroadError};
 use sbroad::executor::ir::{ExecutionPlan, QueryType};
 use sbroad::ir::value::Value;
 use sbroad::otm::{
@@ -77,24 +77,29 @@ impl Default for RequiredData {
 }
 
 impl TryFrom<RequiredData> for Vec<u8> {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: RequiredData) -> Result<Self, Self::Error> {
         bincode::serialize(&value).map_err(|e| {
-            QueryPlannerError::CustomError(format!(
-                "Failed to serialize required data to binary: {:?}",
-                e
-            ))
+            SbroadError::FailedTo(
+                Action::Serialize,
+                Some(Entity::RequiredData),
+                format!("to binary: {e:?}"),
+            )
         })
     }
 }
 
 impl TryFrom<&[u8]> for RequiredData {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
         bincode::deserialize(value).map_err(|e| {
-            QueryPlannerError::CustomError(format!("Failed to deserialize required data: {e:?}"))
+            SbroadError::FailedTo(
+                Action::Deserialize,
+                Some(Entity::RequiredData),
+                format!("{e:?}"),
+            )
         })
     }
 }
@@ -167,7 +172,7 @@ impl From<EncodedRequiredData> for Vec<u8> {
 }
 
 impl TryFrom<RequiredData> for EncodedRequiredData {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: RequiredData) -> Result<Self, Self::Error> {
         let bytes: Vec<u8> = value.try_into()?;
@@ -176,7 +181,7 @@ impl TryFrom<RequiredData> for EncodedRequiredData {
 }
 
 impl TryFrom<EncodedRequiredData> for RequiredData {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: EncodedRequiredData) -> Result<Self, Self::Error> {
         let ir: RequiredData = value.0.as_slice().try_into()?;
@@ -191,24 +196,29 @@ pub struct OptionalData {
 }
 
 impl TryFrom<OptionalData> for Vec<u8> {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: OptionalData) -> Result<Self, Self::Error> {
         bincode::serialize(&value).map_err(|e| {
-            QueryPlannerError::CustomError(format!(
-                "Failed to serialize required data to binary: {:?}",
-                e
-            ))
+            SbroadError::FailedTo(
+                Action::Serialize,
+                Some(Entity::RequiredData),
+                format!("to binary: {e:?}"),
+            )
         })
     }
 }
 
 impl TryFrom<&[u8]> for OptionalData {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
         bincode::deserialize(value).map_err(|e| {
-            QueryPlannerError::CustomError(format!("Failed to deserialize required data: {e:?}"))
+            SbroadError::FailedTo(
+                Action::Deserialize,
+                Some(Entity::RequiredData),
+                format!("{e:?}"),
+            )
         })
     }
 }
@@ -218,18 +228,23 @@ impl OptionalData {
         OptionalData { exec_plan, ordered }
     }
 
-    pub fn to_bytes(&self) -> Result<Vec<u8>, QueryPlannerError> {
+    pub fn to_bytes(&self) -> Result<Vec<u8>, SbroadError> {
         bincode::serialize(self).map_err(|e| {
-            QueryPlannerError::CustomError(format!(
-                "Failed to serialize required data to binary: {:?}",
-                e
-            ))
+            SbroadError::FailedTo(
+                Action::Serialize,
+                Some(Entity::RequiredData),
+                format!("to binary: {e:?}"),
+            )
         })
     }
 
-    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, QueryPlannerError> {
+    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, SbroadError> {
         bincode::deserialize(bytes).map_err(|e| {
-            QueryPlannerError::CustomError(format!("Failed to deserialize required data: {e:?}"))
+            SbroadError::FailedTo(
+                Action::Deserialize,
+                Some(Entity::RequiredData),
+                format!("{e:?}"),
+            )
         })
     }
 }
@@ -249,7 +264,7 @@ impl From<EncodedOptionalData> for Vec<u8> {
 }
 
 impl TryFrom<OptionalData> for EncodedOptionalData {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: OptionalData) -> Result<Self, Self::Error> {
         let bytes: Vec<u8> = value.try_into()?;
@@ -258,7 +273,7 @@ impl TryFrom<OptionalData> for EncodedOptionalData {
 }
 
 impl TryFrom<EncodedOptionalData> for OptionalData {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: EncodedOptionalData) -> Result<Self, Self::Error> {
         let ir: OptionalData = value.0.as_slice().try_into()?;
diff --git a/sbroad-cartridge/src/cartridge.rs b/sbroad-cartridge/src/cartridge.rs
index d848ff4c6f..f96852b47d 100644
--- a/sbroad-cartridge/src/cartridge.rs
+++ b/sbroad-cartridge/src/cartridge.rs
@@ -6,7 +6,7 @@ pub mod storage;
 
 use opentelemetry::global::{set_text_map_propagator, set_tracer_provider};
 use opentelemetry::sdk::propagation::{TextMapCompositePropagator, TraceContextPropagator};
-use sbroad::errors::QueryPlannerError;
+use sbroad::errors::{Action, SbroadError};
 use sbroad::otm::update_global_tracer;
 
 static SERVICE_NAME: &str = "sbroad";
@@ -15,7 +15,7 @@ static SERVICE_NAME: &str = "sbroad";
 ///
 /// # Errors
 /// - failed to build OTM global trace provider
-pub fn update_tracing(host: &str, port: u16) -> Result<(), QueryPlannerError> {
+pub fn update_tracing(host: &str, port: u16) -> Result<(), SbroadError> {
     let propagator = TextMapCompositePropagator::new(vec![Box::new(TraceContextPropagator::new())]);
     set_text_map_propagator(propagator);
     let provider = opentelemetry_jaeger::new_pipeline()
@@ -23,10 +23,11 @@ pub fn update_tracing(host: &str, port: u16) -> Result<(), QueryPlannerError> {
         .with_service_name(SERVICE_NAME)
         .build_simple()
         .map_err(|e| {
-            QueryPlannerError::CustomError(format!(
-                "Failed to build OTM global trace provider: {}",
-                e
-            ))
+            SbroadError::FailedTo(
+                Action::Build,
+                None,
+                format!("OTM global trace provider: {e}"),
+            )
         })?;
     set_tracer_provider(provider);
     update_global_tracer();
diff --git a/sbroad-cartridge/src/cartridge/config.rs b/sbroad-cartridge/src/cartridge/config.rs
index 994eb087b3..b0417828b2 100644
--- a/sbroad-cartridge/src/cartridge/config.rs
+++ b/sbroad-cartridge/src/cartridge/config.rs
@@ -5,7 +5,7 @@ extern crate yaml_rust;
 use std::collections::HashMap;
 use yaml_rust::{Yaml, YamlLoader};
 
-use sbroad::errors::QueryPlannerError;
+use sbroad::errors::{Entity, SbroadError};
 use sbroad::executor::engine::CoordinatorMetadata;
 use sbroad::executor::engine::{normalize_name_from_schema, normalize_name_from_sql};
 use sbroad::executor::lru::DEFAULT_CAPACITY;
@@ -64,8 +64,8 @@ impl RouterConfiguration {
     /// Parse and load yaml cartridge schema to cache
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when process was terminated.
-    pub fn load_schema(&mut self, s: &str) -> Result<(), QueryPlannerError> {
+    /// Returns `SbroadError` when process was terminated.
+    pub fn load_schema(&mut self, s: &str) -> Result<(), SbroadError> {
         self.init_core_functions();
         if let Ok(docs) = YamlLoader::load_from_str(s) {
             if let Some(schema) = docs.get(0) {
@@ -74,7 +74,7 @@ impl RouterConfiguration {
             }
         }
 
-        Err(QueryPlannerError::InvalidClusterSchema)
+        Err(SbroadError::Invalid(Entity::ClusterSchema, None))
     }
 
     pub(crate) fn is_empty(&self) -> bool {
@@ -93,13 +93,18 @@ impl RouterConfiguration {
     /// Transform space information from schema to table segments
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when schema contains errors.
+    /// Returns `SbroadError` when schema contains errors.
     #[allow(clippy::too_many_lines)]
-    fn init_table_segments(&mut self, schema: &Yaml) -> Result<(), QueryPlannerError> {
+    fn init_table_segments(&mut self, schema: &Yaml) -> Result<(), SbroadError> {
         self.tables.clear();
         let spaces = match schema["spaces"].as_hash() {
             Some(v) => v,
-            None => return Err(QueryPlannerError::InvalidSchemaSpaces),
+            None => {
+                return Err(SbroadError::Invalid(
+                    Entity::ClusterSchema,
+                    Some("schema.spaces is invalid".into()),
+                ))
+            }
         };
 
         for (space_name, params) in spaces.iter() {
@@ -109,15 +114,25 @@ impl RouterConfiguration {
                     for val in fields {
                         let name: &str = match val["name"].as_str() {
                             Some(s) => s,
-                            None => return Err(QueryPlannerError::InvalidColumnName),
+                            None => {
+                                return Err(SbroadError::Invalid(
+                                    Entity::ClusterSchema,
+                                    Some(format!(
+                                        "column name of table {current_space_name} is invalid"
+                                    )),
+                                ))
+                            }
                         };
                         let t = match val["type"].as_str() {
                             Some(t) => Type::new(t)?,
                             None => {
-                                return Err(QueryPlannerError::CustomError(format!(
-                                    "Type not found for columns {}",
-                                    name
-                                )))
+                                return Err(SbroadError::Invalid(
+                                    Entity::ClusterSchema,
+                                    Some(format!(
+                                        "Type not found for columns {} of table {}",
+                                        name, current_space_name
+                                    )),
+                                ))
                             }
                         };
                         let qualified_name = normalize_name_from_schema(name);
@@ -183,7 +198,10 @@ impl RouterConfiguration {
                 let t = Table::new_seg(&table_name, fields, keys_str.as_slice())?;
                 self.tables.insert(table_name, t);
             } else {
-                return Err(QueryPlannerError::InvalidSpaceName);
+                return Err(SbroadError::Invalid(
+                    Entity::ClusterSchema,
+                    Some("space name is invalid".into()),
+                ));
             }
         }
 
@@ -232,27 +250,21 @@ impl CoordinatorMetadata for RouterConfiguration {
     /// Get table segment form cache by table name
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when table was not found.
+    /// Returns `SbroadError` when table was not found.
     #[allow(dead_code)]
-    fn get_table_segment(&self, table_name: &str) -> Result<Table, QueryPlannerError> {
+    fn get_table_segment(&self, table_name: &str) -> Result<Table, SbroadError> {
         let name = normalize_name_from_sql(table_name);
         match self.tables.get(&name) {
             Some(v) => Ok(v.clone()),
-            None => Err(QueryPlannerError::CustomError(format!(
-                "Space {} not found",
-                name
-            ))),
+            None => Err(SbroadError::NotFound(Entity::Space, name)),
         }
     }
 
-    fn get_function(&self, fn_name: &str) -> Result<&Function, QueryPlannerError> {
+    fn get_function(&self, fn_name: &str) -> Result<&Function, SbroadError> {
         let name = normalize_name_from_sql(fn_name);
         match self.functions.get(&name) {
             Some(v) => Ok(v),
-            None => Err(QueryPlannerError::CustomError(format!(
-                "Function {} not found",
-                name
-            ))),
+            None => Err(SbroadError::NotFound(Entity::SQLFunction, name)),
         }
     }
 
@@ -266,20 +278,17 @@ impl CoordinatorMetadata for RouterConfiguration {
     }
 
     /// Get sharding key's column names by a space name
-    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<String>, QueryPlannerError> {
+    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<String>, SbroadError> {
         let table = self.get_table_segment(space)?;
         table.get_sharding_column_names()
     }
 
-    fn get_sharding_positions_by_space(
-        &self,
-        space: &str,
-    ) -> Result<Vec<usize>, QueryPlannerError> {
+    fn get_sharding_positions_by_space(&self, space: &str) -> Result<Vec<usize>, SbroadError> {
         let table = self.get_table_segment(space)?;
         Ok(table.get_sharding_positions().to_vec())
     }
 
-    fn get_fields_amount_by_space(&self, space: &str) -> Result<usize, QueryPlannerError> {
+    fn get_fields_amount_by_space(&self, space: &str) -> Result<usize, SbroadError> {
         let table = self.get_table_segment(space)?;
         Ok(table.columns.len())
     }
diff --git a/sbroad-cartridge/src/cartridge/config/tests.rs b/sbroad-cartridge/src/cartridge/config/tests.rs
index 455fbc968f..589cef3b87 100644
--- a/sbroad-cartridge/src/cartridge/config/tests.rs
+++ b/sbroad-cartridge/src/cartridge/config/tests.rs
@@ -156,7 +156,7 @@ fn test_getting_table_segment() {
 
     assert_eq!(
         s.get_table_segment("invalid_table").unwrap_err(),
-        QueryPlannerError::CustomError(r#"Space "INVALID_TABLE" not found"#.into())
+        SbroadError::NotFound(Entity::Space, r#""INVALID_TABLE""#.into())
     );
     assert_eq!(s.get_table_segment("\"hash_testing\"").unwrap(), expected);
 }
@@ -241,6 +241,6 @@ fn test_invalid_schema() {
 
     assert_eq!(
         s.load_schema(test_schema).unwrap_err(),
-        QueryPlannerError::CustomError("type `map` not implemented".into())
+        SbroadError::NotImplemented(Entity::Type, "map".into())
     );
 }
diff --git a/sbroad-cartridge/src/cartridge/router.rs b/sbroad-cartridge/src/cartridge/router.rs
index a68dd1f8ce..06484b6067 100644
--- a/sbroad-cartridge/src/cartridge/router.rs
+++ b/sbroad-cartridge/src/cartridge/router.rs
@@ -18,7 +18,7 @@ use crate::cartridge::config::RouterConfiguration;
 use crate::cartridge::update_tracing;
 
 use sbroad::backend::sql::tree::{OrderedSyntaxNodes, SyntaxPlan};
-use sbroad::errors::QueryPlannerError;
+use sbroad::errors::{Action, Entity, SbroadError};
 use sbroad::executor::bucket::Buckets;
 use sbroad::executor::engine::{
     normalize_name_from_schema, sharding_keys_from_map, sharding_keys_from_tuple, Configuration,
@@ -65,7 +65,7 @@ impl Configuration for RouterRuntime {
     }
 
     #[allow(clippy::too_many_lines)]
-    fn get_config(&self) -> Result<Option<Self::Configuration>, QueryPlannerError> {
+    fn get_config(&self) -> Result<Option<Self::Configuration>, SbroadError> {
         if self.is_config_empty() {
             let lua = tarantool::lua_state();
 
@@ -74,7 +74,7 @@ impl Configuration for RouterRuntime {
                 Ok(res) => res,
                 Err(e) => {
                     error!(Option::from("getting schema"), &format!("{e:?}"));
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
 
@@ -84,7 +84,7 @@ impl Configuration for RouterRuntime {
                 Ok(res) => res,
                 Err(e) => {
                     error!(Option::from("getting jaeger agent host"), &format!("{e:?}"),);
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
 
@@ -94,7 +94,7 @@ impl Configuration for RouterRuntime {
                 Ok(res) => res,
                 Err(e) => {
                     error!(Option::from("getting jaeger agent port"), &format!("{e:?}"),);
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
 
@@ -103,7 +103,7 @@ impl Configuration for RouterRuntime {
                 Ok(res) => res,
                 Err(e) => {
                     error!(Option::from("getting waiting timeout"), &format!("{e:?}"));
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
 
@@ -113,10 +113,10 @@ impl Configuration for RouterRuntime {
                 Ok(capacity) => {
                     let val: u64 = capacity;
                     usize::try_from(val).map_err(|_| {
-                        QueryPlannerError::CustomError(format!(
-                            "Router cache capacity is too big: {}",
-                            capacity
-                        ))
+                        SbroadError::Invalid(
+                            Entity::Cache,
+                            Some(format!("router cache capacity is too big: {capacity}")),
+                        )
                     })?
                 }
                 Err(e) => {
@@ -124,7 +124,7 @@ impl Configuration for RouterRuntime {
                         Option::from("getting router cache capacity"),
                         &format!("{e:?}"),
                     );
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
 
@@ -133,7 +133,7 @@ impl Configuration for RouterRuntime {
                 Ok(column) => column,
                 Err(e) => {
                     error!(Option::from("getting sharding column"), &format!("{e:?}"));
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
 
@@ -164,9 +164,9 @@ impl Coordinator for RouterRuntime {
     type ParseTree = AbstractSyntaxTree;
     type Cache = LRUCache<String, Plan>;
 
-    fn clear_ir_cache(&self) -> Result<(), QueryPlannerError> {
+    fn clear_ir_cache(&self) -> Result<(), SbroadError> {
         *self.ir_cache.try_borrow_mut().map_err(|e| {
-            QueryPlannerError::CustomError(format!("Failed to clear the cache: {e:?}"))
+            SbroadError::FailedTo(Action::Clear, Some(Entity::Cache), format!("{e:?}"))
         })? = Self::Cache::new(DEFAULT_CAPACITY, None)?;
         Ok(())
     }
@@ -182,7 +182,7 @@ impl Coordinator for RouterRuntime {
         plan: &mut ExecutionPlan,
         top_id: usize,
         buckets: &Buckets,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         debug!(
             Option::from("dispatch"),
             &format!("dispatching plan: {plan:?}")
@@ -211,28 +211,27 @@ impl Coordinator for RouterRuntime {
         let can_be_cached =
             |plan: &ExecutionPlan| plan.get_vtables().map_or(true, HashMap::is_empty);
 
-        let encode_plan =
-            |exec_plan: ExecutionPlan| -> Result<(Binary, Binary), QueryPlannerError> {
-                let sp_top_id = exec_plan.get_ir_plan().get_top()?;
-                let sp = SyntaxPlan::new(&exec_plan, sp_top_id, Snapshot::Oldest)?;
-                let ordered = OrderedSyntaxNodes::try_from(sp)?;
-                let nodes = ordered.to_syntax_data()?;
-                // Virtual tables in the plan must be already filtered, so we can use all buckets here.
-                let params = exec_plan.to_params(&nodes, &Buckets::All)?;
-                let query_type = exec_plan.query_type()?;
-                let required_data = RequiredData::new(
-                    sub_plan_id.clone(),
-                    params,
-                    query_type,
-                    can_be_cached(&exec_plan),
-                );
-                let encoded_required_data = EncodedRequiredData::try_from(required_data)?;
-                let raw_required_data: Vec<u8> = encoded_required_data.into();
-                let optional_data = OptionalData::new(exec_plan, ordered);
-                let encoded_optional_data = EncodedOptionalData::try_from(optional_data)?;
-                let raw_optional_data: Vec<u8> = encoded_optional_data.into();
-                Ok((raw_required_data.into(), raw_optional_data.into()))
-            };
+        let encode_plan = |exec_plan: ExecutionPlan| -> Result<(Binary, Binary), SbroadError> {
+            let sp_top_id = exec_plan.get_ir_plan().get_top()?;
+            let sp = SyntaxPlan::new(&exec_plan, sp_top_id, Snapshot::Oldest)?;
+            let ordered = OrderedSyntaxNodes::try_from(sp)?;
+            let nodes = ordered.to_syntax_data()?;
+            // Virtual tables in the plan must be already filtered, so we can use all buckets here.
+            let params = exec_plan.to_params(&nodes, &Buckets::All)?;
+            let query_type = exec_plan.query_type()?;
+            let required_data = RequiredData::new(
+                sub_plan_id.clone(),
+                params,
+                query_type,
+                can_be_cached(&exec_plan),
+            );
+            let encoded_required_data = EncodedRequiredData::try_from(required_data)?;
+            let raw_required_data: Vec<u8> = encoded_required_data.into();
+            let optional_data = OptionalData::new(exec_plan, ordered);
+            let encoded_optional_data = EncodedOptionalData::try_from(optional_data)?;
+            let raw_optional_data: Vec<u8> = encoded_optional_data.into();
+            Ok((raw_required_data.into(), raw_optional_data.into()))
+        };
 
         if let Buckets::Filtered(bucket_set) = buckets {
             let random_bucket = self.get_random_bucket();
@@ -252,8 +251,8 @@ impl Coordinator for RouterRuntime {
             let mut rs_ir: HashMap<String, Message> = HashMap::new();
             let rs_bucket_vec: Vec<(String, Vec<u64>)> = group(buckets)?.drain().collect();
             if rs_bucket_vec.is_empty() {
-                return Err(QueryPlannerError::CustomError(format!(
-                    "No replica sets were found for the buckets {:?} to execute the query on",
+                return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                    "no replica sets were found for the buckets {:?} to execute the query on",
                     buckets
                 )));
             }
@@ -277,15 +276,16 @@ impl Coordinator for RouterRuntime {
         self.exec_ir_on_all(required, optional, query_type, conn_type)
     }
 
-    fn explain_format(&self, explain: String) -> Result<Box<dyn Any>, QueryPlannerError> {
+    fn explain_format(&self, explain: String) -> Result<Box<dyn Any>, SbroadError> {
         let e = explain.lines().collect::<Vec<&str>>();
 
         match Tuple::new(&vec![e]) {
             Ok(t) => Ok(Box::new(t)),
-            Err(e) => Err(QueryPlannerError::CustomError(format!(
-                "Tuple creation error: {}",
-                e
-            ))),
+            Err(e) => Err(SbroadError::FailedTo(
+                Action::Create,
+                Some(Entity::Tuple),
+                format!("{e}"),
+            )),
         }
     }
 
@@ -296,7 +296,7 @@ impl Coordinator for RouterRuntime {
         plan: &mut ExecutionPlan,
         motion_node_id: usize,
         buckets: &Buckets,
-    ) -> Result<VirtualTable, QueryPlannerError> {
+    ) -> Result<VirtualTable, SbroadError> {
         let top_id = plan.get_motion_subtree_root(motion_node_id)?;
         // We should get a motion alias name before we take the subtree in dispatch.
         let alias = plan.get_motion_alias(motion_node_id)?.map(String::from);
@@ -305,18 +305,21 @@ impl Coordinator for RouterRuntime {
         plan.unlink_motion_subtree(motion_node_id)?;
         let mut vtable = if let Ok(tuple) = result.downcast::<Tuple>() {
             let data = tuple.decode::<Vec<ProducerResult>>().map_err(|e| {
-                QueryPlannerError::CustomError(format!("Motion node {motion_node_id}. {e}"))
+                SbroadError::FailedTo(
+                    Action::Decode,
+                    Some(Entity::Tuple),
+                    format!("motion node {motion_node_id}. {e}"),
+                )
             })?;
             data.get(0)
                 .ok_or_else(|| {
-                    QueryPlannerError::CustomError(
-                        "Failed to retrieve producer result from the tuple".into(),
-                    )
+                    SbroadError::NotFound(Entity::ProducerResult, "from the tuple".into())
                 })?
                 .as_virtual_table()?
         } else {
-            return Err(QueryPlannerError::CustomError(
-                "The result of the motion is not a tuple".to_string(),
+            return Err(SbroadError::Invalid(
+                Entity::Motion,
+                Some("the result of the motion is not a tuple".to_string()),
             ));
         };
         if let Some(name) = alias {
@@ -330,7 +333,7 @@ impl Coordinator for RouterRuntime {
         &'engine self,
         space: String,
         map: &'rec HashMap<String, Value>,
-    ) -> Result<Vec<&'rec Value>, QueryPlannerError> {
+    ) -> Result<Vec<&'rec Value>, SbroadError> {
         sharding_keys_from_map(&self.metadata, &space, map)
     }
 
@@ -338,7 +341,7 @@ impl Coordinator for RouterRuntime {
         &'engine self,
         space: String,
         rec: &'rec [Value],
-    ) -> Result<Vec<&'rec Value>, QueryPlannerError> {
+    ) -> Result<Vec<&'rec Value>, SbroadError> {
         sharding_keys_from_tuple(self.cached_config(), &space, rec)
     }
 
@@ -353,7 +356,7 @@ impl RouterRuntime {
     ///
     /// # Errors
     /// - Failed to detect the correct amount of buckets.
-    pub fn new() -> Result<Self, QueryPlannerError> {
+    pub fn new() -> Result<Self, SbroadError> {
         let cache: LRUCache<String, Plan> = LRUCache::new(DEFAULT_CAPACITY, None)?;
         let mut result = RouterRuntime {
             metadata: RouterConfiguration::new(),
@@ -375,7 +378,7 @@ impl RouterRuntime {
     }
 
     /// Function get summary count of bucket from `vshard`
-    fn set_bucket_count(&mut self) -> Result<(), QueryPlannerError> {
+    fn set_bucket_count(&mut self) -> Result<(), SbroadError> {
         let lua = tarantool::lua_state();
 
         let bucket_count_fn: LuaFunction<_> =
@@ -383,7 +386,7 @@ impl RouterRuntime {
                 Ok(v) => v,
                 Err(e) => {
                     error!(Option::from("set_bucket_count"), &format!("{e:?}"));
-                    return Err(QueryPlannerError::LuaError(format!(
+                    return Err(SbroadError::LuaError(format!(
                         "Failed lua function load: {}",
                         e
                     )));
@@ -394,16 +397,17 @@ impl RouterRuntime {
             Ok(r) => r,
             Err(e) => {
                 error!(Option::from("set_bucket_count"), &format!("{e:?}"));
-                return Err(QueryPlannerError::LuaError(e.to_string()));
+                return Err(SbroadError::LuaError(e.to_string()));
             }
         };
 
         self.bucket_count = match bucket_count.try_into() {
             Ok(v) => v,
             Err(_) => {
-                return Err(QueryPlannerError::CustomError(String::from(
-                    "Invalid bucket count",
-                )));
+                return Err(SbroadError::Invalid(
+                    Entity::Runtime,
+                    Some("invalid bucket count".into()),
+                ));
             }
         };
 
@@ -413,12 +417,12 @@ impl RouterRuntime {
     fn read_dql_on_some(
         &self,
         rs_ir: HashMap<String, Message>,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         let lua = tarantool::lua_state();
 
-        let exec_sql: LuaFunction<_> = lua.get("read_dql_on_some").ok_or_else(|| {
-            QueryPlannerError::LuaError("Lua function `read_on_some` not found".into())
-        })?;
+        let exec_sql: LuaFunction<_> = lua
+            .get("read_dql_on_some")
+            .ok_or_else(|| SbroadError::LuaError("Lua function `read_on_some` not found".into()))?;
 
         let waiting_timeout = &self.cached_config().get_exec_waiting_timeout();
         match exec_sql.call_with_args::<Tuple, _>((rs_ir, waiting_timeout)) {
@@ -431,7 +435,7 @@ impl RouterRuntime {
             }
             Err(e) => {
                 error!(Option::from("read_dql_on_some"), &format!("{e:?}"));
-                Err(QueryPlannerError::LuaError(format!(
+                Err(SbroadError::LuaError(format!(
                     "Lua error (IR dispatch): {:?}",
                     e
                 )))
@@ -442,11 +446,11 @@ impl RouterRuntime {
     fn write_dml_on_some(
         &self,
         rs_ir: HashMap<String, Message>,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         let lua = tarantool::lua_state();
 
         let exec_sql: LuaFunction<_> = lua.get("write_dml_on_some").ok_or_else(|| {
-            QueryPlannerError::LuaError("Lua function `write_dml_on_some` not found".into())
+            SbroadError::LuaError("Lua function `write_dml_on_some` not found".into())
         })?;
 
         let waiting_timeout = &self.cached_config().get_exec_waiting_timeout();
@@ -454,7 +458,7 @@ impl RouterRuntime {
             Ok(v) => Ok(Box::new(v)),
             Err(e) => {
                 error!(Option::from("write_on_some"), &format!("{e:?}"));
-                Err(QueryPlannerError::LuaError(format!(
+                Err(SbroadError::LuaError(format!(
                     "Lua error (IR dispatch): {:?}",
                     e
                 )))
@@ -468,14 +472,17 @@ impl RouterRuntime {
         rs_ir: HashMap<String, Message>,
         query_type: QueryType,
         conn_type: ConnectionType,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         match (&query_type, &conn_type) {
             (QueryType::DQL, ConnectionType::Read) => self.read_dql_on_some(rs_ir),
             (QueryType::DML, ConnectionType::Write) => self.write_dml_on_some(rs_ir),
-            _ => Err(QueryPlannerError::CustomError(format!(
-                "Unsupported combination of the query type: {:?} and connection type: {:?}",
-                query_type, conn_type
-            ))),
+            _ => Err(SbroadError::Unsupported(
+                Entity::Type,
+                Some(format!(
+                    "unsupported combination of the query type: {:?} and connection type: {:?}",
+                    query_type, conn_type
+                )),
+            )),
         }
     }
 
@@ -483,10 +490,10 @@ impl RouterRuntime {
         &self,
         required: Binary,
         optional: Binary,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         let lua = tarantool::lua_state();
         let exec_sql: LuaFunction<_> = lua.get("read_dql_on_all").ok_or_else(|| {
-            QueryPlannerError::LuaError("Lua function `read_dql_on_all` not found".into())
+            SbroadError::LuaError("Lua function `read_dql_on_all` not found".into())
         })?;
 
         let waiting_timeout = &self.cached_config().get_exec_waiting_timeout();
@@ -500,7 +507,7 @@ impl RouterRuntime {
             }
             Err(e) => {
                 error!(Option::from("read_dql_on_all"), &format!("{e:?}"));
-                Err(QueryPlannerError::LuaError(format!(
+                Err(SbroadError::LuaError(format!(
                     "Lua error (dispatch IR): {:?}",
                     e
                 )))
@@ -512,11 +519,11 @@ impl RouterRuntime {
         &self,
         required: Binary,
         optional: Binary,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         let lua = tarantool::lua_state();
 
         let exec_sql: LuaFunction<_> = lua.get("write_dml_on_all").ok_or_else(|| {
-            QueryPlannerError::LuaError("Lua function `write_dml_on_all` not found".into())
+            SbroadError::LuaError("Lua function `write_dml_on_all` not found".into())
         })?;
 
         let waiting_timeout = &self.cached_config().get_exec_waiting_timeout();
@@ -524,7 +531,7 @@ impl RouterRuntime {
             Ok(v) => Ok(Box::new(v)),
             Err(e) => {
                 error!(Option::from("write_dml_on_all"), &format!("{e:?}"));
-                Err(QueryPlannerError::LuaError(format!(
+                Err(SbroadError::LuaError(format!(
                     "Lua error (dispatch IR): {:?}",
                     e
                 )))
@@ -539,24 +546,28 @@ impl RouterRuntime {
         optional: Binary,
         query_type: QueryType,
         conn_type: ConnectionType,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         match (&query_type, &conn_type) {
             (QueryType::DQL, ConnectionType::Read) => self.read_dql_on_all(required, optional),
             (QueryType::DML, ConnectionType::Write) => self.write_dml_on_all(required, optional),
-            _ => Err(QueryPlannerError::CustomError(format!(
-                "Unsupported combination of the query type: {:?} and connection type: {:?}",
-                query_type, conn_type
-            ))),
+            _ => Err(SbroadError::Unsupported(
+                Entity::Type,
+                Some(format!(
+                    "unsupported combination of the query type: {:?} and connection type: {:?}",
+                    query_type, conn_type
+                )),
+            )),
         }
     }
 }
 
 #[otm_child_span("buckets.group")]
-fn group(buckets: &Buckets) -> Result<HashMap<String, Vec<u64>>, QueryPlannerError> {
+fn group(buckets: &Buckets) -> Result<HashMap<String, Vec<u64>>, SbroadError> {
     let lua_buckets: Vec<u64> = match buckets {
         Buckets::All => {
-            return Err(QueryPlannerError::CustomError(
-                "Grouping buckets is not supported for all buckets".into(),
+            return Err(SbroadError::Unsupported(
+                Entity::Buckets,
+                Some("grouping buckets is not supported for all buckets".into()),
             ))
         }
         Buckets::Filtered(list) => list.iter().copied().collect(),
@@ -565,14 +576,14 @@ fn group(buckets: &Buckets) -> Result<HashMap<String, Vec<u64>>, QueryPlannerErr
     let lua = tarantool::lua_state();
 
     let fn_group: LuaFunction<_> = lua.get("group_buckets_by_replicasets").ok_or_else(|| {
-        QueryPlannerError::LuaError("Lua function `group_buckets_by_replicasets` not found".into())
+        SbroadError::LuaError("Lua function `group_buckets_by_replicasets` not found".into())
     })?;
 
     let res: GroupedBuckets = match fn_group.call_with_args(lua_buckets) {
         Ok(v) => v,
         Err(e) => {
             error!(Option::from("buckets group"), &format!("{e:?}"));
-            return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+            return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
         }
     };
 
diff --git a/sbroad-cartridge/src/cartridge/storage.rs b/sbroad-cartridge/src/cartridge/storage.rs
index 5664b0dada..807a649ffc 100644
--- a/sbroad-cartridge/src/cartridge/storage.rs
+++ b/sbroad-cartridge/src/cartridge/storage.rs
@@ -1,7 +1,7 @@
 use crate::api::exec_query::protocol::{EncodedOptionalData, OptionalData, RequiredData};
 use crate::cartridge::config::StorageConfiguration;
 use crate::cartridge::update_tracing;
-use sbroad::errors::QueryPlannerError;
+use sbroad::errors::{Action, Entity, SbroadError};
 use sbroad::executor::bucket::Buckets;
 use sbroad::executor::engine::Configuration;
 use sbroad::executor::ir::QueryType;
@@ -29,17 +29,20 @@ impl PreparedStmt {
     ///
     /// # Errors
     /// - Returns None instead of a regular statement (sentinel node in the cache).
-    fn statement(&self) -> Result<&Statement, QueryPlannerError> {
-        self.0
-            .as_ref()
-            .ok_or_else(|| QueryPlannerError::CustomError("Statement is not prepared".to_string()))
+    fn statement(&self) -> Result<&Statement, SbroadError> {
+        self.0.as_ref().ok_or_else(|| {
+            SbroadError::Invalid(
+                Entity::Statement,
+                Some("Statement is not prepared".to_string()),
+            )
+        })
     }
 
-    fn id(&self) -> Result<u32, QueryPlannerError> {
+    fn id(&self) -> Result<u32, SbroadError> {
         Ok(self.statement()?.id)
     }
 
-    fn pattern(&self) -> Result<&str, QueryPlannerError> {
+    fn pattern(&self) -> Result<&str, SbroadError> {
         Ok(&self.statement()?.pattern)
     }
 }
@@ -75,7 +78,7 @@ impl Configuration for StorageRuntime {
         self.metadata.is_empty()
     }
 
-    fn get_config(&self) -> Result<Option<Self::Configuration>, QueryPlannerError> {
+    fn get_config(&self) -> Result<Option<Self::Configuration>, SbroadError> {
         if self.is_config_empty() {
             let lua = tarantool::lua_state();
 
@@ -88,11 +91,11 @@ impl Configuration for StorageRuntime {
                         Option::from("getting storage cache capacity"),
                         &format!("{e:?}"),
                     );
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
             let storage_capacity = usize::try_from(capacity)
-                .map_err(|e| QueryPlannerError::CustomError(format!("{e:?}")))?;
+                .map_err(|e| SbroadError::Invalid(Entity::Cache, Some(format!("{e:?}"))))?;
 
             let storage_cache_size_bytes: LuaFunction<_> =
                 lua.eval("return get_storage_cache_size_bytes;").unwrap();
@@ -103,11 +106,11 @@ impl Configuration for StorageRuntime {
                         Option::from("getting storage cache size bytes"),
                         &format!("{e:?}"),
                     );
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
             let storage_size_bytes = usize::try_from(cache_size_bytes)
-                .map_err(|e| QueryPlannerError::CustomError(format!("{e}")))?;
+                .map_err(|e| SbroadError::Invalid(Entity::Cache, Some(format!("{e}"))))?;
 
             let jaeger_agent_host: LuaFunction<_> =
                 lua.eval("return get_jaeger_agent_host;").unwrap();
@@ -115,7 +118,7 @@ impl Configuration for StorageRuntime {
                 Ok(res) => res,
                 Err(e) => {
                     error!(Option::from("getting jaeger agent host"), &format!("{e:?}"),);
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
 
@@ -125,7 +128,7 @@ impl Configuration for StorageRuntime {
                 Ok(res) => res,
                 Err(e) => {
                     error!(Option::from("getting jaeger agent port"), &format!("{e:?}"),);
-                    return Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")));
+                    return Err(SbroadError::LuaError(format!("Lua error: {e:?}")));
                 }
             };
 
@@ -152,7 +155,7 @@ impl StorageRuntime {
     ///
     /// # Errors
     /// - Failed to initialize the LRU cache.
-    pub fn new() -> Result<Self, QueryPlannerError> {
+    pub fn new() -> Result<Self, SbroadError> {
         let cache: LRUCache<String, PreparedStmt> =
             LRUCache::new(DEFAULT_CAPACITY, Some(Box::new(unprepare)))?;
         let result = StorageRuntime {
@@ -168,7 +171,7 @@ impl StorageRuntime {
         &self,
         required: &mut RequiredData,
         raw_optional: &mut Vec<u8>,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         let plan_id = required.plan_id.clone();
 
         // Use all buckets as we don't want to filter any data from the execution plan
@@ -181,7 +184,7 @@ impl StorageRuntime {
                 .cache
                 .try_borrow_mut()
                 .map_err(|e| {
-                    QueryPlannerError::CustomError(format!("Failed to borrow cache: {e}"))
+                    SbroadError::FailedTo(Action::Borrow, Some(Entity::Cache), format!("{e}"))
                 })?
                 .get(&plan_id)?
             {
@@ -230,10 +233,11 @@ impl StorageRuntime {
                     self.cache
                         .try_borrow_mut()
                         .map_err(|e| {
-                            QueryPlannerError::CustomError(format!(
-                                "Failed to put prepared statement {:?} into the cache: {:?}",
-                                stmt, e
-                            ))
+                            SbroadError::FailedTo(
+                                Action::Put,
+                                None,
+                                format!("prepared statement {stmt:?} into the cache: {e:?}"),
+                            )
                         })?
                         .put(plan_id, stmt)?;
                 }
@@ -279,12 +283,12 @@ impl StorageRuntime {
 }
 
 #[otm_child_span("tarantool.statement.prepare")]
-fn prepare(pattern: &str) -> Result<PreparedStmt, QueryPlannerError> {
+fn prepare(pattern: &str) -> Result<PreparedStmt, SbroadError> {
     let lua = tarantool::lua_state();
 
     let prepare_stmt: LuaFunction<_> = lua
         .get("prepare")
-        .ok_or_else(|| QueryPlannerError::LuaError("Lua function `prepare` not found".into()))?;
+        .ok_or_else(|| SbroadError::LuaError("Lua function `prepare` not found".into()))?;
 
     match prepare_stmt.call_with_args::<u32, _>(pattern) {
         Ok(stmt_id) => {
@@ -296,100 +300,92 @@ fn prepare(pattern: &str) -> Result<PreparedStmt, QueryPlannerError> {
         }
         Err(e) => {
             error!(Option::from("prepare"), &format!("{e:?}"));
-            Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")))
+            Err(SbroadError::LuaError(format!("Lua error: {e:?}")))
         }
     }
 }
 
 #[otm_child_span("tarantool.statement.unprepare")]
-fn unprepare(stmt: &mut PreparedStmt) -> Result<(), QueryPlannerError> {
+fn unprepare(stmt: &mut PreparedStmt) -> Result<(), SbroadError> {
     let lua = tarantool::lua_state();
 
     let unprepare_stmt: LuaFunction<_> = lua
         .get("unprepare")
-        .ok_or_else(|| QueryPlannerError::LuaError("Lua function `unprepare` not found".into()))?;
+        .ok_or_else(|| SbroadError::LuaError("Lua function `unprepare` not found".into()))?;
 
     match unprepare_stmt.call_with_args::<(), _>(stmt.id()?) {
         Ok(_) => Ok(()),
         Err(e) => {
             error!(Option::from("unprepare"), &format!("{e:?}"));
-            Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")))
+            Err(SbroadError::LuaError(format!("Lua error: {e:?}")))
         }
     }
 }
 
 #[otm_child_span("tarantool.statement.prepared.read")]
-fn read_prepared(
-    stmt_id: u32,
-    stmt: &str,
-    params: &[Value],
-) -> Result<Box<dyn Any>, QueryPlannerError> {
+fn read_prepared(stmt_id: u32, stmt: &str, params: &[Value]) -> Result<Box<dyn Any>, SbroadError> {
     let lua = tarantool::lua_state();
 
     let exec_sql: LuaFunction<_> = lua
         .get("read")
-        .ok_or_else(|| QueryPlannerError::LuaError("Lua function `read` not found".into()))?;
+        .ok_or_else(|| SbroadError::LuaError("Lua function `read` not found".into()))?;
 
     match exec_sql.call_with_args::<Tuple, _>((stmt_id, stmt, params)) {
         Ok(v) => Ok(Box::new(v) as Box<dyn Any>),
         Err(e) => {
             error!(Option::from("read_prepared"), &format!("{e:?}"));
-            Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")))
+            Err(SbroadError::LuaError(format!("Lua error: {e:?}")))
         }
     }
 }
 
 #[otm_child_span("tarantool.statement.unprepared.read")]
-fn read_unprepared(stmt: &str, params: &[Value]) -> Result<Box<dyn Any>, QueryPlannerError> {
+fn read_unprepared(stmt: &str, params: &[Value]) -> Result<Box<dyn Any>, SbroadError> {
     let lua = tarantool::lua_state();
 
     let exec_sql: LuaFunction<_> = lua
         .get("read")
-        .ok_or_else(|| QueryPlannerError::LuaError("Lua function `read` not found".into()))?;
+        .ok_or_else(|| SbroadError::LuaError("Lua function `read` not found".into()))?;
 
     match exec_sql.call_with_args::<Tuple, _>((0, stmt, params)) {
         Ok(v) => Ok(Box::new(v) as Box<dyn Any>),
         Err(e) => {
             error!(Option::from("read_unprepared"), &format!("{e:?}"));
-            Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")))
+            Err(SbroadError::LuaError(format!("Lua error: {e:?}")))
         }
     }
 }
 
 #[otm_child_span("tarantool.statement.prepared.write")]
-fn write_prepared(
-    stmt_id: u32,
-    stmt: &str,
-    params: &[Value],
-) -> Result<Box<dyn Any>, QueryPlannerError> {
+fn write_prepared(stmt_id: u32, stmt: &str, params: &[Value]) -> Result<Box<dyn Any>, SbroadError> {
     let lua = tarantool::lua_state();
 
     let exec_sql: LuaFunction<_> = lua
         .get("write")
-        .ok_or_else(|| QueryPlannerError::LuaError("Lua function `write` not found".into()))?;
+        .ok_or_else(|| SbroadError::LuaError("Lua function `write` not found".into()))?;
 
     match exec_sql.call_with_args::<Tuple, _>((stmt_id, stmt, params)) {
         Ok(v) => Ok(Box::new(v) as Box<dyn Any>),
         Err(e) => {
             error!(Option::from("write_prepared"), &format!("{e:?}"));
-            Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")))
+            Err(SbroadError::LuaError(format!("Lua error: {e:?}")))
         }
     }
 }
 
 #[otm_child_span("tarantool.statement.unprepared.write")]
-fn write_unprepared(stmt: &str, params: &[Value]) -> Result<Box<dyn Any>, QueryPlannerError> {
+fn write_unprepared(stmt: &str, params: &[Value]) -> Result<Box<dyn Any>, SbroadError> {
     let lua = tarantool::lua_state();
 
     let exec_sql: LuaFunction<_> = lua
         .get("write")
-        .ok_or_else(|| QueryPlannerError::LuaError("Lua function `write` not found".into()))?;
+        .ok_or_else(|| SbroadError::LuaError("Lua function `write` not found".into()))?;
 
     match exec_sql.call_with_args::<Tuple, _>((0, stmt, params)) {
         Ok(v) => Ok(Box::new(v) as Box<dyn Any>),
         Err(e) => {
             error!(Option::from("write_unprepared"), &format!("{e:?}"));
-            Err(QueryPlannerError::LuaError(format!("Lua error: {e:?}")))
+            Err(SbroadError::LuaError(format!("Lua error: {e:?}")))
         }
     }
 }
diff --git a/sbroad-cartridge/test_app/test/data/config.yml b/sbroad-cartridge/test_app/test/data/config.yml
index 182b10a171..773f37221f 100644
--- a/sbroad-cartridge/test_app/test/data/config.yml
+++ b/sbroad-cartridge/test_app/test/data/config.yml
@@ -867,3 +867,4 @@ schema:
           is_nullable: true
         name: bucket_id
         type: TREE
+
diff --git a/sbroad-cartridge/test_app/test/integration/api_test.lua b/sbroad-cartridge/test_app/test/integration/api_test.lua
index 4e6c169704..28d7c5476e 100644
--- a/sbroad-cartridge/test_app/test/integration/api_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/api_test.lua
@@ -1,7 +1,9 @@
 local t = require('luatest')
+local datetime = require("datetime")
 local g = t.group('integration_api')
 
 local helper = require('test.helper.cluster_no_replication')
+local config_handler = require('test.helper.config_handler')
 local cluster = nil
 
 g.before_all(function()
@@ -81,6 +83,11 @@ g.after_each(
     end
 )
 
+g.after_test("test_unsupported_column", function()
+    local default_config = config_handler.get_init_config(helper.root)
+    cluster:upload_config(default_config)
+end)
+
 g.after_all(function()
     helper.stop_test_cluster()
 end)
@@ -159,9 +166,13 @@ g.test_bucket_id_calculation = function()
     t.assert_equals(r, nil)
     t.assert_str_contains(tostring(err), [[expected to have 3 filed(s), got 5]])
 
+    -- luacheck: max line length 150
     r, err = api:call("sbroad.calculate_bucket_id", { { id = 1 }, "testing_space" })
     t.assert_equals(r, nil)
-    t.assert_str_contains(tostring(err), [[Missing quoted sharding key column]])
+    t.assert_equals(
+        tostring(err),
+        [["sharding key (quoted) column \"\\\"name\\\"\" in the quoted map {\"\\\"id\\\"\": \"id\"} (original map: {\"id\": Integer(1)}) not found"]]
+    )
 
     r, err = api:call("sbroad.calculate_bucket_id", { { id = 1, "123" }, "testing_space" })
     t.assert_equals(r, nil)
@@ -176,7 +187,82 @@ g.test_incorrect_query = function()
     local api = cluster:server("api-1").net_box
 
     local _, err = api:call("sbroad.execute", { [[SELECT * FROM "testing_space" INNER JOIN "testing_space"]], {} })
-    t.assert_str_contains(tostring(err), "Parsing error")
+    t.assert_str_contains(tostring(err), "parsing error")
+end
+
+g.test_query_errored = function()
+    local api = cluster:server("api-1").net_box
+
+    local _, err = api:call("sbroad.execute", { [[SELECT * FROM "NotFoundSpace"]], {} })
+    t.assert_str_contains(tostring(err), "\"space \\\"NotFoundSpace\\\" not found\"")
+
+    -- luacheck: max line length 140
+    local _, err = api:call("sbroad.execute", { [[SELECT "NotFoundColumn" FROM "testing_space"]], {} })
+    t.assert_equals(tostring(err), [["column with name [\"\\\"NotFoundColumn\\\"\"] not found"]])
+
+    local invalid_type_param = datetime.new{
+        nsec = 123456789,
+        sec = 20,
+        min = 25,
+        hour = 18,
+        day = 20,
+        month = 8,
+        year = 2021,
+        tzoffset  = 180
+    }
+
+    local _, err = api:call("sbroad.execute", { [[SELECT * FROM "testing_space" where "id" = ?]], {invalid_type_param} })
+    t.assert_equals(
+        tostring(err),
+        "\"pattern with parameters parsing error: Decode(Syntax(\\\"data did not match any variant of untagged enum EncodedValue\\\"))\""
+    )
+
+    -- check err when params lenght is less then amount of sign `?`
+    local _, err = api:call("sbroad.execute", { [[SELECT * FROM "testing_space" where "id" = ?]], {} })
+    t.assert_equals(
+        tostring(err),
+        "\"invalid node: parameter node does not refer to an expression\""
+    )
+end
+
+g.test_unsupported_column = function()
+    local api = cluster:server("api-1").net_box
+
+    local config = cluster:download_config()
+    local space_with_unsupported_column = {
+        format = {
+            { type = "integer", name = "id", is_nullable = false },
+            { type = "datetime", name = "unsupported_column", is_nullable = false },
+            { type = "unsigned", name = "bucket_id", is_nullable = true },
+        },
+        temporary = false,
+        engine = "memtx",
+        is_local = false,
+        sharding_key = { "id" },
+        indexes = {
+            {
+                unique = true,
+                parts = {{ path = "id", type = "integer", is_nullable = false}},
+                name = "id",
+                type = "TREE"
+            },
+            {
+                unique = false,
+                parts = { { path = "bucket_id", type = "unsigned", is_nullable = true } },
+                name = "bucket_id",
+                type = "TREE"
+            }
+        }
+    }
+
+    config["schema"]["spaces"]["space_with_unsupported_column"] = space_with_unsupported_column
+    cluster:upload_config(config)
+
+    local _, err = api:call("sbroad.execute", { [[SELECT * FROM "space_with_unsupported_column"]], {} })
+    t.assert_str_contains(
+        tostring(err),
+        "type datetime not implemented"
+    )
 end
 
 g.test_join_query_is_valid = function()
diff --git a/sbroad-cartridge/test_app/test/integration/validate_schema_test.lua b/sbroad-cartridge/test_app/test/integration/validate_schema_test.lua
index 785c365653..3a31fe2614 100644
--- a/sbroad-cartridge/test_app/test/integration/validate_schema_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/validate_schema_test.lua
@@ -73,5 +73,5 @@ g.test_schema_invalid = function ()
   local api = helper.cluster:server("api-1").net_box
 
   local _, err = api:call("sbroad.execute", { [[select * from "t"]], {}})
-  t.assert_str_contains(tostring(err), "Failed to get configuration: type `map` not implemented")
+  t.assert_str_contains(tostring(err), "Failed to get configuration: type map not implemented")
 end
diff --git a/sbroad-core/src/backend/sql/ir.rs b/sbroad-core/src/backend/sql/ir.rs
index 3420580c30..9def19f811 100644
--- a/sbroad-core/src/backend/sql/ir.rs
+++ b/sbroad-core/src/backend/sql/ir.rs
@@ -7,7 +7,7 @@ use tarantool::tlua::{self, Push};
 use tarantool::tuple::{FunctionArgs, Tuple};
 
 use crate::debug;
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::bucket::Buckets;
 use crate::executor::ir::ExecutionPlan;
 use crate::ir::expression::Expression;
@@ -37,19 +37,19 @@ impl PartialEq for PatternWithParams {
 }
 
 impl TryFrom<FunctionArgs> for PatternWithParams {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_from(value: FunctionArgs) -> Result<Self, Self::Error> {
         debug!(
             Option::from("argument parsing"),
-            &format!("Query parameters: {:?}", value),
+            &format!("Query parameters: {value:?}"),
         );
         match Tuple::from(value).decode::<EncodedPatternWithParams>() {
             Ok(encoded) => Ok(PatternWithParams::from(encoded)),
-            Err(e) => Err(QueryPlannerError::CustomError(format!(
-                "Parsing error (pattern with parameters): {:?}",
-                e
-            ))),
+            Err(e) => Err(SbroadError::ParsingError(
+                Entity::PatternWithParams,
+                format!("{e:?}"),
+            )),
         }
     }
 }
@@ -143,7 +143,7 @@ impl ExecutionPlan {
         &self,
         nodes: &[&SyntaxData],
         buckets: &Buckets,
-    ) -> Result<Vec<Value>, QueryPlannerError> {
+    ) -> Result<Vec<Value>, SbroadError> {
         let ir_plan = self.get_ir_plan();
         let mut params: Vec<Value> = Vec::new();
 
@@ -154,10 +154,10 @@ impl ExecutionPlan {
                     if let Expression::Constant { value, .. } = value {
                         params.push(value.clone());
                     } else {
-                        return Err(QueryPlannerError::CustomError(format!(
-                            "Parameter {:?} is not a constant",
-                            value
-                        )));
+                        return Err(SbroadError::Invalid(
+                            Entity::Expression,
+                            Some(format!("parameter {value:?} is not a constant")),
+                        ));
                     }
                 }
                 SyntaxData::VTable(motion_id) => {
@@ -185,7 +185,7 @@ impl ExecutionPlan {
         &self,
         nodes: &[&SyntaxData],
         buckets: &Buckets,
-    ) -> Result<PatternWithParams, QueryPlannerError> {
+    ) -> Result<PatternWithParams, SbroadError> {
         let (sql, params) = child_span("\"syntax.ordered.sql\"", || {
             let mut params: Vec<Value> = Vec::new();
 
@@ -242,8 +242,11 @@ impl ExecutionPlan {
                         let node = ir_plan.get_node(*id)?;
                         match node {
                             Node::Parameter => {
-                                return Err(QueryPlannerError::CustomError(
-                                    "Parameters are not supported in the generated SQL".into(),
+                                return Err(SbroadError::Unsupported(
+                                    Entity::Node,
+                                    Some(
+                                        "Parameters are not supported in the generated SQL".into(),
+                                    ),
                                 ));
                             }
                             Node::Relational(rel) => match rel {
@@ -274,9 +277,11 @@ impl ExecutionPlan {
                                     | Expression::Unary { .. } => {}
                                     Expression::Constant { value, .. } => {
                                         write!(sql, "{value}").map_err(|e| {
-                                            QueryPlannerError::CustomError(format!(
-                                                "Failed to write constant value to SQL: {e}"
-                                            ))
+                                            SbroadError::FailedTo(
+                                                Action::Put,
+                                                Some(Entity::Value),
+                                                format!("constant value to SQL: {e}"),
+                                            )
                                         })?;
                                     }
                                     Expression::Reference { position, .. } => {
@@ -286,7 +291,18 @@ impl ExecutionPlan {
 
                                         if rel_node.is_motion() {
                                             if let Ok(vt) = self.get_motion_vtable(*id) {
-                                                let alias = (*vt).get_columns().get(*position).map(|column| &column.name).ok_or_else(|| QueryPlannerError::CustomError(format!("Failed to get column name for position {position}")))?;
+                                                let alias = (*vt)
+                                                    .get_columns()
+                                                    .get(*position)
+                                                    .map(|column| &column.name)
+                                                    .ok_or_else(|| {
+                                                        SbroadError::NotFound(
+                                                            Entity::Name,
+                                                            format!(
+                                                                "for column at position {position}"
+                                                            ),
+                                                        )
+                                                    })?;
                                                 if let Some(name) = (*vt).get_alias() {
                                                     sql.push_str(name);
                                                     sql.push('.');
@@ -326,10 +342,10 @@ impl ExecutionPlan {
                         if let Expression::Constant { value, .. } = value {
                             params.push(value.clone());
                         } else {
-                            return Err(QueryPlannerError::CustomError(format!(
-                                "Parameter {:?} is not a constant",
-                                value
-                            )));
+                            return Err(SbroadError::Invalid(
+                                Entity::Expression,
+                                Some(format!("parameter {value:?} is not a constant")),
+                            ));
                         }
                     }
                     SyntaxData::VTable(motion_id) => {
@@ -362,7 +378,9 @@ impl ExecutionPlan {
                                 cols(&mut anonymous_col_idx_base),
                                 values
                             )
-                            .map_err(|e| QueryPlannerError::CustomError(e.to_string()))?;
+                            .map_err(|e| {
+                                SbroadError::Invalid(Entity::VirtualTable, Some(e.to_string()))
+                            })?;
                         } else {
                             let values = tuples
                                 .iter()
@@ -379,10 +397,11 @@ impl ExecutionPlan {
                                 values
                             )
                             .map_err(|e| {
-                                QueryPlannerError::CustomError(format!(
-                                    "Failed to generate SQL for VTable: {}",
-                                    e
-                                ))
+                                SbroadError::FailedTo(
+                                    Action::Build,
+                                    None,
+                                    format!("SQL for VTable: {e}"),
+                                )
                             })?;
 
                             for t in tuples {
@@ -404,7 +423,7 @@ impl ExecutionPlan {
     ///
     /// # Errors
     /// - If the subtree top is not a relational node.
-    pub fn subtree_modifies_data(&self, top_id: usize) -> Result<bool, QueryPlannerError> {
+    pub fn subtree_modifies_data(&self, top_id: usize) -> Result<bool, SbroadError> {
         // Tarantool doesn't support `INSERT`, `UPDATE` and `DELETE` statements
         // with `RETURNING` clause. That is why it is enough to check if the top
         // node is a data modification statement or not.
diff --git a/sbroad-core/src/backend/sql/tree.rs b/sbroad-core/src/backend/sql/tree.rs
index 63e0d8347d..e16e65bb4a 100644
--- a/sbroad-core/src/backend/sql/tree.rs
+++ b/sbroad-core/src/backend/sql/tree.rs
@@ -5,7 +5,7 @@ use std::mem::take;
 use serde::{Deserialize, Serialize};
 use traversal::DftPost;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::ir::ExecutionPlan;
 use crate::ir::expression::Expression;
 use crate::ir::operator::{Bool, Relational};
@@ -148,11 +148,12 @@ impl SyntaxNode {
         }
     }
 
-    fn left_id_or_err(&self) -> Result<usize, QueryPlannerError> {
+    fn left_id_or_err(&self) -> Result<usize, SbroadError> {
         match self.left {
             Some(id) => Ok(id),
-            None => Err(QueryPlannerError::CustomError(
-                "Left node is not set.".into(),
+            None => Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("left node is not set.".into()),
             )),
         }
     }
@@ -178,13 +179,13 @@ impl SyntaxNodes {
     ///
     /// # Errors
     /// - sub-query in plan tree is invalid
-    fn add_sq(&mut self, rel: &Relational, id: usize) -> Result<usize, QueryPlannerError> {
+    fn add_sq(&mut self, rel: &Relational, id: usize) -> Result<usize, SbroadError> {
         if let Relational::ScanSubQuery {
             children, alias, ..
         } = rel
         {
             let right_id = *children.first().ok_or_else(|| {
-                QueryPlannerError::CustomError("Sub-query has no children.".into())
+                SbroadError::UnexpectedNumberOfValues("Sub-query has no children.".into())
             })?;
             let mut children: Vec<usize> = vec![
                 self.push_syntax_node(SyntaxNode::new_open()),
@@ -197,8 +198,9 @@ impl SyntaxNodes {
             let sn = SyntaxNode::new_pointer(id, None, children);
             Ok(self.push_syntax_node(sn))
         } else {
-            Err(QueryPlannerError::CustomError(
-                "Current node is not a sub-query".into(),
+            Err(SbroadError::Invalid(
+                Entity::SyntaxNode,
+                Some("current node is not a sub-query".into()),
             ))
         }
     }
@@ -206,12 +208,18 @@ impl SyntaxNodes {
     /// Construct syntax nodes from the YAML file.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the YAML nodes arena is invalid.
+    /// Returns `SbroadError` when the YAML nodes arena is invalid.
     #[allow(dead_code)]
-    pub fn from_yaml(s: &str) -> Result<Self, QueryPlannerError> {
+    pub fn from_yaml(s: &str) -> Result<Self, SbroadError> {
         let nodes: SyntaxNodes = match serde_yaml::from_str(s) {
             Ok(p) => p,
-            Err(_) => return Err(QueryPlannerError::Serialization),
+            Err(e) => {
+                return Err(SbroadError::FailedTo(
+                    Action::Serialize,
+                    Some(Entity::SyntaxNodes),
+                    format!("{e:?}"),
+                ))
+            }
         };
         Ok(nodes)
     }
@@ -220,29 +228,34 @@ impl SyntaxNodes {
     ///
     /// # Errors
     /// - current node is invalid (doesn't exist in arena)
-    pub fn get_syntax_node(&self, id: usize) -> Result<&SyntaxNode, QueryPlannerError> {
-        self.arena.get(id).ok_or(QueryPlannerError::InvalidNode)
+    pub fn get_syntax_node(&self, id: usize) -> Result<&SyntaxNode, SbroadError> {
+        self.arena.get(id).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, format!("from arena with index {id}"))
+        })
     }
 
     /// Get a mutable syntax node from arena
     ///
     /// # Errors
     /// - current node is invalid (doesn't exist in arena)
-    pub fn get_mut_syntax_node(&mut self, id: usize) -> Result<&mut SyntaxNode, QueryPlannerError> {
-        self.arena.get_mut(id).ok_or(QueryPlannerError::InvalidNode)
+    pub fn get_mut_syntax_node(&mut self, id: usize) -> Result<&mut SyntaxNode, SbroadError> {
+        self.arena.get_mut(id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {id}"),
+            )
+        })
     }
 
     /// Get syntax node id by the plan node's one
     ///
     /// # Errors
     /// - nothing was found
-    fn get_syntax_node_id(&self, plan_id: usize) -> Result<usize, QueryPlannerError> {
-        self.map.get(&plan_id).copied().ok_or_else(|| {
-            QueryPlannerError::CustomError(format!(
-                "Current plan node ({}) is absent in the map",
-                plan_id
-            ))
-        })
+    fn get_syntax_node_id(&self, plan_id: usize) -> Result<usize, SbroadError> {
+        self.map
+            .get(&plan_id)
+            .copied()
+            .ok_or_else(|| SbroadError::NotFound(Entity::Node, format!("({plan_id}) in the map")))
     }
 
     /// Push a new syntax node to arena
@@ -313,7 +326,7 @@ impl Select {
         parent: Option<usize>,
         branch: Option<Branch>,
         id: usize,
-    ) -> Result<Option<Select>, QueryPlannerError> {
+    ) -> Result<Option<Select>, SbroadError> {
         let sn = sp.nodes.get_syntax_node(id)?;
         // Expecting projection
         // projection -> ...
@@ -374,7 +387,13 @@ impl Select {
                         }));
                     }
                     _ => {
-                        return Err(QueryPlannerError::InvalidPlan);
+                        return Err(SbroadError::Invalid(
+                            Entity::Plan,
+                            Some(
+                                "current node must be InnerJoin, ScanSubQuery or ScanRelation"
+                                    .into(),
+                            ),
+                        ));
                     }
                 }
             }
@@ -414,9 +433,17 @@ impl Select {
                     return Ok(Some(select));
                 }
             }
-            _ => return Err(QueryPlannerError::InvalidPlan),
+            _ => {
+                return Err(SbroadError::Invalid(
+                    Entity::Plan,
+                    Some("current node must be Selection, ScanRelation, or InnerJoin".into()),
+                ))
+            }
         }
-        Err(QueryPlannerError::InvalidPlan)
+        Err(SbroadError::Invalid(
+            Entity::Plan,
+            Some("invalid combination of the select command".into()),
+        ))
     }
 }
 
@@ -438,7 +465,7 @@ impl<'p> SyntaxPlan<'p> {
     /// # Errors
     /// - Failed to translate an IR plan node to a syntax node.
     #[allow(clippy::too_many_lines)]
-    pub fn add_plan_node(&mut self, id: usize) -> Result<usize, QueryPlannerError> {
+    pub fn add_plan_node(&mut self, id: usize) -> Result<usize, SbroadError> {
         let ir_plan = self.plan.get_ir_plan();
         let node = ir_plan.get_node(id)?;
         match node {
@@ -456,12 +483,13 @@ impl<'p> SyntaxPlan<'p> {
                     let row = ir_plan.get_expression_node(*output)?;
                     let aliases: &[usize] = row.get_row_list()?;
 
-                    let get_col_sn = |col_pos: &usize| -> Result<SyntaxNode, QueryPlannerError> {
+                    let get_col_sn = |col_pos: &usize| -> Result<SyntaxNode, SbroadError> {
                         let alias_id = *aliases.get(*col_pos).ok_or_else(|| {
-                            QueryPlannerError::CustomError(format!(
-                                "Failed to get insert output column at position {}",
-                                col_pos,
-                            ))
+                            SbroadError::FailedTo(
+                                Action::Get,
+                                None,
+                                format!("insert output column at position {col_pos}"),
+                            )
                         })?;
                         let alias = ir_plan.get_expression_node(alias_id)?;
                         if let Expression::Alias { child, .. } = alias {
@@ -469,13 +497,15 @@ impl<'p> SyntaxPlan<'p> {
                             if let Expression::Reference { .. } = col_ref {
                                 Ok(SyntaxNode::new_pointer(*child, None, vec![]))
                             } else {
-                                Err(QueryPlannerError::CustomError(
-                                    "Expected a reference expression".into(),
+                                Err(SbroadError::Invalid(
+                                    Entity::Expression,
+                                    Some("expected a reference expression".into()),
                                 ))
                             }
                         } else {
-                            Err(QueryPlannerError::CustomError(
-                                "Expected an alias expression".into(),
+                            Err(SbroadError::Invalid(
+                                Entity::Expression,
+                                Some("expected an alias expression".into()),
                             ))
                         }
                     };
@@ -492,8 +522,9 @@ impl<'p> SyntaxPlan<'p> {
                     }
 
                     if children.is_empty() {
-                        return Err(QueryPlannerError::CustomError(
-                            "Insert node has no children".into(),
+                        return Err(SbroadError::Invalid(
+                            Entity::Node,
+                            Some("insert node has no children".into()),
                         ));
                     }
                     nodes.reserve(children.len());
@@ -509,13 +540,12 @@ impl<'p> SyntaxPlan<'p> {
                     ..
                 } => {
                     let left_id = *children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Inner join doesn't have a left child.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Inner Join has no children.".into())
                     })?;
                     let right_id = *children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Inner join doesn't have a right child.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "that is Inner Join right child.".into(),
                         )
                     })?;
                     let condition_id = match self.snapshot {
@@ -541,7 +571,7 @@ impl<'p> SyntaxPlan<'p> {
                     children, output, ..
                 } => {
                     let left_id = *children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError("Projection has no children.".into())
+                        SbroadError::UnexpectedNumberOfValues("Projection has no children.".into())
                     })?;
                     // We don't need the row node itself, only its children.
                     // Otherwise we'll produce redundant parentheses between
@@ -564,14 +594,14 @@ impl<'p> SyntaxPlan<'p> {
                             return Ok(self.nodes.push_syntax_node(sn));
                         }
                     }
-                    Err(QueryPlannerError::InvalidPlan)
+                    Err(SbroadError::Invalid(Entity::Node, None))
                 }
                 Relational::ScanSubQuery { .. } => self.nodes.add_sq(rel, id),
                 Relational::Selection {
                     children, filter, ..
                 } => {
                     let left_id = *children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError("Selection has no children.".into())
+                        SbroadError::UnexpectedNumberOfValues("Selection has no children.".into())
                     })?;
                     let filter_id = match self.snapshot {
                         Snapshot::Latest => *filter,
@@ -589,13 +619,14 @@ impl<'p> SyntaxPlan<'p> {
                 }
                 Relational::Except { children, .. } | Relational::UnionAll { children, .. } => {
                     let left_id = *children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Union/except doesn't have a left child.".into(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "Union/Except has no children.".into(),
                         )
                     })?;
                     let right_id = *children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Union/except doesn't have a right child.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "that is Union/Except right child.".into(),
                         )
                     })?;
                     let sn = SyntaxNode::new_pointer(
@@ -761,7 +792,7 @@ impl<'p> SyntaxPlan<'p> {
                         let sn = SyntaxNode::new_pointer(id, None, nodes);
                         return Ok(self.nodes.push_syntax_node(sn));
                     }
-                    Err(QueryPlannerError::InvalidRow)
+                    Err(SbroadError::Invalid(Entity::Expression, None))
                 }
                 Expression::Bool {
                     left, right, op, ..
@@ -819,7 +850,7 @@ impl<'p> SyntaxPlan<'p> {
     ///
     /// # Errors
     /// - plan node is invalid
-    pub fn get_plan_node(&self, data: &SyntaxData) -> Result<Option<&Node>, QueryPlannerError> {
+    pub fn get_plan_node(&self, data: &SyntaxData) -> Result<Option<&Node>, SbroadError> {
         if let SyntaxData::PlanId(id) = data {
             Ok(Some(self.plan.get_ir_plan().get_node(*id)?))
         } else {
@@ -832,16 +863,20 @@ impl<'p> SyntaxPlan<'p> {
     /// # Errors
     /// - plan node is invalid
     /// - syntax tree node doesn't have a plan node
-    pub fn plan_node_or_err(&self, data: &SyntaxData) -> Result<&Node, QueryPlannerError> {
-        self.get_plan_node(data)?
-            .ok_or_else(|| QueryPlannerError::CustomError("Plan node is not found.".into()))
+    pub fn plan_node_or_err(&self, data: &SyntaxData) -> Result<&Node, SbroadError> {
+        self.get_plan_node(data)?.ok_or_else(|| {
+            SbroadError::Invalid(
+                Entity::SyntaxPlan,
+                Some("Plan node is not found in syntax tree".into()),
+            )
+        })
     }
 
     /// Set top of the tree.
     ///
     /// # Errors
     /// - top is invalid node
-    pub fn set_top(&mut self, top: usize) -> Result<(), QueryPlannerError> {
+    pub fn set_top(&mut self, top: usize) -> Result<(), SbroadError> {
         self.nodes.get_syntax_node(top)?;
         self.top = Some(top);
         Ok(())
@@ -852,13 +887,14 @@ impl<'p> SyntaxPlan<'p> {
     /// # Errors
     /// - top is not set
     /// - top is not a valid node
-    pub fn get_top(&self) -> Result<usize, QueryPlannerError> {
+    pub fn get_top(&self) -> Result<usize, SbroadError> {
         if let Some(top) = self.top {
             self.nodes.get_syntax_node(top)?;
             Ok(top)
         } else {
-            Err(QueryPlannerError::CustomError(
-                "Syntax tree has an invalid top.".into(),
+            Err(SbroadError::Invalid(
+                Entity::SyntaxPlan,
+                Some("Syntax tree has an invalid top.".into()),
             ))
         }
     }
@@ -868,7 +904,7 @@ impl<'p> SyntaxPlan<'p> {
     ///
     /// # Errors
     /// - got unexpected nodes under projection
-    fn gather_selects(&self) -> Result<Option<Vec<Select>>, QueryPlannerError> {
+    fn gather_selects(&self) -> Result<Option<Vec<Select>>, SbroadError> {
         let mut selects: Vec<Select> = Vec::new();
         let top = self.get_top()?;
         for (pos, node) in self.nodes.arena.iter().enumerate() {
@@ -903,7 +939,7 @@ impl<'p> SyntaxPlan<'p> {
     ///
     /// # Errors
     /// - got unexpected nodes under some projection
-    fn move_proj_under_scan(&mut self) -> Result<(), QueryPlannerError> {
+    fn move_proj_under_scan(&mut self) -> Result<(), SbroadError> {
         let selects = self.gather_selects()?;
         if let Some(selects) = selects {
             for select in &selects {
@@ -933,7 +969,7 @@ impl<'p> SyntaxPlan<'p> {
         plan: &'p ExecutionPlan,
         top: usize,
         snapshot: Snapshot,
-    ) -> Result<Self, QueryPlannerError> {
+    ) -> Result<Self, SbroadError> {
         let mut sp = SyntaxPlan::empty(plan);
         sp.snapshot = snapshot.clone();
         let ir_plan = plan.get_ir_plan();
@@ -972,7 +1008,7 @@ impl<'p> SyntaxPlan<'p> {
     ///
     /// # Errors
     /// - select nodes (parent, scan, projection, selection) are invalid
-    fn reorder(&mut self, select: &Select) -> Result<(), QueryPlannerError> {
+    fn reorder(&mut self, select: &Select) -> Result<(), SbroadError> {
         // Move projection under scan.
         let mut proj = self.nodes.get_mut_syntax_node(select.proj)?;
         proj.left = None;
@@ -1022,14 +1058,19 @@ impl<'p> SyntaxPlan<'p> {
                         }
                     }
                     if !found {
-                        return Err(QueryPlannerError::CustomError(
-                            "Parent node doesn't contain projection in its right children".into(),
+                        return Err(SbroadError::Invalid(
+                            Entity::SyntaxNode,
+                            Some(
+                                "Parent node doesn't contain projection in its right children"
+                                    .into(),
+                            ),
                         ));
                     }
                 }
                 None => {
-                    return Err(QueryPlannerError::CustomError(
-                        "Selection structure is in inconsistent state.".into(),
+                    return Err(SbroadError::Invalid(
+                        Entity::SyntaxNode,
+                        Some("Selection structure is in inconsistent state.".into()),
                     ))
                 }
             }
@@ -1056,16 +1097,14 @@ impl OrderedSyntaxNodes {
     ///
     /// # Errors
     /// - internal error (positions point to invalid nodes in the arena)
-    pub fn to_syntax_data(&self) -> Result<Vec<&SyntaxData>, QueryPlannerError> {
+    pub fn to_syntax_data(&self) -> Result<Vec<&SyntaxData>, SbroadError> {
         let mut result: Vec<&SyntaxData> = Vec::with_capacity(self.positions.len());
         for id in &self.positions {
             result.push(
                 &self
                     .arena
                     .get(*id)
-                    .ok_or_else(|| {
-                        QueryPlannerError::CustomError(format!("Invalid syntax node id: {id}"))
-                    })?
+                    .ok_or_else(|| SbroadError::NotFound(Entity::SyntaxNode, format!("(id {id})")))?
                     .data,
             );
         }
@@ -1074,7 +1113,7 @@ impl OrderedSyntaxNodes {
 }
 
 impl TryFrom<SyntaxPlan<'_>> for OrderedSyntaxNodes {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     #[otm_child_span("syntax.ordered")]
     fn try_from(mut sp: SyntaxPlan) -> Result<Self, Self::Error> {
diff --git a/sbroad-core/src/errors.rs b/sbroad-core/src/errors.rs
index 2843dba55c..a079592054 100644
--- a/sbroad-core/src/errors.rs
+++ b/sbroad-core/src/errors.rs
@@ -1,140 +1,234 @@
-use std::fmt;
-
 use serde::Serialize;
+use std::fmt;
 
-const BUCKET_ID_ERROR: &str = "field doesn't contains sharding key value";
-const DUPLICATE_COLUMN_ERROR: &str = "duplicate column";
 const DO_SKIP: &str = "do skip";
-const EMPTY_PLAN_RELATION: &str = "empty plan relations";
-const EMPTY_RESULT: &str = "empty result";
-const INCORRECT_BUCKET_ID_ERROR: &str = "incorrect bucket id";
-const INVALID_AST: &str = "invalid AST";
-const INVALID_AST_CONDITION_NODE: &str = "Invalid selection condition part";
-const INVALID_AST_SCAN_NODE: &str = "Invalid scan node";
-const INVALID_AST_SELECTION_NODE: &str = "Selection node not found";
-const INVALID_AST_SUBQUERY_NODE: &str = "Invalid subquery node";
-const INVALID_AST_TOP_NODE: &str = "Top node not found";
-const INVALID_BOOL_ERROR: &str = "invalid boolean";
-const INVALID_CONSTANT: &str = "invalid constant";
-const INVALID_COLUMN_NAME: &str = "invalid column name";
-const INVALID_CLUSTER_SCHEMA: &str = "cluster schema is invalid";
-const INVALID_INPUT: &str = "invalid input";
-const INVALID_NAME_ERROR: &str = "invalid name";
-const INVALID_NODE: &str = "invalid node";
-const INVALID_NUMBER_ERROR: &str = "invalid number";
-const INVALID_PLAN_ERROR: &str = "invalid plan";
-const INVALID_REFERENCE: &str = "invalid reference";
-const INVALID_RELATION_ERROR: &str = "invalid relation";
-const INVALID_ROW_ERROR: &str = "invalid row";
-const INVALID_SCHEMA_SPACES: &str = "not found spaces in schema";
-const INVALID_SHARDING_KEY_ERROR: &str = "invalid sharding key";
-const INVALID_SPACE_NAME: &str = "invalid space name";
-const INVALID_SUBQUERY: &str = "invalid sub-query";
-const NOT_EQUAL_ROWS: &str = "not equal rows";
-const QUERY_NOT_IMPLEMENTED: &str = "query wasn't implemented";
-const REDUNDANT_TRANSFORMATION: &str = "redundant transformation";
-const REQUIRE_MOTION: &str = "require motion";
-const SERIALIZATION_ERROR: &str = "serialization";
-const SIMPLE_QUERY_ERROR: &str = "query doesn't simple";
-const SIMPLE_UNION_QUERY_ERROR: &str = "query doesn't simple union";
-const SPACE_NOT_FOUND: &str = "space not found";
-const SPACE_FORMAT_NOT_FOUND: &str = "space format not found";
-const UNINITIALIZED_DISTRIBUTION: &str = "uninitialized distribution";
-const UNSUPPORTED_TYPE_IR_VALUE: &str = "unsupported type ir value";
-const VALUE_OUT_OF_RANGE_ERROR: &str = "value out of range";
 
+/// Reason or object of errors.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum Entity {
+    /// corresponding to enum Args
+    Args,
+    /// corresponding to struct AbstractSyntaxTree
+    AST,
+    /// corresponding to struct Buckets
+    Buckets,
+    /// general variant for cache
+    Cache,
+    /// corresponding to struct Chain
+    Chain,
+    /// cartridge cluster schema
+    ClusterSchema,
+    /// general variant
+    Column,
+    /// corresponds to enum Distribution
+    Distribution,
+    /// tarantool distribution key
+    DistributionKey,
+    /// corresponds to enum Expression
+    Expression,
+    /// corresponds to metadata field of struct ProducerResult
+    Metadata,
+    /// corresponds to enum MotionPolicy
+    Motion,
+    /// tarantool msgpack
+    MsgPack,
+    /// general variant for Name of some object
+    Name,
+    /// variant for node of tree
+    Node,
+    /// SQL operator
+    Operator,
+    /// corresponds to struct PatternWithParams
+    PatternWithParams,
+    /// corresponds to struct Plan
+    Plan,
+    /// corresponds to struct ProducerResult
+    ProducerResult,
+    /// SQL query
+    Query,
+    /// corresponds to enum Relational
+    Relational,
+    /// corresponds to struct RequiredData
+    RequiredData,
+    /// parser rule
+    Rule,
+    /// corresponds to struct RouterRuntime
+    Runtime,
+    /// sharding key of tarantool space
+    ShardingKey,
+    /// tarantool space
+    Space,
+    /// corresponds to Function structs
+    SQLFunction,
+    /// corresponds to struct Statement
+    Statement,
+    /// SQL sub-query
+    SubQuery,
+    /// sub-tree of the Plan
+    SubTree,
+    /// corresponds to sctruct SyntaxNode
+    SyntaxNode,
+    /// corresponds to struct SyntaxNodes
+    SyntaxNodes,
+    /// corresponds to struct SyntaxPlan
+    SyntaxPlan,
+    /// corresponds to struct Table
+    Table,
+    /// corresponds to struct Target
+    Target,
+    /// general variant for tuple
+    Tuple,
+    /// general variant for type of some object
+    Type,
+    /// general variant for value of some object
+    Value,
+    /// corresponds to struct VirtualTable
+    VirtualTable,
+}
+
+impl fmt::Display for Entity {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let p = match self {
+            Entity::Args => "args".to_string(),
+            Entity::AST => "AST".to_string(),
+            Entity::Buckets => "buckets".to_string(),
+            Entity::Cache => "cache".to_string(),
+            Entity::Chain => "chain".to_string(),
+            Entity::ClusterSchema => "cluster schema".to_string(),
+            Entity::Column => "column".to_string(),
+            Entity::Distribution => "distribution".to_string(),
+            Entity::DistributionKey => "distribution key".to_string(),
+            Entity::Expression => "expression".to_string(),
+            Entity::Metadata => "metadata".to_string(),
+            Entity::Motion => "motion".to_string(),
+            Entity::MsgPack => "msgpack".to_string(),
+            Entity::Name => "name".to_string(),
+            Entity::Node => "node".to_string(),
+            Entity::Operator => "operator".to_string(),
+            Entity::PatternWithParams => "pattern with parameters".to_string(),
+            Entity::Plan => "plan".to_string(),
+            Entity::ProducerResult => "producer result".to_string(),
+            Entity::Query => "query".to_string(),
+            Entity::Relational => "relational".to_string(),
+            Entity::RequiredData => "required data".to_string(),
+            Entity::Rule => "rule".to_string(),
+            Entity::Runtime => "runtime".to_string(),
+            Entity::ShardingKey => "sharding key".to_string(),
+            Entity::Space => "space".to_string(),
+            Entity::SQLFunction => "SQL function".to_string(),
+            Entity::Statement => "statement".to_string(),
+            Entity::SubQuery => "sub-query plan subtree".to_string(),
+            Entity::SubTree => "execution plan subtree".to_string(),
+            Entity::SyntaxNode => "syntax node".to_string(),
+            Entity::SyntaxNodes => "syntax nodes".to_string(),
+            Entity::SyntaxPlan => "syntax plan".to_string(),
+            Entity::Table => "table".to_string(),
+            Entity::Target => "target".to_string(),
+            Entity::Tuple => "tuple".to_string(),
+            Entity::Type => "type".to_string(),
+            Entity::Value => "value".to_string(),
+            Entity::VirtualTable => "virtual table".to_string(),
+        };
+        write!(f, "{p}")
+    }
+}
+
+/// Action that failed
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum Action {
+    Add,
+    Borrow,
+    Build,
+    Clear,
+    Create,
+    Decode,
+    Deserialize,
+    Get,
+    Insert,
+    Put,
+    Retrieve,
+    Serialize,
+}
+
+impl fmt::Display for Action {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let p = match self {
+            Action::Add => "add".to_string(),
+            Action::Borrow => "borrow".to_string(),
+            Action::Build => "build".to_string(),
+            Action::Clear => "clear".to_string(),
+            Action::Create => "create".to_string(),
+            Action::Decode => "decode".to_string(),
+            Action::Deserialize => "deserialize".to_string(),
+            Action::Get => "get".to_string(),
+            Action::Insert => "insert".to_string(),
+            Action::Put => "put".to_string(),
+            Action::Retrieve => "retrieve".to_string(),
+            Action::Serialize => "serialize".to_string(),
+        };
+        write!(f, "{p}")
+    }
+}
+
+/// Types of error
 #[derive(Clone, Debug, PartialEq, Eq, Serialize)]
-pub enum QueryPlannerError {
-    BucketIdError,
-    CustomError(String),
+pub enum SbroadError {
+    /// DoSkip is a special case of an error - nothing bad had happened, the target node doesn't contain
+    /// anything interesting for us, skip it without any serious error.
     DoSkip,
-    DuplicateColumn,
-    EmptyPlanRelations,
-    EmptyResult,
-    IncorrectBucketIdError,
-    InvalidAst,
-    InvalidAstConditionNode,
-    InvalidAstScanNode,
-    InvalidAstSelectionNode,
-    InvalidAstSubQueryNode,
-    InvalidAstTopNode,
-    InvalidBool,
-    InvalidConstant,
-    InvalidColumnName,
-    InvalidClusterSchema,
-    InvalidInput,
-    InvalidName,
-    InvalidNode,
-    InvalidNumber,
-    InvalidPlan,
-    InvalidReference,
-    InvalidRelation,
-    InvalidRow,
-    InvalidSchemaSpaces,
-    InvalidShardingKey,
-    InvalidSpaceName,
-    InvalidSubQuery,
+    /// Some value that is considered to be unique is duplicated.
+    /// Second param represents description.
+    DuplicatedValue(String),
+    /// Process of Action variant failed.
+    /// Second param represents object of action.
+    /// Third param represents reason of fail.
+    FailedTo(Action, Option<Entity>, String),
+    /// Object is invalid.
+    /// Second param represents description and can be empty (None).
+    Invalid(Entity, Option<String>),
     LuaError(String),
-    NotEqualRows,
-    QueryNotImplemented,
-    RequireMotion,
-    RedundantTransformation,
-    Serialization,
-    SimpleQueryError,
-    SimpleUnionQueryError,
-    SpaceFormatNotFound,
-    SpaceNotFound,
-    UninitializedDistribution,
-    ValueOutOfRange,
-    UnsupportedValueType,
+    /// Object not found.
+    /// Second param represents description or name that let to identify object.
+    NotFound(Entity, String),
+    /// Object is not implemented yet.
+    /// Second param represents description or name.
+    NotImplemented(Entity, String),
+    /// Error raised by object parsing.
+    /// Second param represents error description.
+    ParsingError(Entity, String),
+    /// Unexpected number of values (list length etc.).
+    /// Second param is information what was expected and what got.
+    UnexpectedNumberOfValues(String),
+    /// Object is not supported.
+    /// Second param represents description or name that let to identify object.
+    /// and can be empty (None).
+    Unsupported(Entity, Option<String>),
 }
 
-impl fmt::Display for QueryPlannerError {
+impl fmt::Display for SbroadError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let p = match self {
-            QueryPlannerError::CustomError(s) => s.as_str(),
-            QueryPlannerError::BucketIdError => BUCKET_ID_ERROR,
-            QueryPlannerError::DoSkip => DO_SKIP,
-            QueryPlannerError::DuplicateColumn => DUPLICATE_COLUMN_ERROR,
-            QueryPlannerError::EmptyPlanRelations => EMPTY_PLAN_RELATION,
-            QueryPlannerError::EmptyResult => EMPTY_RESULT,
-            QueryPlannerError::IncorrectBucketIdError => INCORRECT_BUCKET_ID_ERROR,
-            QueryPlannerError::InvalidAst => INVALID_AST,
-            QueryPlannerError::InvalidAstConditionNode => INVALID_AST_CONDITION_NODE,
-            QueryPlannerError::InvalidAstScanNode => INVALID_AST_SCAN_NODE,
-            QueryPlannerError::InvalidAstSelectionNode => INVALID_AST_SELECTION_NODE,
-            QueryPlannerError::InvalidAstSubQueryNode => INVALID_AST_SUBQUERY_NODE,
-            QueryPlannerError::InvalidAstTopNode => INVALID_AST_TOP_NODE,
-            QueryPlannerError::InvalidBool => INVALID_BOOL_ERROR,
-            QueryPlannerError::InvalidConstant => INVALID_CONSTANT,
-            QueryPlannerError::InvalidColumnName => INVALID_COLUMN_NAME,
-            QueryPlannerError::InvalidClusterSchema => INVALID_CLUSTER_SCHEMA,
-            QueryPlannerError::InvalidInput => INVALID_INPUT,
-            QueryPlannerError::InvalidName => INVALID_NAME_ERROR,
-            QueryPlannerError::InvalidNode => INVALID_NODE,
-            QueryPlannerError::InvalidNumber => INVALID_NUMBER_ERROR,
-            QueryPlannerError::InvalidPlan => INVALID_PLAN_ERROR,
-            QueryPlannerError::InvalidReference => INVALID_REFERENCE,
-            QueryPlannerError::InvalidRelation => INVALID_RELATION_ERROR,
-            QueryPlannerError::InvalidRow => INVALID_ROW_ERROR,
-            QueryPlannerError::InvalidSchemaSpaces => INVALID_SCHEMA_SPACES,
-            QueryPlannerError::InvalidShardingKey => INVALID_SHARDING_KEY_ERROR,
-            QueryPlannerError::InvalidSpaceName => INVALID_SPACE_NAME,
-            QueryPlannerError::InvalidSubQuery => INVALID_SUBQUERY,
-            QueryPlannerError::LuaError(e) => e.as_str(),
-            QueryPlannerError::NotEqualRows => NOT_EQUAL_ROWS,
-            QueryPlannerError::QueryNotImplemented => QUERY_NOT_IMPLEMENTED,
-            QueryPlannerError::RedundantTransformation => REDUNDANT_TRANSFORMATION,
-            QueryPlannerError::RequireMotion => REQUIRE_MOTION,
-            QueryPlannerError::Serialization => SERIALIZATION_ERROR,
-            QueryPlannerError::SimpleQueryError => SIMPLE_QUERY_ERROR,
-            QueryPlannerError::SimpleUnionQueryError => SIMPLE_UNION_QUERY_ERROR,
-            QueryPlannerError::SpaceFormatNotFound => SPACE_FORMAT_NOT_FOUND,
-            QueryPlannerError::SpaceNotFound => SPACE_NOT_FOUND,
-            QueryPlannerError::UninitializedDistribution => UNINITIALIZED_DISTRIBUTION,
-            QueryPlannerError::ValueOutOfRange => VALUE_OUT_OF_RANGE_ERROR,
-            QueryPlannerError::UnsupportedValueType => UNSUPPORTED_TYPE_IR_VALUE,
+            SbroadError::DoSkip => DO_SKIP.to_string(),
+            SbroadError::DuplicatedValue(s) => format!("duplicated value: {s}"),
+            SbroadError::FailedTo(a, e, s) => match e {
+                Some(entity) => format!("failed to {a} {entity}: {s}"),
+                None => format!("failed to {a} {s}"),
+            },
+            SbroadError::Invalid(e, s) => match s {
+                Some(msg) => format!("invalid {e}: {msg}"),
+                None => format!("invalid {e}"),
+            },
+            SbroadError::NotFound(e, s) => format!("{e} {s} not found"),
+            SbroadError::NotImplemented(e, s) => format!("{e} {s} not implemented"),
+            SbroadError::ParsingError(e, s) => format!("{e} parsing error: {s}"),
+            SbroadError::Unsupported(e, s) => match s {
+                Some(msg) => format!("unsupported {e}: {msg}"),
+                None => format!("unsupported {e}"),
+            },
+            SbroadError::UnexpectedNumberOfValues(s) => format!("unexpected number of values: {s}"),
+            SbroadError::LuaError(e) => e.clone(),
         };
+
         write!(f, "{p}")
     }
 }
diff --git a/sbroad-core/src/executor.rs b/sbroad-core/src/executor.rs
index 9606c91495..9668a19fb4 100644
--- a/sbroad-core/src/executor.rs
+++ b/sbroad-core/src/executor.rs
@@ -27,7 +27,7 @@ use std::any::Any;
 use std::collections::{hash_map::Entry, HashMap};
 use std::rc::Rc;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::bucket::Buckets;
 use crate::executor::engine::Coordinator;
 use crate::executor::engine::CoordinatorMetadata;
@@ -54,7 +54,7 @@ pub mod vtable;
 
 impl Plan {
     /// Apply optimization rules to the plan.
-    pub(crate) fn optimize(&mut self) -> Result<(), QueryPlannerError> {
+    pub(crate) fn optimize(&mut self) -> Result<(), SbroadError> {
         self.replace_in_operator()?;
         self.split_columns()?;
         self.set_dnf()?;
@@ -93,7 +93,7 @@ where
     /// - Failed to build IR plan.
     /// - Failed to apply optimizing transformations to IR plan.
     #[otm_child_span("query.new")]
-    pub fn new(coordinator: &'a C, sql: &str, params: Vec<Value>) -> Result<Self, QueryPlannerError>
+    pub fn new(coordinator: &'a C, sql: &str, params: Vec<Value>) -> Result<Self, SbroadError>
     where
         C::Configuration: CoordinatorMetadata,
         C::Cache: Cache<String, Plan>,
@@ -104,7 +104,7 @@ where
 
         let mut plan = Plan::new();
         let mut cache = ir_cache.try_borrow_mut().map_err(|e| {
-            QueryPlannerError::CustomError(format!("Failed to create a new query: {e:?}"))
+            SbroadError::FailedTo(Action::Create, Some(Entity::Query), format!("{e:?}"))
         })?;
         if let Some(cached_plan) = cache.get(&key)? {
             plan = cached_plan.clone();
@@ -151,7 +151,7 @@ where
     /// - Failed to materialize motion result and build a virtual table.
     /// - Failed to get plan top.
     #[otm_child_span("query.dispatch")]
-    pub fn dispatch(&mut self) -> Result<Box<dyn Any>, QueryPlannerError> {
+    pub fn dispatch(&mut self) -> Result<Box<dyn Any>, SbroadError> {
         if self.is_explain() {
             return self.coordinator.explain_format(self.to_explain()?);
         }
@@ -206,7 +206,7 @@ where
         &mut self,
         motion_id: usize,
         mut vtable: VirtualTable,
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         let (policy, generation) = if let Relational::Motion {
             policy, generation, ..
         } = self
@@ -216,8 +216,9 @@ where
         {
             (policy.clone(), generation.clone())
         } else {
-            return Err(QueryPlannerError::CustomError(
-                "Invalid motion node".to_string(),
+            return Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("invalid motion node".to_string()),
             ));
         };
         if let MotionPolicy::Segment(shard_key) = policy {
@@ -249,7 +250,7 @@ where
         vtable: &mut VirtualTable,
         sharding_key: &MotionKey,
         generation: &DataGeneration,
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         vtable.set_motion_key(sharding_key);
 
         let mut index: HashMap<u64, Vec<usize>> = HashMap::new();
@@ -259,10 +260,13 @@ where
                 match target {
                     Target::Reference(col_idx) => {
                         let part = tuple.get(*col_idx).ok_or_else(|| {
-                            QueryPlannerError::CustomError(format!(
-                                "Failed to find a distribution key column {} in the tuple {:?}.",
+                            SbroadError::NotFound(
+                                Entity::DistributionKey,
+                                format!(
+                                "failed to find a distribution key column {} in the tuple {:?}.",
                                 pos, tuple
-                            ))
+                            ),
+                            )
                         })?;
                         shard_key_tuple.push(part);
                     }
@@ -309,7 +313,7 @@ where
     ///
     /// # Errors
     /// - Failed to build explain
-    pub fn to_explain(&self) -> Result<String, QueryPlannerError> {
+    pub fn to_explain(&self) -> Result<String, SbroadError> {
         self.exec_plan.get_ir_plan().as_explain()
     }
 
diff --git a/sbroad-core/src/executor/bucket.rs b/sbroad-core/src/executor/bucket.rs
index 6eb7e5e9e8..45ddb92f4a 100644
--- a/sbroad-core/src/executor/bucket.rs
+++ b/sbroad-core/src/executor/bucket.rs
@@ -2,7 +2,7 @@ use std::collections::HashSet;
 
 use traversal::DftPost;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::engine::Coordinator;
 use crate::executor::Query;
 use crate::ir::distribution::Distribution;
@@ -74,7 +74,7 @@ impl<'a, T> Query<'a, T>
 where
     T: Coordinator,
 {
-    fn get_buckets_from_expr(&self, expr_id: usize) -> Result<Buckets, QueryPlannerError> {
+    fn get_buckets_from_expr(&self, expr_id: usize) -> Result<Buckets, SbroadError> {
         let mut buckets: Vec<Buckets> = Vec::new();
         let ir_plan = self.exec_plan.get_ir_plan();
         let expr = ir_plan.get_expression_node(expr_id)?;
@@ -89,19 +89,23 @@ where
             for (left_id, right_id) in pairs {
                 let left_expr = ir_plan.get_expression_node(left_id)?;
                 if !left_expr.is_row() {
-                    return Err(QueryPlannerError::CustomError(format!(
-                        "Left side of equality expression is not a row: {:?}",
-                        left_expr
-                    )));
+                    return Err(SbroadError::Invalid(
+                        Entity::Expression,
+                        Some(format!(
+                            "left side of equality expression is not a row: {left_expr:?}"
+                        )),
+                    ));
                 }
                 let right_expr = ir_plan.get_expression_node(right_id)?;
                 let right_columns = if let Expression::Row { list, .. } = right_expr {
                     list.clone()
                 } else {
-                    return Err(QueryPlannerError::CustomError(format!(
-                        "Right side of equality expression is not a row: {:?}",
-                        right_expr
-                    )));
+                    return Err(SbroadError::Invalid(
+                        Entity::Expression,
+                        Some(format!(
+                            "right side of equality expression is not a row: {right_expr:?}"
+                        )),
+                    ));
                 };
 
                 // Get the distribution of the left row.
@@ -128,10 +132,10 @@ where
                         for position in &key.positions {
                             let right_column_id =
                                 *right_columns.get(*position).ok_or_else(|| {
-                                    QueryPlannerError::CustomError(format!(
-                                        "Right row does not have column at position {}",
-                                        position
-                                    ))
+                                    SbroadError::NotFound(
+                                        Entity::Column,
+                                        format!("at position {} for right row", position),
+                                    )
                                 })?;
                             let right_column_expr = ir_plan.get_expression_node(right_column_id)?;
                             if let Expression::Constant { .. } = right_column_expr {
@@ -162,7 +166,7 @@ where
         }
     }
 
-    fn get_expression_tree_buckets(&self, expr_id: usize) -> Result<Buckets, QueryPlannerError> {
+    fn get_expression_tree_buckets(&self, expr_id: usize) -> Result<Buckets, SbroadError> {
         let ir_plan = self.exec_plan.get_ir_plan();
         let chains = ir_plan.get_dnf_chains(expr_id)?;
         let mut result: Vec<Buckets> = Vec::new();
@@ -196,7 +200,7 @@ where
     /// - Relational nodes contain invalid children.
     #[allow(clippy::too_many_lines)]
     #[otm_child_span("query.bucket.discovery")]
-    pub fn bucket_discovery(&mut self, top_id: usize) -> Result<Buckets, QueryPlannerError> {
+    pub fn bucket_discovery(&mut self, top_id: usize) -> Result<Buckets, SbroadError> {
         let ir_plan = self.exec_plan.get_ir_plan();
         // We use a `subtree_iter()` because we need DNF version of the filter/condition
         // expressions to determine buckets.
@@ -236,8 +240,9 @@ where
                             .insert(*output, Buckets::new_filtered(buckets));
                     }
                     MotionPolicy::Local => {
-                        return Err(QueryPlannerError::CustomError(
-                            "Local motion policy should never appear in the plan".to_string(),
+                        return Err(SbroadError::Invalid(
+                            Entity::Motion,
+                            Some("local motion policy should never appear in the plan".to_string()),
                         ));
                     }
                 },
@@ -251,7 +256,7 @@ where
                     children, output, ..
                 } => {
                     let child_id = children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
+                        SbroadError::UnexpectedNumberOfValues(
                             "Current node should have exactly one child".to_string(),
                         )
                     })?;
@@ -260,9 +265,10 @@ where
                         .bucket_map
                         .get(&child_rel.output())
                         .ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Failed to retrieve buckets of the child from the bucket map."
-                                    .to_string(),
+                            SbroadError::FailedTo(
+                                Action::Retrieve,
+                                Some(Entity::Buckets),
+                                "of the child from the bucket map.".to_string(),
                             )
                         })?
                         .clone();
@@ -281,16 +287,21 @@ where
                         // child's buckets here.
                         let first_rel =
                             self.exec_plan.get_ir_plan().get_relation_node(*first_id)?;
-                        let first_buckets = self.bucket_map.get(&first_rel.output()).ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Failed to retrieve buckets of the first except child from the bucket map."
-                                    .to_string(),
-                            )
-                        })?.clone();
+                        let first_buckets = self
+                            .bucket_map
+                            .get(&first_rel.output())
+                            .ok_or_else(|| {
+                                SbroadError::FailedTo(
+                                    Action::Retrieve,
+                                    Some(Entity::Buckets),
+                                    "of the first except child from the bucket map.".to_string(),
+                                )
+                            })?
+                            .clone();
                         self.bucket_map.insert(*output, first_buckets);
                     } else {
-                        return Err(QueryPlannerError::CustomError(
-                            "Current node should have exactly two children".to_string(),
+                        return Err(SbroadError::UnexpectedNumberOfValues(
+                            "current node should have exactly two children".to_string(),
                         ));
                     }
                 }
@@ -304,23 +315,28 @@ where
                             self.exec_plan.get_ir_plan().get_relation_node(*first_id)?;
                         let second_rel =
                             self.exec_plan.get_ir_plan().get_relation_node(*second_id)?;
-                        let first_buckets = self.bucket_map.get(&first_rel.output()).ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Failed to retrieve buckets of the first union all child from the bucket map."
-                                    .to_string(),
-                            )
-                        })?;
-                        let second_buckets = self.bucket_map.get(&second_rel.output()).ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Failed to retrieve buckets of the second union all child from the bucket map."
-                                    .to_string(),
-                            )
-                        })?;
+                        let first_buckets =
+                            self.bucket_map.get(&first_rel.output()).ok_or_else(|| {
+                                SbroadError::FailedTo(
+                                    Action::Retrieve,
+                                    Some(Entity::Buckets),
+                                    "of the first union all child from the bucket map.".to_string(),
+                                )
+                            })?;
+                        let second_buckets =
+                            self.bucket_map.get(&second_rel.output()).ok_or_else(|| {
+                                SbroadError::FailedTo(
+                                    Action::Retrieve,
+                                    Some(Entity::Buckets),
+                                    "of the second union all child from the bucket map."
+                                        .to_string(),
+                                )
+                            })?;
                         let buckets = first_buckets.conjunct(second_buckets);
                         self.bucket_map.insert(*output, buckets);
                     } else {
-                        return Err(QueryPlannerError::CustomError(
-                            "Current node should have exactly two children".to_string(),
+                        return Err(SbroadError::UnexpectedNumberOfValues(
+                            "current node should have exactly two children".to_string(),
                         ));
                     }
                 }
@@ -333,8 +349,8 @@ where
                     // We need to get the buckets of the child node for the case
                     // when the filter returns no buckets to reduce.
                     let child_id = children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Current node should have exactly one child".to_string(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "current node should have exactly one child".to_string(),
                         )
                     })?;
                     let child_rel = self.exec_plan.get_ir_plan().get_relation_node(*child_id)?;
@@ -342,10 +358,11 @@ where
                         .bucket_map
                         .get(&child_rel.output())
                         .ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                            "Failed to retrieve buckets of the selection child from the bucket map."
-                                .to_string(),
-                        )
+                            SbroadError::FailedTo(
+                                Action::Retrieve,
+                                Some(Entity::Buckets),
+                                "of the selection child from the bucket map.".to_string(),
+                            )
                         })?
                         .clone();
                     let output_id = *output;
@@ -369,20 +386,22 @@ where
                             .bucket_map
                             .get(&inner_rel.output())
                             .ok_or_else(|| {
-                                QueryPlannerError::CustomError(
-                                "Failed to retrieve buckets of the inner child from the bucket map."
-                                    .to_string(),
-                            )
+                                SbroadError::FailedTo(
+                                    Action::Retrieve,
+                                    Some(Entity::Buckets),
+                                    "of the inner child from the bucket map.".to_string(),
+                                )
                             })?
                             .clone();
                         let outer_buckets = self
                             .bucket_map
                             .get(&outer_rel.output())
                             .ok_or_else(|| {
-                                QueryPlannerError::CustomError(
-                                "Failed to retrieve buckets of the outer child from the bucket map."
-                                .to_string(),
-                            )
+                                SbroadError::FailedTo(
+                                    Action::Retrieve,
+                                    Some(Entity::Buckets),
+                                    "of the outer child from the bucket map.".to_string(),
+                                )
                             })?
                             .clone();
                         let output_id = *output;
@@ -395,8 +414,8 @@ where
                                 .disjunct(&filter_buckets),
                         );
                     } else {
-                        return Err(QueryPlannerError::CustomError(
-                            "Current node should have at least two children".to_string(),
+                        return Err(SbroadError::UnexpectedNumberOfValues(
+                            "current node should have at least two children".to_string(),
                         ));
                     }
                 }
@@ -413,9 +432,10 @@ where
             .bucket_map
             .get(&top_rel.output())
             .ok_or_else(|| {
-                QueryPlannerError::CustomError(
-                    "Failed to retrieve buckets of the top relation from the bucket map."
-                        .to_string(),
+                SbroadError::FailedTo(
+                    Action::Retrieve,
+                    Some(Entity::Buckets),
+                    "of the top relation from the bucket map.".to_string(),
                 )
             })?
             .clone();
diff --git a/sbroad-core/src/executor/engine.rs b/sbroad-core/src/executor/engine.rs
index 59b63d391a..ec7a9fc2f8 100644
--- a/sbroad-core/src/executor/engine.rs
+++ b/sbroad-core/src/executor/engine.rs
@@ -7,7 +7,7 @@ use std::cell::RefCell;
 use std::cmp::Ordering;
 use std::collections::HashMap;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::executor::bucket::Buckets;
 use crate::executor::ir::ExecutionPlan;
 use crate::executor::vtable::VirtualTable;
@@ -24,13 +24,13 @@ pub trait CoordinatorMetadata {
     ///
     /// # Errors
     /// - Failed to get table by name from the metadata.
-    fn get_table_segment(&self, table_name: &str) -> Result<Table, QueryPlannerError>;
+    fn get_table_segment(&self, table_name: &str) -> Result<Table, SbroadError>;
 
     /// Lookup for a function in the metadata cache.
     ///
     /// # Errors
     /// - Failed to get function by name from the metadata.
-    fn get_function(&self, fn_name: &str) -> Result<&Function, QueryPlannerError>;
+    fn get_function(&self, fn_name: &str) -> Result<&Function, SbroadError>;
 
     fn get_exec_waiting_timeout(&self) -> u64;
 
@@ -41,20 +41,19 @@ pub trait CoordinatorMetadata {
     /// # Errors
     /// - Metadata does not contain space
     /// - Metadata contains incorrect sharding keys format
-    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<String>, QueryPlannerError>;
+    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<String>, SbroadError>;
 
     /// Provides vector of the sharding key column positions in a tuple or an error
     ///
     /// # Errors
     /// - Metadata does not contain space
-    fn get_sharding_positions_by_space(&self, space: &str)
-        -> Result<Vec<usize>, QueryPlannerError>;
+    fn get_sharding_positions_by_space(&self, space: &str) -> Result<Vec<usize>, SbroadError>;
 
     /// Provides amlount of table columns
     ///
     /// # Errors
     /// - Metadata does not contain space
-    fn get_fields_amount_by_space(&self, space: &str) -> Result<usize, QueryPlannerError>;
+    fn get_fields_amount_by_space(&self, space: &str) -> Result<usize, SbroadError>;
 }
 
 /// Cluster configuration.
@@ -77,7 +76,7 @@ pub trait Configuration: Sized {
     ///
     /// # Errors
     /// - Internal error.
-    fn get_config(&self) -> Result<Option<Self::Configuration>, QueryPlannerError>;
+    fn get_config(&self) -> Result<Option<Self::Configuration>, SbroadError>;
 
     /// Update cached cluster configuration.
     fn update_config(&mut self, metadata: Self::Configuration);
@@ -92,7 +91,7 @@ pub trait Coordinator: Configuration {
     ///
     /// # Errors
     /// - Invalid capacity (zero).
-    fn clear_ir_cache(&self) -> Result<(), QueryPlannerError>;
+    fn clear_ir_cache(&self) -> Result<(), SbroadError>;
 
     fn ir_cache(&self) -> &RefCell<Self::Cache>
     where
@@ -107,7 +106,7 @@ pub trait Coordinator: Configuration {
         plan: &mut ExecutionPlan,
         motion_node_id: usize,
         buckets: &Buckets,
-    ) -> Result<VirtualTable, QueryPlannerError>;
+    ) -> Result<VirtualTable, SbroadError>;
 
     /// Dispatch a sql query to the shards in cluster and get the results.
     ///
@@ -118,13 +117,13 @@ pub trait Coordinator: Configuration {
         plan: &mut ExecutionPlan,
         top_id: usize,
         buckets: &Buckets,
-    ) -> Result<Box<dyn Any>, QueryPlannerError>;
+    ) -> Result<Box<dyn Any>, SbroadError>;
 
     /// Setup output format of query explain
     ///
     /// # Errors
     /// - internal executor errors
-    fn explain_format(&self, explain: String) -> Result<Box<dyn Any>, QueryPlannerError>;
+    fn explain_format(&self, explain: String) -> Result<Box<dyn Any>, SbroadError>;
 
     /// Extract a list of the sharding keys from a map for the given space.
     ///
@@ -134,7 +133,7 @@ pub trait Coordinator: Configuration {
         &'engine self,
         space: String,
         args: &'rec HashMap<String, Value>,
-    ) -> Result<Vec<&'rec Value>, QueryPlannerError>;
+    ) -> Result<Vec<&'rec Value>, SbroadError>;
 
     /// Extract a list of the sharding key values from a tuple for the given space.
     ///
@@ -144,7 +143,7 @@ pub trait Coordinator: Configuration {
         &'engine self,
         space: String,
         args: &'rec [Value],
-    ) -> Result<Vec<&'rec Value>, QueryPlannerError>;
+    ) -> Result<Vec<&'rec Value>, SbroadError>;
 
     /// Determine shard for query execution by sharding key value
     fn determine_bucket_id(&self, s: &[&Value]) -> u64;
@@ -159,7 +158,7 @@ pub fn sharding_keys_from_tuple<'rec>(
     conf: &impl CoordinatorMetadata,
     space: &str,
     tuple: &'rec [Value],
-) -> Result<Vec<&'rec Value>, QueryPlannerError> {
+) -> Result<Vec<&'rec Value>, SbroadError> {
     let quoted_space = normalize_name_from_schema(space);
     let sharding_positions = conf.get_sharding_positions_by_space(&quoted_space)?;
     let mut sharding_tuple = Vec::with_capacity(sharding_positions.len());
@@ -168,10 +167,10 @@ pub fn sharding_keys_from_tuple<'rec>(
         // The tuple contains a "bucket_id" column.
         for position in &sharding_positions {
             let value = tuple.get(*position).ok_or_else(|| {
-                QueryPlannerError::CustomError(format!(
-                    "Missing sharding key position {:?} in the tuple {:?}",
-                    position, tuple
-                ))
+                SbroadError::NotFound(
+                    Entity::ShardingKey,
+                    format!("position {:?} in the tuple {:?}", position, tuple),
+                )
             })?;
             sharding_tuple.push(value);
         }
@@ -190,29 +189,35 @@ pub fn sharding_keys_from_tuple<'rec>(
             let corrected_pos = match position.cmp(&bucket_position) {
                 Ordering::Less => *position,
                 Ordering::Equal => {
-                    return Err(QueryPlannerError::CustomError(format!(
-                        r#"The tuple {:?} contains a "bucket_id" position {} in a sharding key {:?}"#,
-                        tuple, position, sharding_positions
-                    )))
+                    return Err(SbroadError::Invalid(
+                        Entity::Tuple,
+                        Some(format!(
+                            r#"the tuple {:?} contains a "bucket_id" position {} in a sharding key {:?}"#,
+                            tuple, position, sharding_positions
+                        )),
+                    ))
                 }
                 Ordering::Greater => *position - 1,
             };
             let value = tuple.get(corrected_pos).ok_or_else(|| {
-                QueryPlannerError::CustomError(format!(
-                    "Missing sharding key position {:?} in the tuple {:?}",
-                    corrected_pos, tuple
-                ))
+                SbroadError::NotFound(
+                    Entity::ShardingKey,
+                    format!("position {corrected_pos:?} in the tuple {tuple:?}"),
+                )
             })?;
             sharding_tuple.push(value);
         }
         Ok(sharding_tuple)
     } else {
-        Err(QueryPlannerError::CustomError(format!(
-            "The tuple {:?} was expected to have {} filed(s), got {}.",
-            tuple,
-            table_col_amount - 1,
-            tuple.len()
-        )))
+        Err(SbroadError::Invalid(
+            Entity::Tuple,
+            Some(format!(
+                "the tuple {:?} was expected to have {} filed(s), got {}.",
+                tuple,
+                table_col_amount - 1,
+                tuple.len()
+            )),
+        ))
     }
 }
 
@@ -225,7 +230,7 @@ pub fn sharding_keys_from_map<'rec, S: ::std::hash::BuildHasher>(
     conf: &impl CoordinatorMetadata,
     space: &str,
     map: &'rec HashMap<String, Value, S>,
-) -> Result<Vec<&'rec Value>, QueryPlannerError> {
+) -> Result<Vec<&'rec Value>, SbroadError> {
     let quoted_space = normalize_name_from_schema(space);
     let sharding_key = conf.get_sharding_key_by_space(&quoted_space)?;
     let quoted_map = map
@@ -236,16 +241,17 @@ pub fn sharding_keys_from_map<'rec, S: ::std::hash::BuildHasher>(
     for quoted_column in &sharding_key {
         if let Some(column) = quoted_map.get(quoted_column) {
             let value = map.get(*column).ok_or_else(|| {
-                QueryPlannerError::CustomError(format!(
-                    "Missing sharding key column {:?} in the map {:?}",
-                    column, map
-                ))
+                SbroadError::NotFound(
+                    Entity::ShardingKey,
+                    format!("column {column:?} in the map {map:?}"),
+                )
             })?;
             tuple.push(value);
         } else {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Missing quoted sharding key column {:?} in the quoted map {:?}. Original map: {:?}",
-                quoted_column, quoted_map, map
+            return Err(SbroadError::NotFound(
+                Entity::ShardingKey,
+                format!(
+                "(quoted) column {quoted_column:?} in the quoted map {quoted_map:?} (original map: {map:?})"
             )));
         }
     }
diff --git a/sbroad-core/src/executor/engine/mock.rs b/sbroad-core/src/executor/engine/mock.rs
index 3490e8d7eb..39e6993aea 100644
--- a/sbroad-core/src/executor/engine/mock.rs
+++ b/sbroad-core/src/executor/engine/mock.rs
@@ -4,7 +4,7 @@ use std::collections::{HashMap, HashSet};
 
 use crate::backend::sql::tree::{OrderedSyntaxNodes, SyntaxPlan};
 use crate::collection;
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::executor::bucket::Buckets;
 use crate::executor::engine::{
     normalize_name_from_sql, sharding_keys_from_map, sharding_keys_from_tuple, Configuration,
@@ -34,25 +34,19 @@ pub struct RouterConfigurationMock {
 }
 
 impl CoordinatorMetadata for RouterConfigurationMock {
-    fn get_table_segment(&self, table_name: &str) -> Result<Table, QueryPlannerError> {
+    fn get_table_segment(&self, table_name: &str) -> Result<Table, SbroadError> {
         let name = normalize_name_from_sql(table_name);
         match self.tables.get(&name) {
             Some(v) => Ok(v.clone()),
-            None => Err(QueryPlannerError::CustomError(format!(
-                "Space {} not found",
-                table_name
-            ))),
+            None => Err(SbroadError::NotFound(Entity::Space, table_name.to_string())),
         }
     }
 
-    fn get_function(&self, fn_name: &str) -> Result<&Function, QueryPlannerError> {
+    fn get_function(&self, fn_name: &str) -> Result<&Function, SbroadError> {
         let name = normalize_name_from_sql(fn_name);
         match self.functions.get(&name) {
             Some(v) => Ok(v),
-            None => Err(QueryPlannerError::CustomError(format!(
-                "Function {} not found",
-                name
-            ))),
+            None => Err(SbroadError::NotFound(Entity::SQLFunction, name)),
         }
     }
 
@@ -64,20 +58,17 @@ impl CoordinatorMetadata for RouterConfigurationMock {
         self.sharding_column.as_str()
     }
 
-    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<String>, QueryPlannerError> {
+    fn get_sharding_key_by_space(&self, space: &str) -> Result<Vec<String>, SbroadError> {
         let table = self.get_table_segment(space)?;
         table.get_sharding_column_names()
     }
 
-    fn get_sharding_positions_by_space(
-        &self,
-        space: &str,
-    ) -> Result<Vec<usize>, QueryPlannerError> {
+    fn get_sharding_positions_by_space(&self, space: &str) -> Result<Vec<usize>, SbroadError> {
         let table = self.get_table_segment(space)?;
         Ok(table.get_sharding_positions().to_vec())
     }
 
-    fn get_fields_amount_by_space(&self, space: &str) -> Result<usize, QueryPlannerError> {
+    fn get_fields_amount_by_space(&self, space: &str) -> Result<usize, SbroadError> {
         let table = self.get_table_segment(space)?;
         Ok(table.columns.len())
     }
@@ -242,15 +233,16 @@ impl ProducerResult {
     ///
     ///  # Errors
     ///  - metadata isn't equal.
-    fn extend(&mut self, result: ProducerResult) -> Result<(), QueryPlannerError> {
+    fn extend(&mut self, result: ProducerResult) -> Result<(), SbroadError> {
         if self.metadata.is_empty() {
             self.metadata = result.clone().metadata;
         }
 
         if self.metadata != result.metadata {
-            return Err(QueryPlannerError::CustomError(String::from(
-                "Metadata mismatch. Producer results can't be extended",
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Metadata,
+                Some("Metadata mismatch. Producer results can't be extended".into()),
+            ));
         }
         self.rows.extend(result.rows);
         Ok(())
@@ -272,7 +264,7 @@ impl Configuration for RouterRuntimeMock {
         self.metadata.tables.is_empty()
     }
 
-    fn get_config(&self) -> Result<Option<Self::Configuration>, QueryPlannerError> {
+    fn get_config(&self) -> Result<Option<Self::Configuration>, SbroadError> {
         let config = RouterConfigurationMock::new();
         Ok(Some(config))
     }
@@ -286,7 +278,7 @@ impl Coordinator for RouterRuntimeMock {
     type ParseTree = AbstractSyntaxTree;
     type Cache = LRUCache<String, Plan>;
 
-    fn clear_ir_cache(&self) -> Result<(), QueryPlannerError> {
+    fn clear_ir_cache(&self) -> Result<(), SbroadError> {
         *self.ir_cache.borrow_mut() = Self::Cache::new(DEFAULT_CAPACITY, None)?;
         Ok(())
     }
@@ -300,14 +292,15 @@ impl Coordinator for RouterRuntimeMock {
         plan: &mut ExecutionPlan,
         motion_node_id: usize,
         _buckets: &Buckets,
-    ) -> Result<VirtualTable, QueryPlannerError> {
+    ) -> Result<VirtualTable, SbroadError> {
         plan.get_motion_subtree_root(motion_node_id)?;
 
         if let Some(virtual_table) = self.virtual_tables.borrow().get(&motion_node_id) {
             Ok(virtual_table.clone())
         } else {
-            Err(QueryPlannerError::CustomError(
-                "No virtual table found for motion node".to_string(),
+            Err(SbroadError::NotFound(
+                Entity::VirtualTable,
+                format!("for motion node {}", motion_node_id),
             ))
         }
     }
@@ -317,7 +310,7 @@ impl Coordinator for RouterRuntimeMock {
         plan: &mut ExecutionPlan,
         top_id: usize,
         buckets: &Buckets,
-    ) -> Result<Box<dyn Any>, QueryPlannerError> {
+    ) -> Result<Box<dyn Any>, SbroadError> {
         let mut result = ProducerResult::new();
         let sp = SyntaxPlan::new(plan, top_id, Snapshot::Oldest)?;
         let ordered = OrderedSyntaxNodes::try_from(sp)?;
@@ -343,7 +336,7 @@ impl Coordinator for RouterRuntimeMock {
         Ok(Box::new(result))
     }
 
-    fn explain_format(&self, explain: String) -> Result<Box<dyn Any>, QueryPlannerError> {
+    fn explain_format(&self, explain: String) -> Result<Box<dyn Any>, SbroadError> {
         Ok(Box::new(explain))
     }
 
@@ -351,7 +344,7 @@ impl Coordinator for RouterRuntimeMock {
         &'engine self,
         space: String,
         args: &'rec HashMap<String, Value>,
-    ) -> Result<Vec<&'rec Value>, QueryPlannerError> {
+    ) -> Result<Vec<&'rec Value>, SbroadError> {
         sharding_keys_from_map(&self.metadata, &space, args)
     }
 
@@ -359,7 +352,7 @@ impl Coordinator for RouterRuntimeMock {
         &'engine self,
         space: String,
         rec: &'rec [Value],
-    ) -> Result<Vec<&'rec Value>, QueryPlannerError> {
+    ) -> Result<Vec<&'rec Value>, SbroadError> {
         sharding_keys_from_tuple(self.cached_config(), &space, rec)
     }
 
diff --git a/sbroad-core/src/executor/ir.rs b/sbroad-core/src/executor/ir.rs
index a089b2b296..d92ad547d7 100644
--- a/sbroad-core/src/executor/ir.rs
+++ b/sbroad-core/src/executor/ir.rs
@@ -5,8 +5,7 @@ use ahash::AHashMap;
 use serde::{Deserialize, Serialize};
 use traversal::DftPost;
 
-use crate::errors::QueryPlannerError;
-use crate::errors::QueryPlannerError::CustomError;
+use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::vtable::{VirtualTable, VirtualTableMap};
 use crate::ir::expression::Expression;
 use crate::ir::operator::Relational;
@@ -72,20 +71,17 @@ impl ExecutionPlan {
     ///
     /// # Errors
     /// - Failed to find a virtual table for the motion node.
-    pub fn get_motion_vtable(
-        &self,
-        motion_id: usize,
-    ) -> Result<Rc<VirtualTable>, QueryPlannerError> {
+    pub fn get_motion_vtable(&self, motion_id: usize) -> Result<Rc<VirtualTable>, SbroadError> {
         if let Some(vtable) = self.get_vtables() {
             if let Some(result) = vtable.get(&motion_id) {
                 return Ok(Rc::clone(result));
             }
         }
 
-        Err(QueryPlannerError::CustomError(format!(
-            "Motion node ({}) doesn't have a corresponding virtual table",
-            motion_id
-        )))
+        Err(SbroadError::NotFound(
+            Entity::VirtualTable,
+            format!("for Motion node ({motion_id})"),
+        ))
     }
 
     /// Extract policy from motion node
@@ -93,21 +89,22 @@ impl ExecutionPlan {
     /// # Errors
     /// - node is not `Relation` type
     /// - node is not `Motion` type
-    pub fn get_motion_policy(&self, node_id: usize) -> Result<MotionPolicy, QueryPlannerError> {
+    pub fn get_motion_policy(&self, node_id: usize) -> Result<MotionPolicy, SbroadError> {
         if let Relational::Motion { policy, .. } = &self.plan.get_relation_node(node_id)? {
             return Ok(policy.clone());
         }
 
-        Err(QueryPlannerError::CustomError(String::from(
-            "Invalid motion",
-        )))
+        Err(SbroadError::Invalid(
+            Entity::Relational,
+            Some("invalid motion".into()),
+        ))
     }
 
     /// Get motion alias name
     ///
     /// # Errors
     /// - node is not valid
-    pub fn get_motion_alias(&self, node_id: usize) -> Result<Option<&String>, QueryPlannerError> {
+    pub fn get_motion_alias(&self, node_id: usize) -> Result<Option<&String>, SbroadError> {
         let sq_id = &self.get_motion_child(node_id)?;
         if let Relational::ScanSubQuery { alias, .. } =
             self.get_ir_plan().get_relation_node(*sq_id)?
@@ -122,7 +119,7 @@ impl ExecutionPlan {
     ///
     /// # Errors
     /// - node is not valid
-    pub fn get_motion_subtree_root(&self, node_id: usize) -> Result<usize, QueryPlannerError> {
+    pub fn get_motion_subtree_root(&self, node_id: usize) -> Result<usize, SbroadError> {
         let top_id = &self.get_motion_child(node_id)?;
         let rel = self.get_ir_plan().get_relation_node(*top_id)?;
         match rel {
@@ -135,9 +132,10 @@ impl ExecutionPlan {
             | Relational::UnionAll { .. }
             | Relational::Values { .. }
             | Relational::ValuesRow { .. } => Ok(*top_id),
-            Relational::Motion { .. } | Relational::Insert { .. } => Err(
-                QueryPlannerError::CustomError("Invalid motion child node".to_string()),
-            ),
+            Relational::Motion { .. } | Relational::Insert { .. } => Err(SbroadError::Invalid(
+                Entity::Relational,
+                Some("invalid motion child node".to_string()),
+            )),
         }
     }
 
@@ -146,21 +144,21 @@ impl ExecutionPlan {
     /// # Errors
     /// - node is not `Relation` type
     /// - node does not contain children
-    pub(crate) fn get_motion_child(&self, node_id: usize) -> Result<usize, QueryPlannerError> {
+    pub(crate) fn get_motion_child(&self, node_id: usize) -> Result<usize, SbroadError> {
         let node = self.get_ir_plan().get_relation_node(node_id)?;
         if !node.is_motion() {
-            return Err(CustomError(format!(
-                "Current node ({}) is not motion",
-                node_id
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Relational,
+                Some(format!("current node ({node_id}) is not motion")),
+            ));
         }
 
         let children = self.plan.get_relational_children(node_id)?.ok_or_else(|| {
-            QueryPlannerError::CustomError("Could not get motion children".to_string())
+            SbroadError::NotFound(Entity::Node, format!("that is Motion {node_id} child(ren)"))
         })?;
 
         if children.len() != 1 {
-            return Err(CustomError(format!(
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
                 "Motion node ({}) must have once child only (actual {})",
                 node_id,
                 children.len()
@@ -168,7 +166,7 @@ impl ExecutionPlan {
         }
 
         let child_id = children.first().ok_or_else(|| {
-            QueryPlannerError::CustomError("Failed to get the first motion child".to_string())
+            SbroadError::UnexpectedNumberOfValues("Motion has no children".to_string())
         })?;
 
         Ok(*child_id)
@@ -179,21 +177,24 @@ impl ExecutionPlan {
     /// # Errors
     /// - node is not `Relation` type
     /// - node does not contain children
-    fn get_subquery_child(&self, node_id: usize) -> Result<usize, QueryPlannerError> {
+    fn get_subquery_child(&self, node_id: usize) -> Result<usize, SbroadError> {
         let node = self.get_ir_plan().get_relation_node(node_id)?;
         if !node.is_subquery() {
-            return Err(CustomError(format!(
-                "Current node ({}) is not sub query",
-                node_id
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Node,
+                Some(format!("current node ({node_id}) is not sub query")),
+            ));
         }
 
         let children = self.plan.get_relational_children(node_id)?.ok_or_else(|| {
-            QueryPlannerError::CustomError("Could not get subquery children".to_string())
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("that is Subquery {node_id} child(ren)"),
+            )
         })?;
 
         if children.len() != 1 {
-            return Err(CustomError(format!(
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
                 "Sub query node ({}) must have once child only (actual {})",
                 node_id,
                 children.len()
@@ -201,7 +202,7 @@ impl ExecutionPlan {
         }
 
         let child_id = children.first().ok_or_else(|| {
-            QueryPlannerError::CustomError("Could not find subquery child".to_string())
+            SbroadError::UnexpectedNumberOfValues("could not find subquery child".to_string())
         })?;
 
         Ok(*child_id)
@@ -211,7 +212,7 @@ impl ExecutionPlan {
     ///
     /// # Errors
     /// - not a motion node
-    pub fn unlink_motion_subtree(&mut self, motion_id: usize) -> Result<(), QueryPlannerError> {
+    pub fn unlink_motion_subtree(&mut self, motion_id: usize) -> Result<(), SbroadError> {
         let motion = self.get_mut_ir_plan().get_mut_relation_node(motion_id)?;
         if let Relational::Motion {
             ref mut children, ..
@@ -219,10 +220,10 @@ impl ExecutionPlan {
         {
             *children = vec![];
         } else {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Node ({}) is not motion",
-                motion_id
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Relational,
+                Some(format!("node ({motion_id}) is not motion")),
+            ));
         }
         Ok(())
     }
@@ -233,7 +234,7 @@ impl ExecutionPlan {
     /// # Errors
     /// - the original execution plan is invalid
     #[allow(clippy::too_many_lines)]
-    pub fn take_subtree(&mut self, top_id: usize) -> Result<Self, QueryPlannerError> {
+    pub fn take_subtree(&mut self, top_id: usize) -> Result<Self, SbroadError> {
         let nodes_capacity = self.get_ir_plan().nodes.len();
         // Translates the original plan's node id to the new sub-plan one.
         let mut translation: AHashMap<usize, usize> = AHashMap::with_capacity(nodes_capacity);
@@ -262,10 +263,11 @@ impl ExecutionPlan {
                 Node::Relational(ref mut rel) => {
                     if let Relational::ValuesRow { data, .. } = rel {
                         *data = *translation.get(data).ok_or_else(|| {
-                            QueryPlannerError::CustomError(format!(
-                                "Failed to build an execution plan subtree: could not find data node id {} in the map",
-                                data
-                            ))
+                            SbroadError::FailedTo(
+                                Action::Build,
+                                Some(Entity::SubTree),
+                                format!("could not find data node id {data} in the map"),
+                            )
                         })?;
                     }
 
@@ -281,20 +283,21 @@ impl ExecutionPlan {
                     if let Some(children) = rel.mut_children() {
                         for child_id in children {
                             *child_id = *translation.get(child_id).ok_or_else(|| {
-                                QueryPlannerError::CustomError(format!(
-                                    "Failed to build an execution plan subtree: could not find child node id {} in the map",
-                                    child_id
-                                ))
+                                SbroadError::FailedTo(
+                                    Action::Build,
+                                    Some(Entity::SubTree),
+                                    format!("could not find child node id {child_id} in the map"),
+                                )
                             })?;
                         }
                     }
 
                     let output = rel.output();
                     *rel.mut_output() = *translation.get(&output).ok_or_else(|| {
-                        QueryPlannerError::CustomError(format!(
-                            "Failed to find an output node {} in relational node {:?}",
-                            output, rel
-                        ))
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            format!("as output node {output} in relational node {rel:?}"),
+                        )
                     })?;
                     new_plan.replace_parent_in_subtree(rel.output(), None, Some(next_id))?;
 
@@ -315,10 +318,14 @@ impl ExecutionPlan {
                         // for filter/condition (but then the UNDO logic should be changed as well).
                         let undo_expr_id = ir_plan.undo.get_oldest(expr_id).unwrap_or(expr_id);
                         *expr_id = *translation.get(undo_expr_id).ok_or_else(|| {
-                            QueryPlannerError::CustomError(format!(
-                                "Failed to build an execution plan subtree: could not find filter/condition node id {} in the map",
-                                undo_expr_id
-                            ))
+                            SbroadError::FailedTo(
+                                Action::Build,
+                                Some(Entity::SubTree),
+                                format!(
+                                    "could not find filter/condition node id {} in the map",
+                                    undo_expr_id
+                                ),
+                            )
                         })?;
                         new_plan.replace_parent_in_subtree(*expr_id, None, Some(next_id))?;
                     }
@@ -326,12 +333,20 @@ impl ExecutionPlan {
                     if let Relational::ScanRelation { relation, .. }
                     | Relational::Insert { relation, .. } = rel
                     {
-                        let table = ir_plan.relations.get(relation).ok_or_else(|| {
-                            QueryPlannerError::CustomError(format!(
-                                "Failed to build an execution plan subtree: could not find relation {} in the original plan",
-                                relation
-                            ))
-                        })?.clone();
+                        let table = ir_plan
+                            .relations
+                            .get(relation)
+                            .ok_or_else(|| {
+                                SbroadError::FailedTo(
+                                    Action::Build,
+                                    Some(Entity::SubTree),
+                                    format!(
+                                        "could not find relation {} in the original plan",
+                                        relation
+                                    ),
+                                )
+                            })?
+                            .clone();
                         new_plan.add_rel(table);
                     }
                 }
@@ -340,11 +355,12 @@ impl ExecutionPlan {
                     | Expression::Cast { ref mut child, .. }
                     | Expression::Unary { ref mut child, .. } => {
                         *child = *translation.get(child).ok_or_else(|| {
-                                QueryPlannerError::CustomError(format!(
-                                    "Failed to build an execution plan subtree: could not find child node id {} in the map",
-                                    child
-                                ))
-                            })?;
+                            SbroadError::FailedTo(
+                                Action::Build,
+                                Some(Entity::SubTree),
+                                format!("could not find child node id {child} in the map"),
+                            )
+                        })?;
                     }
                     Expression::Bool {
                         ref mut left,
@@ -357,17 +373,19 @@ impl ExecutionPlan {
                         ..
                     } => {
                         *left = *translation.get(left).ok_or_else(|| {
-                                QueryPlannerError::CustomError(format!(
-                                    "Failed to build an execution plan subtree: could not find left child node id {} in the map",
-                                    left
-                                ))
-                            })?;
+                            SbroadError::FailedTo(
+                                Action::Build,
+                                Some(Entity::SubTree),
+                                format!("could not find left child node id {left} in the map"),
+                            )
+                        })?;
                         *right = *translation.get(right).ok_or_else(|| {
-                                QueryPlannerError::CustomError(format!(
-                                    "Failed to build an execution plan subtree: could not find right child node id {} in the map",
-                                    right
-                                ))
-                            })?;
+                            SbroadError::FailedTo(
+                                Action::Build,
+                                Some(Entity::SubTree),
+                                format!("could not find right child node id {right} in the map"),
+                            )
+                        })?;
                     }
                     Expression::Reference { ref mut parent, .. } => {
                         // The new parent node id MUST be set while processing the relational nodes.
@@ -382,11 +400,12 @@ impl ExecutionPlan {
                     } => {
                         for child in children {
                             *child = *translation.get(child).ok_or_else(|| {
-                                    QueryPlannerError::CustomError(format!(
-                                        "Failed to build an execution plan subtree: could not find child node id {} in the map",
-                                        child
-                                    ))
-                                })?;
+                                SbroadError::FailedTo(
+                                    Action::Build,
+                                    Some(Entity::SubTree),
+                                    format!("could not find child node id {child} in the map"),
+                                )
+                            })?;
                         }
                     }
                     Expression::Constant { .. } => {}
@@ -417,7 +436,7 @@ impl ExecutionPlan {
 
     /// # Errors
     /// - execution plan is invalid
-    pub fn query_type(&self) -> Result<QueryType, QueryPlannerError> {
+    pub fn query_type(&self) -> Result<QueryType, SbroadError> {
         let top_id = self.get_ir_plan().get_top()?;
         let top = self.get_ir_plan().get_relation_node(top_id)?;
         if top.is_insert() {
@@ -429,7 +448,7 @@ impl ExecutionPlan {
 
     /// # Errors
     /// - execution plan is invalid
-    pub fn connection_type(&self) -> Result<ConnectionType, QueryPlannerError> {
+    pub fn connection_type(&self) -> Result<ConnectionType, SbroadError> {
         match self.query_type()? {
             QueryType::DML => Ok(ConnectionType::Write),
             QueryType::DQL => Ok(ConnectionType::Read),
diff --git a/sbroad-core/src/executor/lru.rs b/sbroad-core/src/executor/lru.rs
index 318fe1eb48..8e207197e1 100644
--- a/sbroad-core/src/executor/lru.rs
+++ b/sbroad-core/src/executor/lru.rs
@@ -1,17 +1,17 @@
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 use std::collections::{hash_map::Entry, HashMap};
 use std::fmt::Debug;
 
 pub const DEFAULT_CAPACITY: usize = 50;
 
-pub type EvictFn<Value> = Box<dyn Fn(&mut Value) -> Result<(), QueryPlannerError>>;
+pub type EvictFn<Value> = Box<dyn Fn(&mut Value) -> Result<(), SbroadError>>;
 
 pub trait Cache<Key, Value> {
     /// Builds a new cache with the given capacity.
     ///
     /// # Errors
     /// - Capacity is not valid (zero).
-    fn new(capacity: usize, evict_fn: Option<EvictFn<Value>>) -> Result<Self, QueryPlannerError>
+    fn new(capacity: usize, evict_fn: Option<EvictFn<Value>>) -> Result<Self, SbroadError>
     where
         Self: Sized;
 
@@ -19,13 +19,13 @@ pub trait Cache<Key, Value> {
     ///
     /// # Errors
     /// - Internal error (should never happen).
-    fn get(&mut self, key: &Key) -> Result<Option<&Value>, QueryPlannerError>;
+    fn get(&mut self, key: &Key) -> Result<Option<&Value>, SbroadError>;
 
     /// Inserts a key-value pair into the cache.
     ///
     /// # Errors
     /// - Internal error (should never happen).
-    fn put(&mut self, key: Key, value: Value) -> Result<(), QueryPlannerError>;
+    fn put(&mut self, key: Key, value: Value) -> Result<(), SbroadError>;
 }
 
 #[derive(Debug)]
@@ -92,22 +92,19 @@ where
         self.map.get(key)
     }
 
-    fn get_node(&self, key: &Option<Key>) -> Result<&LRUNode<Key, Value>, QueryPlannerError> {
-        self.map.get(key).ok_or_else(|| {
-            QueryPlannerError::CustomError(format!("LRU node with key {key:?} not found"))
-        })
+    fn get_node(&self, key: &Option<Key>) -> Result<&LRUNode<Key, Value>, SbroadError> {
+        self.map
+            .get(key)
+            .ok_or_else(|| SbroadError::NotFound(Entity::Node, format!("(LRU) with key {key:?}")))
     }
 
-    fn get_node_mut(
-        &mut self,
-        key: &Option<Key>,
-    ) -> Result<&mut LRUNode<Key, Value>, QueryPlannerError> {
+    fn get_node_mut(&mut self, key: &Option<Key>) -> Result<&mut LRUNode<Key, Value>, SbroadError> {
         self.map.get_mut(key).ok_or_else(|| {
-            QueryPlannerError::CustomError(format!("Mutable LRU node with key {key:?} not found"))
+            SbroadError::NotFound(Entity::Node, format!("(mutable LRU) with key {key:?}"))
         })
     }
 
-    fn add_first(&mut self, key: Key, value: Value) -> Result<(), QueryPlannerError> {
+    fn add_first(&mut self, key: Key, value: Value) -> Result<(), SbroadError> {
         let new_node = LRUNode::new(value);
         self.map.insert(Some(key.clone()), new_node);
         self.size += 1;
@@ -117,7 +114,7 @@ where
         Ok(())
     }
 
-    fn make_first(&mut self, key: &Key) -> Result<(), QueryPlannerError> {
+    fn make_first(&mut self, key: &Key) -> Result<(), SbroadError> {
         self.unlink_node(&Some(key.clone()))?;
         let head_node = self.get_node(&None)?;
         let head_next_id = head_node.next.clone();
@@ -125,7 +122,7 @@ where
         Ok(())
     }
 
-    fn is_first(&self, key: &Key) -> Result<bool, QueryPlannerError> {
+    fn is_first(&self, key: &Key) -> Result<bool, SbroadError> {
         let head_node = self.get_node(&None)?;
         Ok(head_node.next == Some(key.clone()))
     }
@@ -135,7 +132,7 @@ where
         key: Key,
         prev: &Option<Key>,
         next: &Option<Key>,
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         let node = self.get_node_mut(&Some(key.clone()))?;
         node.replace_prev(prev.clone());
         node.replace_next(next.clone());
@@ -146,7 +143,7 @@ where
         Ok(())
     }
 
-    fn unlink_node(&mut self, key: &Option<Key>) -> Result<(), QueryPlannerError> {
+    fn unlink_node(&mut self, key: &Option<Key>) -> Result<(), SbroadError> {
         // We don't want to remove sentinel.
         if key.is_none() {
             return Ok(());
@@ -162,7 +159,7 @@ where
         Ok(())
     }
 
-    fn remove_last(&mut self) -> Result<(), QueryPlannerError> {
+    fn remove_last(&mut self) -> Result<(), SbroadError> {
         let head_node = self.get_node(&None)?;
         let head_prev_id = head_node.prev.clone();
         if head_prev_id.is_none() {
@@ -172,10 +169,10 @@ where
         let map = &mut self.map;
         if let Some(evict_fn) = &self.evict_fn {
             let head_prev = map.get_mut(&head_prev_id).ok_or_else(|| {
-                QueryPlannerError::CustomError(format!(
-                    "Mutable LRU node with key {:?} not found",
-                    &head_prev_id
-                ))
+                SbroadError::NotFound(
+                    Entity::Node,
+                    format!("(mutable LRU) with key {:?}", &head_prev_id),
+                )
             })?;
             evict_fn(&mut head_prev.value)?;
         }
@@ -194,10 +191,11 @@ where
     Value: Default + Debug,
     Key: Clone + Eq + std::hash::Hash + std::fmt::Debug,
 {
-    fn new(capacity: usize, evict_fn: Option<EvictFn<Value>>) -> Result<Self, QueryPlannerError> {
+    fn new(capacity: usize, evict_fn: Option<EvictFn<Value>>) -> Result<Self, SbroadError> {
         if capacity == 0 {
-            return Err(QueryPlannerError::CustomError(
-                "LRU cache capacity must be greater than zero".to_string(),
+            return Err(SbroadError::Invalid(
+                Entity::Cache,
+                Some("LRU cache capacity must be greater than zero".to_string()),
             ));
         }
         let head = LRUNode::sentinel();
@@ -213,7 +211,7 @@ where
         })
     }
 
-    fn get(&mut self, key: &Key) -> Result<Option<&Value>, QueryPlannerError> {
+    fn get(&mut self, key: &Key) -> Result<Option<&Value>, SbroadError> {
         if self.get_node_or_none(&Some(key.clone())).is_none() {
             return Ok(None);
         }
@@ -227,14 +225,15 @@ where
         if let Some(node) = self.get_node_or_none(&Some(key.clone())) {
             Ok(Some(&node.value))
         } else {
-            Err(QueryPlannerError::CustomError(format!(
-                "Failed to retrieve a value from the LRU cache for a key {:?}",
-                key
-            )))
+            Err(SbroadError::FailedTo(
+                Action::Retrieve,
+                Some(Entity::Value),
+                format!("from the LRU cache for a key {key:?}"),
+            ))
         }
     }
 
-    fn put(&mut self, key: Key, value: Value) -> Result<(), QueryPlannerError> {
+    fn put(&mut self, key: Key, value: Value) -> Result<(), SbroadError> {
         if let Entry::Occupied(mut entry) = self.map.entry(Some(key.clone())) {
             let node = entry.get_mut();
             node.value = value;
diff --git a/sbroad-core/src/executor/lru/tests.rs b/sbroad-core/src/executor/lru/tests.rs
index c79d6d472b..681a8d8f84 100644
--- a/sbroad-core/src/executor/lru/tests.rs
+++ b/sbroad-core/src/executor/lru/tests.rs
@@ -1,5 +1,5 @@
 use super::{Cache, LRUCache};
-use crate::errors::QueryPlannerError;
+use crate::errors::SbroadError;
 use crate::ir::Plan;
 use pretty_assertions::assert_eq;
 
@@ -17,7 +17,7 @@ fn lru1() {
 
 #[test]
 fn lru2() {
-    let cache_err: Result<LRUCache<usize, Plan>, QueryPlannerError> = LRUCache::new(0, None);
+    let cache_err: Result<LRUCache<usize, Plan>, SbroadError> = LRUCache::new(0, None);
     assert_eq!(cache_err.is_err(), true);
 }
 
@@ -26,7 +26,7 @@ fn lru3() {
     let evict_fn = Box::new(|value: &mut String| {
         let value_old = value.clone();
         value.push_str("_old");
-        Err(QueryPlannerError::CustomError(format!(
+        Err(SbroadError::UnexpectedNumberOfValues(format!(
             "changed {} to {} during cache eviction",
             value_old, value
         )))
@@ -34,7 +34,9 @@ fn lru3() {
     let mut cache: LRUCache<usize, String> = LRUCache::new(1, Some(evict_fn)).unwrap();
     cache.put(1, "one".to_string()).unwrap();
     assert_eq!(
-        QueryPlannerError::CustomError("changed one to one_old during cache eviction".to_string()),
+        SbroadError::UnexpectedNumberOfValues(
+            "changed one to one_old during cache eviction".to_string()
+        ),
         cache.put(2, "two".to_string()).unwrap_err()
     );
 }
diff --git a/sbroad-core/src/executor/result.rs b/sbroad-core/src/executor/result.rs
index b6818e4df2..1714ceea79 100644
--- a/sbroad-core/src/executor/result.rs
+++ b/sbroad-core/src/executor/result.rs
@@ -3,7 +3,7 @@ use serde::ser::{Serialize, SerializeMap, Serializer};
 use serde::Deserialize;
 use tarantool::tlua::{self, LuaRead};
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::executor::vtable::VirtualTable;
 use crate::ir::relation::{Column, ColumnRole, Type};
 use crate::ir::value::{EncodedValue, Value};
@@ -36,7 +36,7 @@ impl Serialize for MetadataColumn {
 }
 
 impl TryInto<Column> for &MetadataColumn {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     fn try_into(self) -> Result<Column, Self::Error> {
         match self.r#type.as_str() {
@@ -50,10 +50,10 @@ impl TryInto<Column> for &MetadataColumn {
                 Ok(Column::new(&self.name, Type::String, ColumnRole::User))
             }
             "unsigned" => Ok(Column::new(&self.name, Type::Unsigned, ColumnRole::User)),
-            _ => Err(QueryPlannerError::CustomError(format!(
-                "unsupported column type: {}",
-                self.r#type
-            ))),
+            _ => Err(SbroadError::Unsupported(
+                Entity::Type,
+                Some(format!("column type {}", self.r#type)),
+            )),
         }
     }
 }
@@ -87,7 +87,7 @@ impl ProducerResult {
     ///
     /// # Errors
     /// - convert to virtual table error
-    pub fn as_virtual_table(&self) -> Result<VirtualTable, QueryPlannerError> {
+    pub fn as_virtual_table(&self) -> Result<VirtualTable, SbroadError> {
         let mut result = VirtualTable::new();
 
         for col in &self.metadata {
diff --git a/sbroad-core/src/executor/shard.rs b/sbroad-core/src/executor/shard.rs
index 99d0f03e8d..6f80b84485 100644
--- a/sbroad-core/src/executor/shard.rs
+++ b/sbroad-core/src/executor/shard.rs
@@ -4,7 +4,7 @@ use traversal::DftPost;
 
 use crate::executor::ir::ExecutionPlan;
 use crate::{
-    errors::QueryPlannerError,
+    errors::{SbroadError, Entity},
     ir::{distribution::Distribution, expression::Expression},
 };
 
@@ -32,7 +32,7 @@ impl<'e> ExecutionPlan<'e> {
     fn get_motion_sharding_keys(
         &self,
         top_node_id: usize,
-    ) -> Result<HashSet<String>, QueryPlannerError> {
+    ) -> Result<HashSet<String>, SbroadError> {
         let mut result = HashSet::new();
 
         let ir_plan = self.get_ir_plan();
@@ -55,7 +55,7 @@ impl<'e> ExecutionPlan<'e> {
     pub fn discovery(
         &mut self,
         node_id: usize,
-    ) -> Result<Option<HashSet<String>>, QueryPlannerError> {
+    ) -> Result<Option<HashSet<String>>, SbroadError> {
         let mut dist_keys: HashSet<String> = HashSet::new();
 
         let motion_shard_keys = self.get_motion_sharding_keys(node_id)?;
@@ -88,7 +88,7 @@ impl<'e> ExecutionPlan<'e> {
                 };
 
                 // Get the distribution of the left row.
-                if let Err(QueryPlannerError::UninitializedDistribution) =
+                if let Err(SbroadError::Invalid(Entity::Distribution, _)) =
                     ir_plan.get_distribution(left_id)
                 {
                     ir_plan.set_distribution(left_id)?;
@@ -103,12 +103,10 @@ impl<'e> ExecutionPlan<'e> {
                             return Ok(None);
                         }
                         let position = positions.get(0).ok_or_else(|| {
-                            QueryPlannerError::CustomError("Invalid distribution key".into())
+                            SbroadError::NotFound(Entity::DistributionKey, "with index 0".into())
                         })?;
                         let right_column_id = *right_columns.get(*position).ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Left and right rows have different length.".into(),
-                            )
+                            SbroadError::UnexpectedNumberOfValues("left and right rows have different length.".into())
                         })?;
                         let right_column = ir_plan.get_expression_node(right_column_id)?;
                         if right_column.is_const() {
diff --git a/sbroad-core/src/executor/tests.rs b/sbroad-core/src/executor/tests.rs
index 2903d60b18..149891b671 100644
--- a/sbroad-core/src/executor/tests.rs
+++ b/sbroad-core/src/executor/tests.rs
@@ -1288,10 +1288,11 @@ fn insert7_test() {
     let result = Query::new(&coordinator, sql, vec![]).unwrap_err();
 
     assert_eq!(
-        QueryPlannerError::CustomError(format!(
-            "System column {} cannot be inserted",
-            "\"bucket_id\""
-        )),
+        SbroadError::FailedTo(
+            Action::Insert,
+            Some(Entity::Column),
+            format!("system column {} cannot be inserted", "\"bucket_id\""),
+        ),
         result
     );
 }
diff --git a/sbroad-core/src/executor/tests/frontend.rs b/sbroad-core/src/executor/tests/frontend.rs
index c80b30ce7b..31cae0fd47 100644
--- a/sbroad-core/src/executor/tests/frontend.rs
+++ b/sbroad-core/src/executor/tests/frontend.rs
@@ -19,8 +19,8 @@ fn front_ivalid_sql1() {
     let plan_err = Query::new(metadata, query, vec![]).unwrap_err();
 
     assert_eq!(
-        QueryPlannerError::CustomError(
-            "Row can't be added because `\"sys_op\"` already has an alias".into()
+        SbroadError::DuplicatedValue(
+            "row can't be added because `\"sys_op\"` already has an alias".into()
         ),
         plan_err
     );
@@ -34,8 +34,8 @@ fn front_invalid_sql2() {
     let plan_err = Query::new(metadata, query, vec![]).unwrap_err();
 
     assert_eq!(
-        QueryPlannerError::CustomError(
-            r#"Invalid number of values: 2. Table "t" expects 1 column(s)."#.into()
+        SbroadError::UnexpectedNumberOfValues(
+            r#"invalid number of values: 2. Table "t" expects 1 column(s)."#.into()
         ),
         plan_err
     );
@@ -49,8 +49,8 @@ fn front_invalid_sql3() {
     let plan_err = Query::new(metadata, query, vec![]).unwrap_err();
 
     assert_eq!(
-        QueryPlannerError::CustomError(
-            r#"Invalid number of values: 2. Table "t" expects 4 column(s)."#.into()
+        SbroadError::UnexpectedNumberOfValues(
+            r#"invalid number of values: 2. Table "t" expects 4 column(s)."#.into()
         ),
         plan_err
     );
@@ -64,8 +64,8 @@ fn front_invalid_sql4() {
     let plan_err = Query::new(metadata, query, vec![]).unwrap_err();
 
     assert_eq!(
-        QueryPlannerError::CustomError(
-            r#"Invalid number of values: 2. Table "t" expects 4 column(s)."#.into()
+        SbroadError::UnexpectedNumberOfValues(
+            r#"invalid number of values: 2. Table "t" expects 4 column(s)."#.into()
         ),
         plan_err
     );
diff --git a/sbroad-core/src/executor/vtable.rs b/sbroad-core/src/executor/vtable.rs
index 0449052120..b1b2a8de06 100644
--- a/sbroad-core/src/executor/vtable.rs
+++ b/sbroad-core/src/executor/vtable.rs
@@ -4,7 +4,7 @@ use std::vec;
 
 use serde::{Deserialize, Serialize};
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::executor::bucket::Buckets;
 use crate::ir::relation::Column;
 use crate::ir::transformation::redistribution::{MotionKey, Target};
@@ -156,7 +156,7 @@ impl VirtualTable {
     ///
     /// # Errors
     /// - Failed to find a distribution key.
-    pub fn get_tuple_distribution(&self) -> Result<HashSet<Vec<&Value>>, QueryPlannerError> {
+    pub fn get_tuple_distribution(&self) -> Result<HashSet<Vec<&Value>>, SbroadError> {
         let mut result: HashSet<Vec<&Value>> = HashSet::with_capacity(self.tuples.len());
 
         for tuple in &self.tuples {
@@ -166,10 +166,10 @@ impl VirtualTable {
                     match target {
                         Target::Reference(pos) => {
                             let part = tuple.get(*pos).ok_or_else(|| {
-                                QueryPlannerError::CustomError(format!(
-                                    "Failed to find a distribution key column {} in the tuple {:?}.",
-                                    pos, tuple
-                                ))
+                                SbroadError::NotFound(
+                                    Entity::DistributionKey,
+                                    format!("column {pos} in the tuple {tuple:?}."),
+                                )
                             })?;
                             shard_key_tuple.push(part);
                         }
@@ -179,8 +179,9 @@ impl VirtualTable {
                     }
                 }
             } else {
-                return Err(QueryPlannerError::CustomError(
-                    "Distribution key not found".into(),
+                return Err(SbroadError::NotFound(
+                    Entity::DistributionKey,
+                    "for virtual table".into(),
                 ));
             }
             result.insert(shard_key_tuple);
@@ -193,10 +194,11 @@ impl VirtualTable {
     ///
     /// # Errors
     /// - Try to set an empty alias name to the virtual table.
-    pub fn set_alias(&mut self, name: &str) -> Result<(), QueryPlannerError> {
+    pub fn set_alias(&mut self, name: &str) -> Result<(), SbroadError> {
         if name.is_empty() {
-            return Err(QueryPlannerError::CustomError(
-                "Can't set empty alias for virtual table".into(),
+            return Err(SbroadError::Invalid(
+                Entity::Value,
+                Some("can't set empty alias for virtual table".into()),
             ));
         }
 
diff --git a/sbroad-core/src/frontend.rs b/sbroad-core/src/frontend.rs
index acfaaf90e7..39aad965c6 100644
--- a/sbroad-core/src/frontend.rs
+++ b/sbroad-core/src/frontend.rs
@@ -3,7 +3,7 @@
 //! A list of different frontend implementations
 //! to build the intermediate representation (IR).
 
-use crate::errors::QueryPlannerError;
+use crate::errors::SbroadError;
 use crate::executor::engine::CoordinatorMetadata;
 use crate::ir::Plan;
 
@@ -16,7 +16,7 @@ pub trait Ast {
     ///
     /// # Errors
     /// - SQL query is not valid or not supported.
-    fn new(query: &str) -> Result<Self, QueryPlannerError>
+    fn new(query: &str) -> Result<Self, SbroadError>
     where
         Self: Sized;
 
@@ -27,7 +27,7 @@ pub trait Ast {
     ///
     /// # Errors
     /// - Failed to resolve AST nodes with cluster metadata.
-    fn resolve_metadata<M>(&self, metadata: &M) -> Result<Plan, QueryPlannerError>
+    fn resolve_metadata<M>(&self, metadata: &M) -> Result<Plan, SbroadError>
     where
         M: CoordinatorMetadata;
 }
diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs
index a507c61f78..710e9ce079 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -7,7 +7,7 @@ use pest::Parser;
 use std::collections::{HashMap, HashSet};
 use traversal::DftPost;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::executor::engine::{normalize_name_from_sql, CoordinatorMetadata};
 use crate::frontend::sql::ast::{
     AbstractSyntaxTree, ParseNode, ParseNodes, ParseTree, Rule, StackParseNode, Type,
@@ -59,20 +59,15 @@ impl Ast for AbstractSyntaxTree {
     /// # Errors
     /// - Failed to parse an SQL query.
     #[otm_child_span("ast.parse")]
-    fn new(query: &str) -> Result<Self, QueryPlannerError> {
+    fn new(query: &str) -> Result<Self, SbroadError> {
         let mut ast = AbstractSyntaxTree::empty();
 
         let mut command_pair = match ParseTree::parse(Rule::Command, query) {
             Ok(p) => p,
-            Err(e) => {
-                return Err(QueryPlannerError::CustomError(format!(
-                    "Parsing error: {}",
-                    e
-                )))
-            }
+            Err(e) => return Err(SbroadError::ParsingError(Entity::Rule, format!("{e}"))),
         };
         let top_pair = command_pair.next().ok_or_else(|| {
-            QueryPlannerError::CustomError("No query found in the parse tree.".to_string())
+            SbroadError::UnexpectedNumberOfValues("no query found in the parse tree.".to_string())
         })?;
         let top = StackParseNode::new(top_pair, None);
 
@@ -118,7 +113,7 @@ impl Ast for AbstractSyntaxTree {
     #[allow(dead_code)]
     #[allow(clippy::too_many_lines)]
     #[otm_child_span("ast.resolve")]
-    fn resolve_metadata<M>(&self, metadata: &M) -> Result<Plan, QueryPlannerError>
+    fn resolve_metadata<M>(&self, metadata: &M) -> Result<Plan, SbroadError>
     where
         M: CoordinatorMetadata,
     {
@@ -126,7 +121,7 @@ impl Ast for AbstractSyntaxTree {
 
         let top = match self.top {
             Some(t) => t,
-            None => return Err(QueryPlannerError::InvalidAst),
+            None => return Err(SbroadError::Invalid(Entity::AST, None)),
         };
         let dft_post = DftPost::new(&top, |node| self.nodes.ast_iter(node));
         let mut map = Translation::with_capacity(self.nodes.next_id());
@@ -140,8 +135,8 @@ impl Ast for AbstractSyntaxTree {
             match &node.rule {
                 Type::Scan => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Could not find child id in scan node".to_string(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "could not find child id in scan node".to_string(),
                         )
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
@@ -155,8 +150,9 @@ impl Ast for AbstractSyntaxTree {
                             let scan = plan.get_mut_relation_node(plan_child_id)?;
                             scan.set_scan_name(ast_scan_name)?;
                         } else {
-                            return Err(QueryPlannerError::CustomError(
-                                "Expected scan name AST node.".into(),
+                            return Err(SbroadError::Invalid(
+                                Entity::Type,
+                                Some("expected scan name AST node.".into()),
                             ));
                         }
                     }
@@ -169,15 +165,16 @@ impl Ast for AbstractSyntaxTree {
                         let scan_id = plan.add_scan(&normalize_name_from_sql(table), None)?;
                         map.add(*id, scan_id);
                     } else {
-                        return Err(QueryPlannerError::CustomError(
-                            "Table name is not found.".into(),
+                        return Err(SbroadError::Invalid(
+                            Entity::Type,
+                            Some("Table name is not found.".into()),
                         ));
                     }
                 }
                 Type::SubQuery => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Child node id is not found among sub-query children.".into(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "child node id is not found among sub-query children.".into(),
                         )
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
@@ -186,10 +183,13 @@ impl Ast for AbstractSyntaxTree {
                         let ast_alias = self.nodes.get_node(*ast_name_id)?;
                         if let Type::SubQueryName = ast_alias.rule {
                         } else {
-                            return Err(QueryPlannerError::CustomError(format!(
-                                "Expected a sub-query name, got {:?}.",
-                                ast_alias.rule
-                            )));
+                            return Err(SbroadError::Invalid(
+                                Entity::Type,
+                                Some(format!(
+                                    "expected a sub-query name, got {:?}.",
+                                    ast_alias.rule
+                                )),
+                            ));
                         }
                         ast_alias.value.as_deref().map(normalize_name_from_sql)
                     } else {
@@ -206,25 +206,27 @@ impl Ast for AbstractSyntaxTree {
                         plan_rel_list.push(plan_id);
                     }
 
-                    let get_column_name = |ast_id: usize| -> Result<String, QueryPlannerError> {
+                    let get_column_name = |ast_id: usize| -> Result<String, SbroadError> {
                         let ast_col_name = self.nodes.get_node(ast_id)?;
                         if let Type::ColumnName = ast_col_name.rule {
                             let name: Option<String> =
                                 ast_col_name.value.as_deref().map(normalize_name_from_sql);
                             Ok(name.ok_or_else(|| {
-                                QueryPlannerError::CustomError("Empty AST column name".into())
+                                SbroadError::Invalid(
+                                    Entity::Name,
+                                    Some("empty AST column name".into()),
+                                )
                             })?)
                         } else {
-                            Err(QueryPlannerError::CustomError(
-                                "Expected column name AST node.".into(),
+                            Err(SbroadError::Invalid(
+                                Entity::Type,
+                                Some("expected column name AST node.".into()),
                             ))
                         }
                     };
 
                     let get_scan_name =
-                        |col_name: &str,
-                         plan_id: usize|
-                         -> Result<Option<String>, QueryPlannerError> {
+                        |col_name: &str, plan_id: usize| -> Result<Option<String>, SbroadError> {
                             let child = plan.get_relation_node(plan_id)?;
                             let col_position = child
                                 .output_alias_position_map(&plan.nodes)?
@@ -269,10 +271,13 @@ impl Ast for AbstractSyntaxTree {
                                         rows.insert(ref_id);
                                         map.add(*id, ref_id);
                                     } else {
-                                        return Err(QueryPlannerError::CustomError(format!(
-                                            "Column '{}' not found in for the join left child '{:?}'.",
-                                            col_name, left_name
-                                        )));
+                                        return Err(SbroadError::NotFound(
+                                            Entity::Column,
+                                            format!(
+                                                "'{}' in the join left child '{:?}'",
+                                                col_name, left_name
+                                            ),
+                                        ));
                                     }
                                 } else if right_name == ast_scan_name {
                                     let right_col_map = plan
@@ -287,19 +292,24 @@ impl Ast for AbstractSyntaxTree {
                                         rows.insert(ref_id);
                                         map.add(*id, ref_id);
                                     } else {
-                                        return Err(QueryPlannerError::CustomError(format!(
-                                            "Column '{}' not found in for the join right child '{:?}'.",
-                                            col_name, right_name
-                                        )));
+                                        return Err(SbroadError::NotFound(
+                                            Entity::Column,
+                                            format!(
+                                                "'{}' in the join right child '{:?}'",
+                                                col_name, right_name
+                                            ),
+                                        ));
                                     }
                                 } else {
-                                    return Err(QueryPlannerError::CustomError(
-                                        format!("Left and right plan nodes do not match the AST scan name: {ast_scan_name:?}"),
+                                    return Err(SbroadError::Invalid(
+                                        Entity::Plan,
+                                        Some(format!("left and right plan nodes do not match the AST scan name: {ast_scan_name:?}")),
                                     ));
                                 }
                             } else {
-                                return Err(QueryPlannerError::CustomError(
-                                    "Expected AST node to be a scan name.".into(),
+                                return Err(SbroadError::Invalid(
+                                    Entity::Node,
+                                    Some("expected AST node to be a scan name.".into()),
                                 ));
                             }
                         } else if let (Some(ast_col_name_id), None) =
@@ -331,13 +341,13 @@ impl Ast for AbstractSyntaxTree {
                                 rows.insert(ref_id);
                                 map.add(*id, ref_id);
                             }
-                            return Err(QueryPlannerError::CustomError(format!(
-                                "Column '{}' not found in for the join left or right children.",
-                                col_name
-                            )));
+                            return Err(SbroadError::NotFound(
+                                Entity::Column,
+                                format!("'{}' for the join left or right children", col_name),
+                            ));
                         } else {
-                            return Err(QueryPlannerError::CustomError(
-                                "Expected children nodes contain a column name.".into(),
+                            return Err(SbroadError::UnexpectedNumberOfValues(
+                                "expected children nodes contain a column name.".into(),
                             ));
                         };
 
@@ -355,22 +365,22 @@ impl Ast for AbstractSyntaxTree {
                             if let Type::ScanName = ast_scan.rule {
                                 let ast_scan_name = Some(normalize_name_from_sql(
                                     ast_scan.value.as_ref().ok_or_else(|| {
-                                        QueryPlannerError::CustomError(
-                                            "Expected AST node to have a non-empty scan name."
-                                                .into(),
+                                        SbroadError::UnexpectedNumberOfValues(
+                                            "empty scan name for AST node.".into(),
                                         )
                                     })?,
                                 ));
                                 let plan_scan_name = get_scan_name(&col_name, *plan_rel_id)?;
                                 if plan_scan_name != ast_scan_name {
-                                    return Err(QueryPlannerError::CustomError(
+                                    return Err(SbroadError::UnexpectedNumberOfValues(
                                             format!("Scan name for the column {:?} doesn't match: expected {plan_scan_name:?}, found {ast_scan_name:?}",
                                             get_column_name(*ast_col_id)
                                         )));
                                 }
                             } else {
-                                return Err(QueryPlannerError::CustomError(
-                                    "Expected AST node to be a scan name.".into(),
+                                return Err(SbroadError::Invalid(
+                                    Entity::Node,
+                                    Some("expected AST node to be a scan name.".into()),
                                 ));
                             };
                             col_name
@@ -380,8 +390,8 @@ impl Ast for AbstractSyntaxTree {
                             // Get the column name.
                             get_column_name(*ast_col_id)?
                         } else {
-                            return Err(QueryPlannerError::CustomError(
-                                "No child node found in the AST reference.".into(),
+                            return Err(SbroadError::UnexpectedNumberOfValues(
+                                "no child node found in the AST reference.".into(),
                             ));
                         };
 
@@ -394,12 +404,14 @@ impl Ast for AbstractSyntaxTree {
                             true,
                         )?;
                         let ref_id = *ref_list.first().ok_or_else(|| {
-                            QueryPlannerError::CustomError("Referred column is not found.".into())
+                            SbroadError::UnexpectedNumberOfValues(
+                                "Referred column is not found.".into(),
+                            )
                         })?;
                         map.add(*id, ref_id);
                     } else {
-                        return Err(QueryPlannerError::CustomError(
-                            "Expected one or two referred relational nodes, got less or more."
+                        return Err(SbroadError::UnexpectedNumberOfValues(
+                            "expected one or two referred relational nodes, got less or more."
                                 .into(),
                         ));
                     }
@@ -427,13 +439,14 @@ impl Ast for AbstractSyntaxTree {
                         plan_rel_list.push(plan_id);
                     }
                     if plan_rel_list.len() > 1 {
-                        return Err(QueryPlannerError::CustomError(
-                            "Sub-queries in projections are not implemented yet.".into(),
+                        return Err(SbroadError::NotImplemented(
+                            Entity::SubQuery,
+                            "in projections".into(),
                         ));
                     }
                     let plan_rel_id = *plan_rel_list.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Referred relational node is not found.".into(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "list of referred relational nodes is empty.".into(),
                         )
                     })?;
                     let plan_asterisk_id = plan.add_row_for_output(plan_rel_id, &[], false)?;
@@ -441,24 +454,21 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Alias => {
                     let ast_ref_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Reference node id is not found among alias children.".into(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "list of alias children is empty, Reference node id is not found."
+                                .into(),
                         )
                     })?;
                     let plan_ref_id = map.get(*ast_ref_id)?;
                     let ast_name_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Alias name node id is not found among alias children.".into(),
-                        )
+                        SbroadError::NotFound(Entity::Node, "(Alias name) with index 1".into())
                     })?;
                     let name = self
                         .nodes
                         .get_node(*ast_name_id)?
                         .value
                         .as_ref()
-                        .ok_or_else(|| {
-                            QueryPlannerError::CustomError("Alias name is not found.".into())
-                        })?;
+                        .ok_or_else(|| SbroadError::NotFound(Entity::Name, "of Alias".into()))?;
                     let plan_alias_id = plan
                         .nodes
                         .add_alias(&normalize_name_from_sql(name), plan_ref_id)?;
@@ -466,7 +476,7 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Column => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError("Column has no children.".into())
+                        SbroadError::UnexpectedNumberOfValues("Column has no children.".into())
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
                     map.add(*id, plan_child_id);
@@ -480,7 +490,7 @@ impl Ast for AbstractSyntaxTree {
                         let plan_id = if rows.get(&plan_child_id).is_some() {
                             let plan_inner_expr = plan.get_expression_node(plan_child_id)?;
                             *plan_inner_expr.get_row_list()?.first().ok_or_else(|| {
-                                QueryPlannerError::CustomError("Row is empty.".into())
+                                SbroadError::UnexpectedNumberOfValues("Row is empty.".into())
                             })?
                         } else {
                             plan_child_id
@@ -501,14 +511,13 @@ impl Ast for AbstractSyntaxTree {
                 | Type::NotEq
                 | Type::NotIn => {
                     let ast_left_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Left node id is not found among comparison children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Comparison has no children.".into())
                     })?;
                     let plan_left_id = plan.as_row(map.get(*ast_left_id)?, &mut rows)?;
                     let ast_right_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Right node id is not found among comparison children.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "that is right node with index 1 among comparison children".into(),
                         )
                     })?;
                     let plan_right_id = plan.as_row(map.get(*ast_right_id)?, &mut rows)?;
@@ -518,7 +527,10 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::IsNull | Type::IsNotNull => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(format!("{:?} has no children.", &node.rule))
+                        SbroadError::UnexpectedNumberOfValues(format!(
+                            "{:?} has no children.",
+                            &node.rule
+                        ))
                     })?;
                     let plan_child_id = plan.as_row(map.get(*ast_child_id)?, &mut rows)?;
                     let op = Unary::from_node_type(&node.rule)?;
@@ -528,21 +540,18 @@ impl Ast for AbstractSyntaxTree {
                 Type::Between => {
                     // left BETWEEN center AND right
                     let ast_left_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Left node id is not found among between children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Between has no children.".into())
                     })?;
                     let plan_left_id = plan.as_row(map.get(*ast_left_id)?, &mut rows)?;
                     let ast_center_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Center node id is not found among between children.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "(center) among between children".into(),
                         )
                     })?;
                     let plan_center_id = plan.as_row(map.get(*ast_center_id)?, &mut rows)?;
                     let ast_right_id = node.children.get(2).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Right node id is not found among between children.".into(),
-                        )
+                        SbroadError::NotFound(Entity::Node, "(right) among between children".into())
                     })?;
                     let plan_right_id = plan.as_row(map.get(*ast_right_id)?, &mut rows)?;
 
@@ -554,20 +563,22 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Cast => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError("Condition has no children.".into())
+                        SbroadError::UnexpectedNumberOfValues("Condition has no children.".into())
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
                     let ast_type_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Cast type node id is not found among cast children.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "(Cast type) among cast children".into(),
                         )
                     })?;
                     let ast_type = self.nodes.get_node(*ast_type_id)?;
                     let cast_type = if ast_type.rule == Type::TypeVarchar {
                         // Get the length of the varchar.
                         let ast_len_id = ast_type.children.first().ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Cast type length node id is not found among cast children.".into(),
+                            SbroadError::UnexpectedNumberOfValues(
+                                "Cast has no children. Cast type length node id is not found."
+                                    .into(),
                             )
                         })?;
                         let ast_len = self.nodes.get_node(*ast_len_id)?;
@@ -575,14 +586,16 @@ impl Ast for AbstractSyntaxTree {
                             .value
                             .as_ref()
                             .ok_or_else(|| {
-                                QueryPlannerError::CustomError("Varchar length is empty".into())
+                                SbroadError::UnexpectedNumberOfValues(
+                                    "Varchar length is empty".into(),
+                                )
                             })?
                             .parse::<usize>()
                             .map_err(|e| {
-                                QueryPlannerError::CustomError(format!(
-                                    "Failed to parse varchar length: {:?}",
-                                    e
-                                ))
+                                SbroadError::ParsingError(
+                                    Entity::Value,
+                                    format!("failed to parse varchar length: {e:?}"),
+                                )
                             })?;
                         Ok(CastType::Varchar(len))
                     } else {
@@ -593,15 +606,11 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Concat => {
                     let ast_left_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Left node id is not found among concat children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Concat has no children.".into())
                     })?;
                     let plan_left_id = plan.as_row(map.get(*ast_left_id)?, &mut rows)?;
                     let ast_right_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Right node id is not found among concat children.".into(),
-                        )
+                        SbroadError::NotFound(Entity::Node, "(right) among concat children".into())
                     })?;
                     let plan_right_id = plan.as_row(map.get(*ast_right_id)?, &mut rows)?;
                     let concat_id = plan.add_concat(plan_left_id, plan_right_id)?;
@@ -609,7 +618,7 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Condition => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError("Condition has no children.".into())
+                        SbroadError::UnexpectedNumberOfValues("Condition has no children.".into())
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
                     map.add(*id, plan_child_id);
@@ -623,7 +632,7 @@ impl Ast for AbstractSyntaxTree {
                         }
                         let function_name =
                             self.nodes.get_node(*first)?.value.as_ref().ok_or_else(|| {
-                                QueryPlannerError::CustomError("Function name is not found.".into())
+                                SbroadError::NotFound(Entity::Name, "of sql function".into())
                             })?;
                         let func = metadata.get_function(function_name)?;
                         if func.is_stable() {
@@ -632,33 +641,30 @@ impl Ast for AbstractSyntaxTree {
                         } else {
                             // At the moment we don't support any non-stable functions.
                             // Later this code block should handle other function behaviors.
-                            return Err(QueryPlannerError::CustomError(format!(
-                                "Function {} is not stable.",
-                                function_name
-                            )));
+                            return Err(SbroadError::Invalid(
+                                Entity::SQLFunction,
+                                Some(format!("function {function_name} is not stable.")),
+                            ));
                         }
                     } else {
-                        return Err(QueryPlannerError::CustomError(
-                            "Function has no children.".into(),
+                        return Err(SbroadError::UnexpectedNumberOfValues(
+                            "function has no children.".into(),
                         ));
                     }
                 }
                 Type::InnerJoin => {
                     let ast_left_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Left node id is not found among join children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Join has no children.".into())
                     })?;
                     let plan_left_id = map.get(*ast_left_id)?;
                     let ast_right_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Right node id is not found among join children.".into(),
-                        )
+                        SbroadError::NotFound(Entity::Node, "(right) among Join children.".into())
                     })?;
                     let plan_right_id = map.get(*ast_right_id)?;
                     let ast_cond_id = node.children.get(2).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Condition node id is not found among join children.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "(Condition) among Join children".into(),
                         )
                     })?;
                     let plan_cond_id = map.get(*ast_cond_id)?;
@@ -667,14 +673,13 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Selection => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Child node id is not found among selection children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Selection has no children.".into())
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
                     let ast_filter_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Filter node id is not found among selection children.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "(Filter) among Selection children".into(),
                         )
                     })?;
                     let plan_filter_id = map.get(*ast_filter_id)?;
@@ -683,9 +688,7 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Projection => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Child node id is not found among projection children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Projection has no children.".into())
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
                     let mut columns: Vec<usize> = Vec::new();
@@ -695,9 +698,8 @@ impl Ast for AbstractSyntaxTree {
                             Type::Column => {
                                 let ast_alias_id =
                                     *ast_column.children.first().ok_or_else(|| {
-                                        QueryPlannerError::CustomError(
-                                            "Alias node id is not found among column children."
-                                                .into(),
+                                        SbroadError::UnexpectedNumberOfValues(
+                                            "Column has no children.".into(),
                                         )
                                     })?;
                                 let plan_alias_id = map.get(ast_alias_id)?;
@@ -712,17 +714,23 @@ impl Ast for AbstractSyntaxTree {
                                         columns.push(*row_id);
                                     }
                                 } else {
-                                    return Err(QueryPlannerError::CustomError(
-                                        "A plan node corresponding to asterisk is not a row."
-                                            .into(),
+                                    return Err(SbroadError::Invalid(
+                                        Entity::Node,
+                                        Some(
+                                            "a plan node corresponding to asterisk is not a Row."
+                                                .into(),
+                                        ),
                                     ));
                                 }
                             }
                             _ => {
-                                return Err(QueryPlannerError::CustomError(format!(
-                                    "Expected a column in projection, got {:?}.",
-                                    ast_column.rule
-                                )));
+                                return Err(SbroadError::Invalid(
+                                    Entity::Type,
+                                    Some(format!(
+                                        "expected a Column in projection, got {:?}.",
+                                        ast_column.rule
+                                    )),
+                                ));
                             }
                         }
                     }
@@ -731,15 +739,11 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Except => {
                     let ast_left_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Left node id is not found among except children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Except has no children.".into())
                     })?;
                     let plan_left_id = map.get(*ast_left_id)?;
                     let ast_right_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Right node id is not found among except children.".into(),
-                        )
+                        SbroadError::NotFound(Entity::Node, "(right) among Except children".into())
                     })?;
                     let plan_right_id = map.get(*ast_right_id)?;
                     let plan_except_id = plan.add_except(plan_left_id, plan_right_id)?;
@@ -747,14 +751,13 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::UnionAll => {
                     let ast_left_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Left node id is not found among union all children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Union All has no children.".into())
                     })?;
                     let plan_left_id = map.get(*ast_left_id)?;
                     let ast_right_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Right node id is not found among union all children.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "(right) among Union All children".into(),
                         )
                     })?;
                     let plan_right_id = map.get(*ast_right_id)?;
@@ -763,7 +766,7 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::ValuesRow => {
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError("Values row has no children.".into())
+                        SbroadError::UnexpectedNumberOfValues("Values Row has no children.".into())
                     })?;
                     let plan_child_id = map.get(*ast_child_id)?;
                     let values_row_id = plan.add_values_row(plan_child_id, &mut col_idx)?;
@@ -780,27 +783,24 @@ impl Ast for AbstractSyntaxTree {
                 }
                 Type::Insert => {
                     let ast_table_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Table node id is not found among insert children.".into(),
-                        )
+                        SbroadError::UnexpectedNumberOfValues("Insert has no children.".into())
                     })?;
                     let ast_table = self.nodes.get_node(*ast_table_id)?;
                     if let Type::Table = ast_table.rule {
                     } else {
-                        return Err(QueryPlannerError::CustomError(format!(
-                            "Expected a table in insert, got {:?}.",
-                            ast_table
-                        )));
+                        return Err(SbroadError::Invalid(
+                            Entity::Type,
+                            Some(format!("expected a Table in insert, got {ast_table:?}.",)),
+                        ));
                     }
                     let relation: &str = ast_table.value.as_ref().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Table name was not found in the AST.".into(),
-                        )
+                        SbroadError::NotFound(Entity::Name, "of table in the AST".into())
                     })?;
 
                     let ast_child_id = node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Second child is not found among insert children.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "(second child) among insert children".into(),
                         )
                     })?;
                     let ast_child = self.nodes.get_node(*ast_child_id)?;
@@ -810,20 +810,23 @@ impl Ast for AbstractSyntaxTree {
                         for col_id in &ast_child.children {
                             let col = self.nodes.get_node(*col_id)?;
                             if let Type::ColumnName = col.rule {
-                                col_names.push(col.value.as_ref().ok_or_else(||
-                                        QueryPlannerError::CustomError(
-                                            "Column name was not found among the AST target columns (insert).".into(),
-                                    ))?);
+                                col_names.push(col.value.as_ref().ok_or_else(|| {
+                                    SbroadError::NotFound(
+                                        Entity::Name,
+                                        "of Column among the AST target columns (insert)".into(),
+                                    )
+                                })?);
                             } else {
-                                return Err(QueryPlannerError::CustomError(format!(
-                                    "Expected a column name in insert, got {:?}.",
-                                    col
-                                )));
+                                return Err(SbroadError::Invalid(
+                                    Entity::Type,
+                                    Some(format!("expected a Column name in insert, got {col:?}.")),
+                                ));
                             }
                         }
                         let ast_rel_child_id = node.children.get(2).ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Third child is not found among insert children.".into(),
+                            SbroadError::NotFound(
+                                Entity::Node,
+                                "(third child) among Insert children".into(),
                             )
                         })?;
                         let plan_rel_child_id = map.get(*ast_rel_child_id)?;
@@ -839,7 +842,7 @@ impl Ast for AbstractSyntaxTree {
                     plan.mark_as_explain();
 
                     let ast_child_id = node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError("Explain has no children.".into())
+                        SbroadError::UnexpectedNumberOfValues("Explain has no children.".into())
                     })?;
                     map.add(0, map.get(*ast_child_id)?);
                 }
@@ -863,19 +866,19 @@ impl Ast for AbstractSyntaxTree {
                 | Type::TypeUnsigned
                 | Type::TypeVarchar => {}
                 rule => {
-                    return Err(QueryPlannerError::CustomError(format!(
-                        "Not implements type: {:?}",
-                        rule
-                    )));
+                    return Err(SbroadError::NotImplemented(
+                        Entity::Type,
+                        format!("{rule:?}"),
+                    ));
                 }
             }
         }
 
         // get root node id
-        let plan_top_id = map.get(
-            self.top
-                .ok_or_else(|| QueryPlannerError::CustomError("No top in AST.".into()))?,
-        )?;
+        let plan_top_id = map
+            .get(self.top.ok_or_else(|| {
+                SbroadError::Invalid(Entity::AST, Some("no top in AST".into()))
+            })?)?;
         plan.set_top(plan_top_id)?;
         let replaces = plan.replace_sq_with_references()?;
         plan.fix_betweens(&betweens, &replaces)?;
@@ -886,11 +889,7 @@ impl Ast for AbstractSyntaxTree {
 
 impl Plan {
     /// Wrap references, constants, functions, concatenations and casts in the plan into rows.
-    fn as_row(
-        &mut self,
-        expr_id: usize,
-        rows: &mut HashSet<usize>,
-    ) -> Result<usize, QueryPlannerError> {
+    fn as_row(&mut self, expr_id: usize, rows: &mut HashSet<usize>) -> Result<usize, SbroadError> {
         if let Node::Expression(
             Expression::Reference { .. }
             | Expression::Constant { .. }
diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs
index 1f954ee95a..98fad9d621 100644
--- a/sbroad-core/src/frontend/sql/ast.rs
+++ b/sbroad-core/src/frontend/sql/ast.rs
@@ -12,7 +12,7 @@ use pest::iterators::Pair;
 use serde::{Deserialize, Serialize};
 use traversal::DftPost;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 
 /// Parse tree
 #[derive(Parser)]
@@ -94,7 +94,7 @@ pub enum Type {
 
 impl Type {
     #[allow(dead_code)]
-    fn from_rule(rule: Rule) -> Result<Self, QueryPlannerError> {
+    fn from_rule(rule: Rule) -> Result<Self, SbroadError> {
         match rule {
             Rule::Alias => Ok(Type::Alias),
             Rule::AliasName => Ok(Type::AliasName),
@@ -161,7 +161,10 @@ impl Type {
             Rule::Value => Ok(Type::Value),
             Rule::Values => Ok(Type::Values),
             Rule::ValuesRow => Ok(Type::ValuesRow),
-            _ => Err(QueryPlannerError::InvalidAst),
+            _ => Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("got unexpected rule".into()),
+            )),
         }
     }
 }
@@ -176,7 +179,7 @@ pub struct ParseNode {
 
 #[allow(dead_code)]
 impl ParseNode {
-    pub(super) fn new(rule: Rule, value: Option<String>) -> Result<Self, QueryPlannerError> {
+    pub(super) fn new(rule: Rule, value: Option<String>) -> Result<Self, SbroadError> {
         Ok(ParseNode {
             children: vec![],
             rule: Type::from_rule(rule)?,
@@ -204,18 +207,23 @@ impl ParseNodes {
     ///
     /// # Errors
     /// - Failed to get a node from arena.
-    pub fn get_node(&self, node: usize) -> Result<&ParseNode, QueryPlannerError> {
-        self.arena.get(node).ok_or(QueryPlannerError::InvalidNode)
+    pub fn get_node(&self, node: usize) -> Result<&ParseNode, SbroadError> {
+        self.arena.get(node).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, format!("from arena with index {node}"))
+        })
     }
 
     /// Get a mutable node from arena
     ///
     /// # Errors
     /// - Failed to get a node from arena.
-    pub fn get_mut_node(&mut self, node: usize) -> Result<&mut ParseNode, QueryPlannerError> {
-        self.arena
-            .get_mut(node)
-            .ok_or(QueryPlannerError::InvalidNode)
+    pub fn get_mut_node(&mut self, node: usize) -> Result<&mut ParseNode, SbroadError> {
+        self.arena.get_mut(node).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {node}"),
+            )
+        })
     }
 
     /// Push a new node to arena
@@ -243,17 +251,15 @@ impl ParseNodes {
     ///
     /// # Errors
     /// - Failed to retrieve node from arena.
-    pub fn add_child(
-        &mut self,
-        node: Option<usize>,
-        child: usize,
-    ) -> Result<(), QueryPlannerError> {
+    pub fn add_child(&mut self, node: Option<usize>, child: usize) -> Result<(), SbroadError> {
         if let Some(parent) = node {
             self.get_node(child)?;
-            let parent_node = self
-                .arena
-                .get_mut(parent)
-                .ok_or(QueryPlannerError::InvalidNode)?;
+            let parent_node = self.arena.get_mut(parent).ok_or_else(|| {
+                SbroadError::NotFound(
+                    Entity::Node,
+                    format!("(mutable) from arena with index {parent}"),
+                )
+            })?;
             parent_node.children.insert(0, child);
         }
         Ok(())
@@ -263,15 +269,13 @@ impl ParseNodes {
     ///
     /// # Errors
     /// - Target node is present in the arena.
-    pub fn update_value(
-        &mut self,
-        node: usize,
-        value: Option<String>,
-    ) -> Result<(), QueryPlannerError> {
-        let mut node = self
-            .arena
-            .get_mut(node)
-            .ok_or(QueryPlannerError::InvalidNode)?;
+    pub fn update_value(&mut self, node: usize, value: Option<String>) -> Result<(), SbroadError> {
+        let mut node = self.arena.get_mut(node).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {node}"),
+            )
+        })?;
         node.value = value;
         Ok(())
     }
@@ -310,7 +314,7 @@ impl AbstractSyntaxTree {
     ///
     /// # Errors
     /// - The new top is not in the arena.
-    pub fn set_top(&mut self, top: usize) -> Result<(), QueryPlannerError> {
+    pub fn set_top(&mut self, top: usize) -> Result<(), SbroadError> {
         self.nodes.get_node(top)?;
         self.top = Some(top);
         Ok(())
@@ -320,26 +324,33 @@ impl AbstractSyntaxTree {
     ///
     /// # Errors
     /// - AST tree doesn't have a top node.
-    pub fn get_top(&self) -> Result<usize, QueryPlannerError> {
-        self.top
-            .ok_or_else(|| QueryPlannerError::CustomError("No top node found in AST".to_string()))
+    pub fn get_top(&self) -> Result<usize, SbroadError> {
+        self.top.ok_or_else(|| {
+            SbroadError::Invalid(Entity::AST, Some("no top node found in AST".into()))
+        })
     }
 
     /// Serialize AST from YAML.
     ///
     /// # Errors
     /// - Failed to parse YAML.
-    pub fn from_yaml(s: &str) -> Result<Self, QueryPlannerError> {
+    pub fn from_yaml(s: &str) -> Result<Self, SbroadError> {
         let ast: AbstractSyntaxTree = match serde_yaml::from_str(s) {
             Ok(p) => p,
-            Err(_) => return Err(QueryPlannerError::Serialization),
+            Err(e) => {
+                return Err(SbroadError::FailedTo(
+                    Action::Serialize,
+                    Some(Entity::AST),
+                    format!("{e:?}"),
+                ))
+            }
         };
         Ok(ast)
     }
 
     /// `Select` node is not IR-friendly as it can have up to five children.
     /// Transform this node in IR-way (to a binary sub-tree).
-    pub(super) fn transform_select(&mut self) -> Result<(), QueryPlannerError> {
+    pub(super) fn transform_select(&mut self) -> Result<(), SbroadError> {
         let mut selects: HashSet<usize> = HashSet::new();
         for (id, node) in self.nodes.arena.iter().enumerate() {
             if node.rule == Type::Select {
@@ -354,7 +365,7 @@ impl AbstractSyntaxTree {
                 3 => self.transform_select_3(*node, &children)?,
                 4 => self.transform_select_4(*node, &children)?,
                 5 => self.transform_select_5(*node, &children)?,
-                _ => return Err(QueryPlannerError::InvalidAst),
+                _ => return Err(SbroadError::Invalid(Entity::AST, None)),
             }
         }
 
@@ -371,15 +382,14 @@ impl AbstractSyntaxTree {
         for (parent_id, children_pos) in parents {
             let parent = self.nodes.get_node(parent_id)?;
             let child_id = *parent.children.get(children_pos).ok_or_else(|| {
-                QueryPlannerError::CustomError(
-                    "Parent node doesn't contain a child at expected position.".into(),
+                SbroadError::NotFound(
+                    Entity::Node,
+                    format!("at expected position {}", children_pos),
                 )
             })?;
             let child = self.nodes.get_node(child_id)?;
             let mut node_id = *child.children.first().ok_or_else(|| {
-                QueryPlannerError::CustomError(
-                    "Selection node doesn't contain any children.".into(),
-                )
+                SbroadError::UnexpectedNumberOfValues("Selection node has no children.".into())
             })?;
             let parent = self.nodes.get_mut_node(parent_id)?;
             swap(&mut parent.children[children_pos], &mut node_id);
@@ -389,7 +399,7 @@ impl AbstractSyntaxTree {
         if selects.contains(&top_id) {
             let top = self.nodes.get_node(top_id)?;
             let child_id = *top.children.first().ok_or_else(|| {
-                QueryPlannerError::CustomError(
+                SbroadError::UnexpectedNumberOfValues(
                     "Selection node doesn't contain any children.".into(),
                 )
             })?;
@@ -403,38 +413,53 @@ impl AbstractSyntaxTree {
         &mut self,
         select_id: usize,
         children: &[usize],
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         if children.len() != 2 {
-            return Err(QueryPlannerError::InvalidInput);
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                "expect children list len 2, got {}",
+                children.len()
+            )));
         }
 
         // Check that the second child is `Scan`.
-        let scan_id: usize = *children.get(1).ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let scan_id: usize = *children.get(1).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, "from children list with index 1".into())
+        })?;
         let scan = self.nodes.get_node(scan_id)?;
         if scan.rule != Type::Scan {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("scan.rule is not Scan type".into()),
+            ));
         }
 
         // Check that the first child is `Projection`.
-        let proj_id: usize = *children.first().ok_or(QueryPlannerError::ValueOutOfRange)?;
-        let proj = self
-            .nodes
-            .arena
-            .get_mut(proj_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let proj_id: usize = *children.first().ok_or_else(|| {
+            SbroadError::UnexpectedNumberOfValues("children list is empty".into())
+        })?;
+        let proj = self.nodes.arena.get_mut(proj_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {proj_id}"),
+            )
+        })?;
         if proj.rule != Type::Projection {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("proj.rule is not Projection type".into()),
+            ));
         }
 
         // Append `Scan` to the `Projection` children (zero position)
         proj.children.insert(0, scan_id);
 
         // Leave `Projection` the only child of `Select`.
-        let mut select = self
-            .nodes
-            .arena
-            .get_mut(select_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let mut select = self.nodes.arena.get_mut(select_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {select_id}"),
+            )
+        })?;
         select.children = vec![proj_id];
 
         Ok(())
@@ -445,52 +470,73 @@ impl AbstractSyntaxTree {
         &mut self,
         select_id: usize,
         children: &[usize],
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         if children.len() != 3 {
-            return Err(QueryPlannerError::InvalidInput);
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                "expect children list len 3, got {}",
+                children.len()
+            )));
         }
 
         // Check that the second child is `Scan`.
-        let scan_id: usize = *children.get(1).ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let scan_id: usize = *children.get(1).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, "from children list with index 1".into())
+        })?;
         let scan = self.nodes.get_node(scan_id)?;
         if scan.rule != Type::Scan {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("scan.rule is not Scan type".into()),
+            ));
         }
 
         // Check that the third child is `Selection`.
-        let selection_id: usize = *children.get(2).ok_or(QueryPlannerError::ValueOutOfRange)?;
-        let selection = self
-            .nodes
-            .arena
-            .get_mut(selection_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let selection_id: usize = *children.get(2).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, "from children list with index 2".into())
+        })?;
+        let selection = self.nodes.arena.get_mut(selection_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {selection_id}"),
+            )
+        })?;
         if selection.rule != Type::Selection {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("selection.rule is not Selection type".into()),
+            ));
         }
 
         // Append `Scan` to the `Selection` children (zero position)
         selection.children.insert(0, scan_id);
 
         // Check that the first child is `Projection`.
-        let proj_id: usize = *children.first().ok_or(QueryPlannerError::ValueOutOfRange)?;
-        let proj = self
-            .nodes
-            .arena
-            .get_mut(proj_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let proj_id: usize = *children.first().ok_or_else(|| {
+            SbroadError::UnexpectedNumberOfValues("children list is empty".into())
+        })?;
+        let proj = self.nodes.arena.get_mut(proj_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {proj_id}"),
+            )
+        })?;
         if proj.rule != Type::Projection {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("proj.rule is not Projection type".into()),
+            ));
         }
 
         // Append `Selection` to the `Projection` children (zero position)
         proj.children.insert(0, selection_id);
 
         // Leave `Projection` the only child of `Select`.
-        let mut select = self
-            .nodes
-            .arena
-            .get_mut(select_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let mut select = self.nodes.arena.get_mut(select_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {select_id}"),
+            )
+        })?;
         select.children = vec![proj_id];
 
         Ok(())
@@ -501,34 +547,53 @@ impl AbstractSyntaxTree {
         &mut self,
         select_id: usize,
         children: &[usize],
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         if children.len() != 4 {
-            return Err(QueryPlannerError::InvalidInput);
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                "expect children list len 4, got {}",
+                children.len()
+            )));
         }
 
         // Check that the second child is `Scan`.
-        let scan_id: usize = *children.get(1).ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let scan_id: usize = *children.get(1).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, "from children list with index 1".into())
+        })?;
         let scan = self.nodes.get_node(scan_id)?;
         if scan.rule != Type::Scan {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("scan.rule is not Scan type".into()),
+            ));
         }
 
         // Check that the forth child is `Condition`.
-        let cond_id: usize = *children.get(3).ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let cond_id: usize = *children.get(3).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, "from children list with index 3".into())
+        })?;
         let cond = self.nodes.get_node(cond_id)?;
         if cond.rule != Type::Condition {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("cond.rule is not Condition type".into()),
+            ));
         }
 
         // Check that the third child is `InnerJoin`.
-        let join_id: usize = *children.get(2).ok_or(QueryPlannerError::ValueOutOfRange)?;
-        let join = self
-            .nodes
-            .arena
-            .get_mut(join_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let join_id: usize = *children
+            .get(2)
+            .ok_or_else(|| SbroadError::NotFound(Entity::Node, "with index 2".into()))?;
+        let join = self.nodes.arena.get_mut(join_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {join_id}"),
+            )
+        })?;
         if join.rule != Type::InnerJoin {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("join.rule is not InnerJoin type".into()),
+            ));
         }
 
         // Push `Condition` (forth child) to the end of th `InnerJoin` children list.
@@ -538,25 +603,32 @@ impl AbstractSyntaxTree {
         join.children.insert(0, scan_id);
 
         // Check that the first child is `Projection`.
-        let proj_id: usize = *children.first().ok_or(QueryPlannerError::ValueOutOfRange)?;
-        let proj = self
-            .nodes
-            .arena
-            .get_mut(proj_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let proj_id: usize = *children.first().ok_or_else(|| {
+            SbroadError::UnexpectedNumberOfValues("children list is empty".into())
+        })?;
+        let proj = self.nodes.arena.get_mut(proj_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {proj_id}"),
+            )
+        })?;
         if proj.rule != Type::Projection {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("proj.rule is not Projection type".into()),
+            ));
         }
 
         // Append `InnerJoin` to the `Projection` children (zero position)
         proj.children.insert(0, join_id);
 
         // Leave `Projection` the only child of `Select`.
-        let mut select = self
-            .nodes
-            .arena
-            .get_mut(select_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let mut select = self.nodes.arena.get_mut(select_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {select_id}"),
+            )
+        })?;
         select.children = vec![proj_id];
 
         Ok(())
@@ -567,34 +639,53 @@ impl AbstractSyntaxTree {
         &mut self,
         select_id: usize,
         children: &[usize],
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         if children.len() != 5 {
-            return Err(QueryPlannerError::InvalidInput);
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                "expect children list len 5, got {}",
+                children.len()
+            )));
         }
 
         // Check that the second child is `Scan`.
-        let scan_id: usize = *children.get(1).ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let scan_id: usize = *children.get(1).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, "from children list with index 1".into())
+        })?;
         let scan = self.nodes.get_node(scan_id)?;
         if scan.rule != Type::Scan {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("scan.rule is not Scan type".into()),
+            ));
         }
 
         // Check that the forth child is `Condition`.
-        let cond_id: usize = *children.get(3).ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let cond_id: usize = *children.get(3).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, "from children list with index 3".into())
+        })?;
         let cond = self.nodes.get_node(cond_id)?;
         if cond.rule != Type::Condition {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("cond.rule is not Condition type".into()),
+            ));
         }
 
         // Check that the third child is `InnerJoin`.
-        let join_id: usize = *children.get(2).ok_or(QueryPlannerError::ValueOutOfRange)?;
-        let join = self
-            .nodes
-            .arena
-            .get_mut(join_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let join_id: usize = *children.get(2).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, "from children list with index 2".into())
+        })?;
+        let join = self.nodes.arena.get_mut(join_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {join_id}"),
+            )
+        })?;
         if join.rule != Type::InnerJoin {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("join.rule is not InnerJoin type".into()),
+            ));
         }
 
         // Push `Condition` (forth child) to the end of the `InnerJoin` children list.
@@ -604,39 +695,52 @@ impl AbstractSyntaxTree {
         join.children.insert(0, scan_id);
 
         // Check that the fifth child is `Selection`.
-        let selection_id: usize = *children.get(4).ok_or(QueryPlannerError::ValueOutOfRange)?;
-        let selection = self
-            .nodes
-            .arena
-            .get_mut(selection_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let selection_id: usize = *children.get(4).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, "from children list with index 4".into())
+        })?;
+        let selection = self.nodes.arena.get_mut(selection_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {selection_id}"),
+            )
+        })?;
         if selection.rule != Type::Selection {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("selection.rule is not Selection type".into()),
+            ));
         }
 
         // Append `InnerJoin` to the `Selection` children (zero position)
         selection.children.insert(0, join_id);
 
         // Check that the first child is `Projection`.
-        let proj_id: usize = *children.first().ok_or(QueryPlannerError::ValueOutOfRange)?;
-        let proj = self
-            .nodes
-            .arena
-            .get_mut(proj_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let proj_id: usize = *children.first().ok_or_else(|| {
+            SbroadError::UnexpectedNumberOfValues("children list is empty".into())
+        })?;
+        let proj = self.nodes.arena.get_mut(proj_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {proj_id}"),
+            )
+        })?;
         if proj.rule != Type::Projection {
-            return Err(QueryPlannerError::InvalidAst);
+            return Err(SbroadError::Invalid(
+                Entity::AST,
+                Some("proj.rule is not Projection type".into()),
+            ));
         }
 
         // Append `Selection` to the `Projection` children (zero position)
         proj.children.insert(0, selection_id);
 
         // Leave `Projection` the only child of `Select`.
-        let mut select = self
-            .nodes
-            .arena
-            .get_mut(select_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?;
+        let mut select = self.nodes.arena.get_mut(select_id).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {select_id}"),
+            )
+        })?;
         select.children = vec![proj_id];
         Ok(())
     }
@@ -645,7 +749,7 @@ impl AbstractSyntaxTree {
     ///
     /// # Errors
     /// - columns are invalid
-    pub(super) fn add_aliases_to_projection(&mut self) -> Result<(), QueryPlannerError> {
+    pub(super) fn add_aliases_to_projection(&mut self) -> Result<(), SbroadError> {
         let mut columns: Vec<(usize, Option<String>)> = Vec::new();
         // Collect projection columns and their names.
         for (_, node) in self.nodes.arena.iter().enumerate() {
@@ -655,9 +759,7 @@ impl AbstractSyntaxTree {
                     let child = self.nodes.get_node(*child_id)?;
                     if let Type::Column = child.rule {
                         let col_child_id = *child.children.first().ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Column doesn't have any children".into(),
-                            )
+                            SbroadError::UnexpectedNumberOfValues("Column has no children".into())
                         })?;
                         let col_child = self.nodes.get_node(col_child_id)?;
                         match &col_child.rule {
@@ -674,8 +776,9 @@ impl AbstractSyntaxTree {
                                 {
                                     *col_name_id
                                 } else {
-                                    return Err(QueryPlannerError::CustomError(
-                                        "Column doesn't have any children".into(),
+                                    return Err(SbroadError::NotFound(
+                                        Entity::Node,
+                                        "that is first child of the Column".into(),
                                     ));
                                 };
                                 let col_name = self.nodes.get_node(col_name_id)?;
@@ -683,8 +786,9 @@ impl AbstractSyntaxTree {
                                     .value
                                     .as_ref()
                                     .ok_or_else(|| {
-                                        QueryPlannerError::CustomError(
-                                            "Column name is empty".into(),
+                                        SbroadError::Invalid(
+                                            Entity::Name,
+                                            Some("of Column is empty".into()),
                                         )
                                     })?
                                     .clone();
@@ -702,12 +806,13 @@ impl AbstractSyntaxTree {
         for (id, name) in columns {
             let node = self.nodes.get_node(id)?;
             if node.rule != Type::Column {
-                return Err(QueryPlannerError::CustomError(
-                    "Parsed node is not a column.".into(),
+                return Err(SbroadError::Invalid(
+                    Entity::Node,
+                    Some("Parsed node is not a column.".into()),
                 ));
             }
             let child_id = *node.children.first().ok_or_else(|| {
-                QueryPlannerError::CustomError("Column doesn't have any children".into())
+                SbroadError::UnexpectedNumberOfValues("Column has no children".into())
             })?;
             let child = self.nodes.get_node(child_id)?;
             if let Type::Alias | Type::Asterisk = child.rule {
@@ -731,7 +836,7 @@ impl AbstractSyntaxTree {
     ///
     /// # Errors
     /// - Projection, selection and inner join nodes don't have valid children.
-    pub(super) fn build_ref_to_relation_map(&mut self) -> Result<(), QueryPlannerError> {
+    pub(super) fn build_ref_to_relation_map(&mut self) -> Result<(), SbroadError> {
         let mut map: HashMap<usize, Vec<usize>> = HashMap::new();
         // Traverse relational nodes in Post Order and then enter their subtrees
         // and map expressions to relational nodes.
@@ -742,8 +847,8 @@ impl AbstractSyntaxTree {
             match rel_node.rule {
                 Type::Projection => {
                     let rel_id = rel_node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "AST projection doesn't have any children.".into(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "AST Projection has no children.".into(),
                         )
                     })?;
                     for top in rel_node.children.iter().skip(1) {
@@ -760,13 +865,14 @@ impl AbstractSyntaxTree {
                 }
                 Type::Selection => {
                     let rel_id = rel_node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "AST selection doesn't have any children.".into(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "AST selection has no children.".into(),
                         )
                     })?;
                     let filter = rel_node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "AST selection doesn't have a filter child.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "that is AST selection filter child with index 1".into(),
                         )
                     })?;
                     let subtree = DftPost::new(filter, |node| self.nodes.ast_iter(node));
@@ -781,18 +887,20 @@ impl AbstractSyntaxTree {
                 }
                 Type::InnerJoin => {
                     let left_id = rel_node.children.first().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "AST inner join doesn't have a left child.".into(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "AST inner join has no children.".into(),
                         )
                     })?;
                     let right_id = rel_node.children.get(1).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "AST inner join doesn't have a right child.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "that is AST inner join right child with index 1".into(),
                         )
                     })?;
                     let cond_id = rel_node.children.get(2).ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "AST inner join doesn't have a condition child.".into(),
+                        SbroadError::NotFound(
+                            Entity::Node,
+                            "that is AST inner join condition child with index 2".into(),
                         )
                     })?;
                     // ast_iter is not working here - we have to ignore sub-queries in the join condition.
@@ -817,14 +925,11 @@ impl AbstractSyntaxTree {
     ///
     /// # Errors
     /// - Reference is not found.
-    pub fn get_referred_relational_nodes(
-        &self,
-        id: usize,
-    ) -> Result<Vec<usize>, QueryPlannerError> {
+    pub fn get_referred_relational_nodes(&self, id: usize) -> Result<Vec<usize>, SbroadError> {
         self.map
             .get(&id)
             .cloned()
-            .ok_or_else(|| QueryPlannerError::CustomError("Reference is not found.".into()))
+            .ok_or_else(|| SbroadError::NotFound(Entity::Relational, format!("(id {id})")))
     }
 }
 
diff --git a/sbroad-core/src/frontend/sql/ast/tests.rs b/sbroad-core/src/frontend/sql/ast/tests.rs
index a2edc6feeb..caf4329ade 100644
--- a/sbroad-core/src/frontend/sql/ast/tests.rs
+++ b/sbroad-core/src/frontend/sql/ast/tests.rs
@@ -18,6 +18,12 @@ fn ast() {
     let s = fs::read_to_string(path).unwrap();
     let expected: AbstractSyntaxTree = AbstractSyntaxTree::from_yaml(&s).unwrap();
     assert_eq!(expected, ast);
+
+    let empty_yaml = "";
+    assert_eq!(
+        SbroadError::FailedTo(Action::Serialize, Some(Entity::AST), "EndOfStream".into(),),
+        AbstractSyntaxTree::from_yaml(empty_yaml).unwrap_err()
+    );
 }
 
 #[test]
@@ -148,7 +154,7 @@ fn invalid_query() {
     let ast = AbstractSyntaxTree::new(query).unwrap_err();
     assert_eq!(
         format!(
-            r#"Parsing error:  --> 1:8
+            r#"rule parsing error:  --> 1:8
   |
 1 | select a frAm t
   |        ^---
@@ -166,13 +172,13 @@ fn invalid_condition() {
     let ast = AbstractSyntaxTree::new(query).unwrap_err();
     assert_eq!(
         format!(
-            r#"Parsing error:  --> 2:33
+            r#"rule parsing error:  --> 2:33
   |
 2 |     "identification_number" = 1 "product_code" = 2
   |                                 ^---
   |
   = expected EOI"#,
         ),
-        format!("{}", ast),
+        format!("{ast}"),
     )
 }
diff --git a/sbroad-core/src/frontend/sql/ir.rs b/sbroad-core/src/frontend/sql/ir.rs
index 5a44249c14..5791ae48b3 100644
--- a/sbroad-core/src/frontend/sql/ir.rs
+++ b/sbroad-core/src/frontend/sql/ir.rs
@@ -4,7 +4,7 @@ use ahash::AHashMap;
 use tarantool::decimal::Decimal;
 use traversal::DftPost;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 use crate::frontend::sql::ast::{ParseNode, Type};
 use crate::ir::expression::Expression;
 use crate::ir::helpers::RepeatableState;
@@ -19,9 +19,9 @@ impl Bool {
     /// Creates `Bool` from ast node type.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the operator is invalid.
+    /// Returns `SbroadError` when the operator is invalid.
     #[allow(dead_code)]
-    pub(super) fn from_node_type(s: &Type) -> Result<Self, QueryPlannerError> {
+    pub(super) fn from_node_type(s: &Type) -> Result<Self, SbroadError> {
         match s {
             Type::And => Ok(Bool::And),
             Type::Or => Ok(Bool::Or),
@@ -33,7 +33,10 @@ impl Bool {
             Type::LtEq => Ok(Bool::LtEq),
             Type::NotEq => Ok(Bool::NotEq),
             Type::NotIn => Ok(Bool::NotIn),
-            _ => Err(QueryPlannerError::InvalidBool),
+            _ => Err(SbroadError::Invalid(
+                Entity::Operator,
+                Some(format!("bool operator: {s:?}")),
+            )),
         }
     }
 }
@@ -42,16 +45,16 @@ impl Unary {
     /// Creates `Unary` from ast node type.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the operator is invalid.
+    /// Returns `SbroadError` when the operator is invalid.
     #[allow(dead_code)]
-    pub(super) fn from_node_type(s: &Type) -> Result<Self, QueryPlannerError> {
+    pub(super) fn from_node_type(s: &Type) -> Result<Self, SbroadError> {
         match s {
             Type::IsNull => Ok(Unary::IsNull),
             Type::IsNotNull => Ok(Unary::IsNotNull),
-            _ => Err(QueryPlannerError::CustomError(format!(
-                "Invalid unary operator: {:?}",
-                s
-            ))),
+            _ => Err(SbroadError::Invalid(
+                Entity::Operator,
+                Some(format!("unary operator: {s:?}")),
+            )),
         }
     }
 }
@@ -60,9 +63,9 @@ impl Value {
     /// Creates `Value` from ast node type and text.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the operator is invalid.
+    /// Returns `SbroadError` when the operator is invalid.
     #[allow(dead_code)]
-    pub(super) fn from_node(s: &ParseNode) -> Result<Self, QueryPlannerError> {
+    pub(super) fn from_node(s: &ParseNode) -> Result<Self, SbroadError> {
         let val = match &s.value {
             Some(v) => v.clone(),
             None => String::new(),
@@ -73,25 +76,34 @@ impl Value {
             Type::Null => Ok(Value::Null),
             Type::Integer => Ok(val
                 .parse::<i64>()
-                .map_err(|e| QueryPlannerError::CustomError(format!("i64 parsing error {e}")))?
+                .map_err(|e| {
+                    SbroadError::ParsingError(Entity::Value, format!("i64 parsing error {e}"))
+                })?
                 .into()),
             Type::Decimal => Ok(val
                 .parse::<Decimal>()
                 .map_err(|e| {
-                    QueryPlannerError::CustomError(format!("decimal parsing error {e:?}"))
+                    SbroadError::ParsingError(Entity::Value, format!("decimal parsing error {e:?}"))
                 })?
                 .into()),
             Type::Double => Ok(val
                 .parse::<Double>()
-                .map_err(|e| QueryPlannerError::CustomError(format!("double parsing error {e}")))?
+                .map_err(|e| {
+                    SbroadError::ParsingError(Entity::Value, format!("double parsing error {e}"))
+                })?
                 .into()),
             Type::Unsigned => Ok(val
                 .parse::<u64>()
-                .map_err(|e| QueryPlannerError::CustomError(format!("u64 parsing error {e}")))?
+                .map_err(|e| {
+                    SbroadError::ParsingError(Entity::Value, format!("u64 parsing error {e}"))
+                })?
                 .into()),
             Type::String => Ok(val.into()),
             Type::True => Ok(true.into()),
-            _ => Err(QueryPlannerError::UnsupportedValueType),
+            _ => Err(SbroadError::Unsupported(
+                Entity::Type,
+                Some("can not create Value from ParseNode".into()),
+            )),
         }
     }
 }
@@ -112,12 +124,12 @@ impl Translation {
         self.map.insert(parse_id, plan_id);
     }
 
-    pub(super) fn get(&self, old: usize) -> Result<usize, QueryPlannerError> {
+    pub(super) fn get(&self, old: usize) -> Result<usize, SbroadError> {
         self.map.get(&old).copied().ok_or_else(|| {
-            QueryPlannerError::CustomError(format!(
-                "Could not find parse node [{}] in translation map",
-                old
-            ))
+            SbroadError::NotFound(
+                Entity::Node,
+                format!("(parse node) [{}] in translation map", old),
+            )
         })
     }
 }
@@ -141,9 +153,7 @@ impl SubQuery {
 }
 
 impl Plan {
-    fn gather_sq_for_replacement(
-        &self,
-    ) -> Result<HashSet<SubQuery, RepeatableState>, QueryPlannerError> {
+    fn gather_sq_for_replacement(&self) -> Result<HashSet<SubQuery, RepeatableState>, SbroadError> {
         let mut set: HashSet<SubQuery, RepeatableState> = HashSet::with_hasher(RepeatableState);
         let top = self.get_top()?;
         let rel_post = DftPost::new(&top, |node| self.nodes.rel_iter(node));
@@ -182,7 +192,7 @@ impl Plan {
     /// Replace sub-queries with references to the sub-query.
     pub(super) fn replace_sq_with_references(
         &mut self,
-    ) -> Result<AHashMap<usize, usize>, QueryPlannerError> {
+    ) -> Result<AHashMap<usize, usize>, SbroadError> {
         let set = self.gather_sq_for_replacement()?;
         let mut replaces: AHashMap<usize, usize> = AHashMap::with_capacity(set.len());
         for sq in set {
@@ -197,8 +207,9 @@ impl Plan {
                     }
                 }
                 _ => {
-                    return Err(QueryPlannerError::CustomError(
-                        "Sub-query is not in selection or join node".into(),
+                    return Err(SbroadError::Invalid(
+                        Entity::Relational,
+                        Some("Sub-query is not in selection or join node".into()),
                     ))
                 }
             }
@@ -220,22 +231,26 @@ impl Plan {
                         // TODO: should we add current row_id to the set of the generated rows?
                         let position: usize =
                             children.iter().position(|&x| x == sq.sq).ok_or_else(|| {
-                                QueryPlannerError::CustomError(
-                                    "Failed to generate a reference to the sub-query".into(),
+                                SbroadError::FailedTo(
+                                    Action::Build,
+                                    None,
+                                    "a reference to the sub-query".into(),
                                 )
                             })?;
                         let row_id = self.add_row_from_sub_query(&nodes, position, &names_str)?;
                         self.replace_parent_in_subtree(row_id, None, Some(sq.relational))?;
                         row_id
                     } else {
-                        return Err(QueryPlannerError::CustomError(
-                            "Sub-query output is not a row".into(),
+                        return Err(SbroadError::Invalid(
+                            Entity::Expression,
+                            Some("Sub-query output is not a row".into()),
                         ));
                     }
                 }
                 _ => {
-                    return Err(QueryPlannerError::CustomError(
-                        "Sub-query is not in selection or join node".into(),
+                    return Err(SbroadError::Invalid(
+                        Entity::Node,
+                        Some("Sub-query is not in selection or join node".into()),
                     ))
                 }
             };
@@ -253,14 +268,16 @@ impl Plan {
                 } else if *right == sq.sq {
                     *right = row_id;
                 } else {
-                    return Err(QueryPlannerError::CustomError(
-                        "Sub-query is not a left or right operand".into(),
+                    return Err(SbroadError::Invalid(
+                        Entity::Expression,
+                        Some("Sub-query is not a left or right operand".into()),
                     ));
                 }
                 replaces.insert(sq.sq, row_id);
             } else {
-                return Err(QueryPlannerError::CustomError(
-                    "Sub-query is not in a boolean expression".into(),
+                return Err(SbroadError::Invalid(
+                    Entity::Expression,
+                    Some("Sub-query is not in a boolean expression".into()),
                 ));
             }
         }
@@ -281,7 +298,7 @@ impl Plan {
         &mut self,
         betweens: &[Between],
         replaces: &AHashMap<usize, usize>,
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         for between in betweens {
             let left_id: usize = if let Some(id) = replaces.get(&between.left_id) {
                 self.clone_expr_subtree(*id)?
@@ -292,15 +309,16 @@ impl Plan {
             if let Expression::Bool { ref mut left, .. } = less_eq_expr {
                 *left = left_id;
             } else {
-                return Err(QueryPlannerError::CustomError(
-                    "Expected a boolean expression".into(),
+                return Err(SbroadError::Invalid(
+                    Entity::Expression,
+                    Some("expected a boolean Expression".into()),
                 ));
             }
         }
         Ok(())
     }
 
-    fn clone_expr_subtree(&mut self, top_id: usize) -> Result<usize, QueryPlannerError> {
+    fn clone_expr_subtree(&mut self, top_id: usize) -> Result<usize, SbroadError> {
         let subtree = DftPost::new(&top_id, |node| self.nodes.expr_iter(node, false));
         let nodes = subtree.map(|(_, node_id)| *node_id).collect::<Vec<_>>();
         let mut map = HashMap::new();
@@ -313,9 +331,7 @@ impl Plan {
                 | Expression::Cast { ref mut child, .. }
                 | Expression::Unary { ref mut child, .. } => {
                     *child = *map.get(child).ok_or_else(|| {
-                        QueryPlannerError::CustomError(format!(
-                            "Failed to clone expression subtree (id {id})"
-                        ))
+                        SbroadError::NotFound(Entity::SubTree, format!("(id {id})"))
                     })?;
                 }
                 Expression::Bool {
@@ -329,14 +345,10 @@ impl Plan {
                     ..
                 } => {
                     *left = *map.get(left).ok_or_else(|| {
-                        QueryPlannerError::CustomError(format!(
-                            "Failed to clone expression subtree (id {id})"
-                        ))
+                        SbroadError::NotFound(Entity::SubTree, format!("(id {id})"))
                     })?;
                     *right = *map.get(right).ok_or_else(|| {
-                        QueryPlannerError::CustomError(format!(
-                            "Failed to clone expression subtree (id {id})"
-                        ))
+                        SbroadError::NotFound(Entity::SubTree, format!("(id {id})"))
                     })?;
                 }
                 Expression::Row {
@@ -348,9 +360,7 @@ impl Plan {
                 } => {
                     for child in children {
                         *child = *map.get(child).ok_or_else(|| {
-                            QueryPlannerError::CustomError(format!(
-                                "Failed to clone expression subtree (id {id})"
-                            ))
+                            SbroadError::NotFound(Entity::SubTree, format!("(id {id})"))
                         })?;
                     }
                 }
diff --git a/sbroad-core/src/ir.rs b/sbroad-core/src/ir.rs
index 9553dbd1ee..5d3e9c11eb 100644
--- a/sbroad-core/src/ir.rs
+++ b/sbroad-core/src/ir.rs
@@ -9,7 +9,7 @@ use expression::Expression;
 use operator::Relational;
 use relation::Table;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 use crate::ir::undo::TransformationLog;
 
 use self::parameters::Parameters;
@@ -105,11 +105,10 @@ impl Nodes {
     ///
     /// # Errors
     /// - The node with the given position doesn't exist.
-    pub fn replace(&mut self, id: usize, node: Node) -> Result<Node, QueryPlannerError> {
+    pub fn replace(&mut self, id: usize, node: Node) -> Result<Node, SbroadError> {
         if id >= self.arena.len() {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Can't replace node with id {} as it is out of arena bounds",
-                id
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                "can't replace node with id {id} as it is out of arena bounds"
             )));
         }
         let old_node = std::mem::replace(&mut self.arena[id], node);
@@ -231,13 +230,21 @@ impl Plan {
     /// Check that plan tree is valid.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the plan tree check fails.
-    pub fn check(&self) -> Result<(), QueryPlannerError> {
+    /// Returns `SbroadError` when the plan tree check fails.
+    pub fn check(&self) -> Result<(), SbroadError> {
         match self.top {
-            None => return Err(QueryPlannerError::InvalidPlan),
+            None => {
+                return Err(SbroadError::Invalid(
+                    Entity::Plan,
+                    Some("plan tree top is None".into()),
+                ))
+            }
             Some(top) => {
                 if self.nodes.arena.get(top).is_none() {
-                    return Err(QueryPlannerError::ValueOutOfRange);
+                    return Err(SbroadError::NotFound(
+                        Entity::Node,
+                        format!("from arena with index {top}"),
+                    ));
                 }
             }
         }
@@ -270,11 +277,14 @@ impl Plan {
     /// Get a node by its pointer (position in the node arena).
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the node with requested index
+    /// Returns `SbroadError` when the node with requested index
     /// doesn't exist.
-    pub fn get_node(&self, id: usize) -> Result<&Node, QueryPlannerError> {
+    pub fn get_node(&self, id: usize) -> Result<&Node, SbroadError> {
         match self.nodes.arena.get(id) {
-            None => Err(QueryPlannerError::ValueOutOfRange),
+            None => Err(SbroadError::NotFound(
+                Entity::Node,
+                format!("from arena with index {id}"),
+            )),
             Some(node) => Ok(node),
         }
     }
@@ -282,11 +292,14 @@ impl Plan {
     /// Get a mutable node by its pointer (position in the node arena).
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the node with requested index
+    /// Returns `SbroadError` when the node with requested index
     /// doesn't exist.
-    pub fn get_mut_node(&mut self, id: usize) -> Result<&mut Node, QueryPlannerError> {
+    pub fn get_mut_node(&mut self, id: usize) -> Result<&mut Node, SbroadError> {
         match self.nodes.arena.get_mut(id) {
-            None => Err(QueryPlannerError::ValueOutOfRange),
+            None => Err(SbroadError::NotFound(
+                Entity::Node,
+                format!("(mutable) from arena with index {id}"),
+            )),
             Some(node) => Ok(node),
         }
     }
@@ -295,8 +308,9 @@ impl Plan {
     ///
     /// # Errors
     /// - top node is None (i.e. invalid plan)
-    pub fn get_top(&self) -> Result<usize, QueryPlannerError> {
-        self.top.ok_or(QueryPlannerError::InvalidPlan)
+    pub fn get_top(&self) -> Result<usize, SbroadError> {
+        self.top
+            .ok_or_else(|| SbroadError::Invalid(Entity::Plan, Some("plan tree top is None".into())))
     }
 
     /// Clone plan slices.
@@ -314,11 +328,11 @@ impl Plan {
     /// Construct a plan from the YAML file.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the YAML plan is invalid.
-    pub fn from_yaml(s: &str) -> Result<Self, QueryPlannerError> {
+    /// Returns `SbroadError` when the YAML plan is invalid.
+    pub fn from_yaml(s: &str) -> Result<Self, SbroadError> {
         let plan: Plan = match serde_yaml::from_str(s) {
             Ok(p) => p,
-            Err(e) => return Err(QueryPlannerError::CustomError(e.to_string())),
+            Err(e) => return Err(SbroadError::Invalid(Entity::Plan, Some(e.to_string()))),
         };
         plan.check()?;
         Ok(plan)
@@ -329,7 +343,7 @@ impl Plan {
     /// # Errors
     /// - node is not relational
     /// - node's output is not a row of aliases
-    pub fn get_row_from_rel_node(&mut self, node: usize) -> Result<usize, QueryPlannerError> {
+    pub fn get_row_from_rel_node(&mut self, node: usize) -> Result<usize, SbroadError> {
         if let Node::Relational(rel) = self.get_node(node)? {
             if let Node::Expression(Expression::Row { list, .. }) = self.get_node(rel.output())? {
                 let mut cols: Vec<usize> = Vec::with_capacity(list.len());
@@ -339,14 +353,18 @@ impl Plan {
                     {
                         cols.push(*child);
                     } else {
-                        return Err(QueryPlannerError::InvalidNode);
+                        return Err(SbroadError::Invalid(
+                            Entity::Node,
+                            Some("node's output is not a row of aliases".into()),
+                        ));
                     }
                 }
                 return Ok(self.nodes.add_row(cols, None));
             }
         }
-        Err(QueryPlannerError::CustomError(
-            "Node is not relational".into(),
+        Err(SbroadError::Invalid(
+            Entity::Node,
+            Some("node is not Relational type".into()),
         ))
     }
 
@@ -358,13 +376,13 @@ impl Plan {
     /// Add condition node to the plan.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the condition node can't append'.
+    /// Returns `SbroadError` when the condition node can't append'.
     pub fn add_cond(
         &mut self,
         left: usize,
         op: operator::Bool,
         right: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         self.nodes.add_bool(left, op, right)
     }
 
@@ -372,11 +390,7 @@ impl Plan {
     ///
     /// # Errors
     /// - Child node is invalid
-    pub fn add_unary(
-        &mut self,
-        op: operator::Unary,
-        child: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    pub fn add_unary(&mut self, op: operator::Unary, child: usize) -> Result<usize, SbroadError> {
         self.nodes.add_unary_bool(op, child)
     }
 
@@ -394,7 +408,7 @@ impl Plan {
     /// Set top node of plan
     /// # Errors
     /// - top node doesn't exist in the plan.
-    pub fn set_top(&mut self, top: usize) -> Result<(), QueryPlannerError> {
+    pub fn set_top(&mut self, top: usize) -> Result<(), SbroadError> {
         self.get_node(top)?;
         self.top = Some(top);
         Ok(())
@@ -405,11 +419,12 @@ impl Plan {
     /// # Errors
     /// - node doesn't exist in the plan
     /// - node is not a relational type
-    pub fn get_relation_node(&self, node_id: usize) -> Result<&Relational, QueryPlannerError> {
+    pub fn get_relation_node(&self, node_id: usize) -> Result<&Relational, SbroadError> {
         match self.get_node(node_id)? {
             Node::Relational(rel) => Ok(rel),
-            Node::Expression(_) | Node::Parameter => Err(QueryPlannerError::CustomError(
-                "Node isn't relational".into(),
+            Node::Expression(_) | Node::Parameter => Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("node is not Relational type".into()),
             )),
         }
     }
@@ -422,11 +437,12 @@ impl Plan {
     pub fn get_mut_relation_node(
         &mut self,
         node_id: usize,
-    ) -> Result<&mut Relational, QueryPlannerError> {
+    ) -> Result<&mut Relational, SbroadError> {
         match self.get_mut_node(node_id)? {
             Node::Relational(rel) => Ok(rel),
-            Node::Expression(_) | Node::Parameter => Err(QueryPlannerError::CustomError(
-                "Node isn't relational".into(),
+            Node::Expression(_) | Node::Parameter => Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("Node is not relational".into()),
             )),
         }
     }
@@ -436,7 +452,7 @@ impl Plan {
     /// # Errors
     /// - node doesn't exist in the plan
     /// - node is not expression type
-    pub fn get_expression_node(&self, node_id: usize) -> Result<&Expression, QueryPlannerError> {
+    pub fn get_expression_node(&self, node_id: usize) -> Result<&Expression, SbroadError> {
         match self.get_node(node_id)? {
             Node::Expression(exp) => Ok(exp),
             Node::Parameter => {
@@ -444,13 +460,15 @@ impl Plan {
                 if let Some(Node::Expression(exp)) = node {
                     Ok(exp)
                 } else {
-                    Err(QueryPlannerError::CustomError(
-                        "Parameter node does not refer to an expression".into(),
+                    Err(SbroadError::Invalid(
+                        Entity::Node,
+                        Some("parameter node does not refer to an expression".into()),
                     ))
                 }
             }
-            Node::Relational(_) => Err(QueryPlannerError::CustomError(
-                "Node isn't expression".into(),
+            Node::Relational(_) => Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("node is not Expression type".into()),
             )),
         }
     }
@@ -463,11 +481,12 @@ impl Plan {
     pub fn get_mut_expression_node(
         &mut self,
         node_id: usize,
-    ) -> Result<&mut Expression, QueryPlannerError> {
+    ) -> Result<&mut Expression, SbroadError> {
         match self.get_mut_node(node_id)? {
             Node::Expression(exp) => Ok(exp),
-            Node::Relational(_) | Node::Parameter => Err(QueryPlannerError::CustomError(
-                "Node isn't expression".into(),
+            Node::Relational(_) | Node::Parameter => Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("node is not expression type".into()),
             )),
         }
     }
@@ -478,10 +497,7 @@ impl Plan {
     /// - node doesn't exist in the plan
     /// - node is not `Reference`
     /// - invalid references between nodes
-    pub fn get_alias_from_reference_node(
-        &self,
-        node: &Expression,
-    ) -> Result<&str, QueryPlannerError> {
+    pub fn get_alias_from_reference_node(&self, node: &Expression) -> Result<&str, SbroadError> {
         if let Expression::Reference {
             targets,
             position,
@@ -491,7 +507,7 @@ impl Plan {
             let ref_node = if let Some(parent) = parent {
                 self.get_relation_node(*parent)?
             } else {
-                return Err(QueryPlannerError::CustomError(
+                return Err(SbroadError::UnexpectedNumberOfValues(
                     "Reference node has no parent".into(),
                 ));
             };
@@ -499,17 +515,18 @@ impl Plan {
             // In a case of insert we don't inspect children output tuple
             // but rather use target relation columns.
             if let Relational::Insert { ref relation, .. } = ref_node {
-                let rel = self.relations.get(relation).ok_or_else(|| {
-                    QueryPlannerError::CustomError(format!("Relation {relation} doesn't exist"))
-                })?;
+                let rel = self
+                    .relations
+                    .get(relation)
+                    .ok_or_else(|| SbroadError::NotFound(Entity::Table, relation.to_string()))?;
                 let col_name = rel
                     .columns
                     .get(*position)
                     .ok_or_else(|| {
-                        QueryPlannerError::CustomError(format!(
-                            "Relation {} has no column {}",
-                            relation, position
-                        ))
+                        SbroadError::NotFound(
+                            Entity::Table,
+                            "{relation}'s column {position}".into(),
+                        )
                     })?
                     .name
                     .as_str();
@@ -518,19 +535,22 @@ impl Plan {
 
             if let Some(list_of_column_nodes) = ref_node.children() {
                 let child_ids = targets.as_ref().ok_or_else(|| {
-                    QueryPlannerError::CustomError("Node refs to scan node, not alias".into())
+                    SbroadError::Invalid(
+                        Entity::Target,
+                        Some("node refs to scan node, not alias".into()),
+                    )
                 })?;
                 let column_index_in_list = child_ids.first().ok_or_else(|| {
-                    QueryPlannerError::CustomError("Invalid child index in target".into())
+                    SbroadError::UnexpectedNumberOfValues("Target has no children".into())
                 })?;
                 let col_idx_in_rel =
                     list_of_column_nodes
                         .get(*column_index_in_list)
                         .ok_or_else(|| {
-                            QueryPlannerError::CustomError(format!(
-                                "Not found column node with index {}",
-                                column_index_in_list
-                            ))
+                            SbroadError::NotFound(
+                                Entity::Node,
+                                format!("type Column with index {column_index_in_list}"),
+                            )
                         })?;
 
                 let column_rel_node = self.get_relation_node(*col_idx_in_rel)?;
@@ -541,23 +561,34 @@ impl Plan {
                         .get_row_list()?
                         .get(*position)
                         .ok_or_else(|| {
-                            QueryPlannerError::CustomError("Invalid position in row list".into())
+                            SbroadError::NotFound(
+                                Entity::Column,
+                                format!("at position {} in row list", position),
+                            )
                         })?;
 
                 let col_alias_node = self.get_expression_node(*col_alias_idx)?;
                 match col_alias_node {
                     Expression::Alias { name, .. } => return Ok(name),
-                    _ => return Err(QueryPlannerError::CustomError("Expected alias node".into())),
+                    _ => {
+                        return Err(SbroadError::Invalid(
+                            Entity::Expression,
+                            Some("expected alias node".into()),
+                        ))
+                    }
                 }
             }
 
-            return Err(QueryPlannerError::CustomError(
-                "Failed to get a referred relational node".into(),
+            return Err(SbroadError::FailedTo(
+                Action::Get,
+                None,
+                "a referred relational node".into(),
             ));
         }
 
-        Err(QueryPlannerError::CustomError(
-            "Node is not of a reference type".into(),
+        Err(SbroadError::Invalid(
+            Entity::Node,
+            Some("node is not of a reference type".into()),
         ))
     }
 
@@ -568,30 +599,34 @@ impl Plan {
 
     /// # Errors
     /// - serialization error (to binary)
-    pub fn pattern_id(&self) -> Result<String, QueryPlannerError> {
+    pub fn pattern_id(&self) -> Result<String, SbroadError> {
         let mut bytes: Vec<u8> = bincode::serialize(&self.nodes).map_err(|e| {
-            QueryPlannerError::CustomError(format!(
-                "Failed to serialize plan nodes to binary: {:?}",
-                e
-            ))
+            SbroadError::FailedTo(
+                Action::Serialize,
+                None,
+                format!("plan nodes to binary: {e:?}"),
+            )
         })?;
         let mut relation_bytes: Vec<u8> = bincode::serialize(&self.relations).map_err(|e| {
-            QueryPlannerError::CustomError(format!(
-                "Failed to serialize plan relations to binary: {:?}",
-                e
-            ))
+            SbroadError::FailedTo(
+                Action::Serialize,
+                None,
+                format!("plan relations to binary: {e:?}"),
+            )
         })?;
         let mut slice_bytes: Vec<u8> = bincode::serialize(&self.slices).map_err(|e| {
-            QueryPlannerError::CustomError(format!(
-                "Failed to serialize plan slices to binary: {:?}",
-                e
-            ))
+            SbroadError::FailedTo(
+                Action::Serialize,
+                None,
+                format!("plan slices to binary: {e:?}"),
+            )
         })?;
         let mut top_bytes: Vec<u8> = bincode::serialize(&self.top).map_err(|e| {
-            QueryPlannerError::CustomError(format!(
-                "Failed to serialize plan top to binary: {:?}",
-                e
-            ))
+            SbroadError::FailedTo(
+                Action::Serialize,
+                None,
+                format!("plan top to binary: {e:?}"),
+            )
         })?;
         bytes.append(&mut relation_bytes);
         bytes.append(&mut slice_bytes);
diff --git a/sbroad-core/src/ir/api/constant.rs b/sbroad-core/src/ir/api/constant.rs
index d2108fae34..1f314bb17d 100644
--- a/sbroad-core/src/ir/api/constant.rs
+++ b/sbroad-core/src/ir/api/constant.rs
@@ -1,4 +1,4 @@
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::value::Value;
 use crate::ir::{Node, Nodes, Plan};
@@ -8,13 +8,14 @@ impl Expression {
     ///
     /// # Errors
     /// - node isn't constant type
-    pub fn as_const_value(&self) -> Result<Value, QueryPlannerError> {
+    pub fn as_const_value(&self) -> Result<Value, SbroadError> {
         if let Expression::Constant { value } = self.clone() {
             return Ok(value);
         }
 
-        Err(QueryPlannerError::CustomError(
-            "Node isn't const type".into(),
+        Err(SbroadError::Invalid(
+            Entity::Node,
+            Some("node is not Const type".into()),
         ))
     }
 
@@ -22,13 +23,14 @@ impl Expression {
     ///
     /// # Errors
     /// - node isn't constant type
-    pub fn as_const_value_ref(&self) -> Result<&Value, QueryPlannerError> {
+    pub fn as_const_value_ref(&self) -> Result<&Value, SbroadError> {
         if let Expression::Constant { value } = self {
             return Ok(value);
         }
 
-        Err(QueryPlannerError::CustomError(
-            "Node isn't const type".into(),
+        Err(SbroadError::Invalid(
+            Entity::Node,
+            Some("node is not Const type".into()),
         ))
     }
 
@@ -72,14 +74,17 @@ impl Plan {
     ///
     /// # Errors
     /// - The parameters map is corrupted (parameters map points to invalid nodes).
-    pub fn restore_constants(&mut self) -> Result<(), QueryPlannerError> {
+    pub fn restore_constants(&mut self) -> Result<(), SbroadError> {
         for (id, const_node) in self.constants.drain() {
             if let Node::Expression(Expression::Constant { .. }) = const_node {
             } else {
-                return Err(QueryPlannerError::CustomError(format!(
-                    "Restoring parameters filed: node {:?} (id: {}) is not of a constant type",
-                    const_node, id
-                )));
+                return Err(SbroadError::Invalid(
+                    Entity::Expression,
+                    Some(format!(
+                        "Restoring parameters filed: node {:?} (id: {}) is not of a constant type",
+                        const_node, id
+                    )),
+                ));
             }
             self.nodes.replace(id, const_node)?;
         }
@@ -90,7 +95,7 @@ impl Plan {
     ///
     /// # Errors
     /// - The plan is corrupted (collected constants point to invalid arena positions).
-    pub fn stash_constants(&mut self) -> Result<(), QueryPlannerError> {
+    pub fn stash_constants(&mut self) -> Result<(), SbroadError> {
         let constants = self.get_const_list();
         for const_id in constants {
             let const_node = self.nodes.replace(const_id, Node::Parameter)?;
diff --git a/sbroad-core/src/ir/api/parameter.rs b/sbroad-core/src/ir/api/parameter.rs
index 212c8a9c34..6921c38d23 100644
--- a/sbroad-core/src/ir/api/parameter.rs
+++ b/sbroad-core/src/ir/api/parameter.rs
@@ -1,4 +1,4 @@
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::operator::Relational;
 use crate::ir::value::Value;
@@ -41,7 +41,7 @@ impl Plan {
     /// - Internal errors.
     #[allow(clippy::too_many_lines)]
     #[otm_child_span("plan.bind")]
-    pub fn bind_params(&mut self, mut params: Vec<Value>) -> Result<(), QueryPlannerError> {
+    pub fn bind_params(&mut self, mut params: Vec<Value>) -> Result<(), SbroadError> {
         // Nothing to do here.
         if params.is_empty() {
             return Ok(());
@@ -68,12 +68,9 @@ impl Plan {
         let param_set = self.get_param_set();
 
         // Closure to retrieve a corresponding value for a parameter node.
-        let get_value = |pos: usize| -> Result<usize, QueryPlannerError> {
+        let get_value = |pos: usize| -> Result<usize, SbroadError> {
             let val_id = value_ids.get(pos).ok_or_else(|| {
-                QueryPlannerError::CustomError(format!(
-                    "Parameter in position {} is not found.",
-                    pos
-                ))
+                SbroadError::NotFound(Entity::Node, format!("(Parameter) in position {pos}"))
             })?;
             Ok(*val_id)
         };
@@ -154,9 +151,9 @@ impl Plan {
             }
         }
 
-        let get_row = |idx: usize| -> Result<usize, QueryPlannerError> {
+        let get_row = |idx: usize| -> Result<usize, SbroadError> {
             let row_id = row_ids.get(&idx).ok_or_else(|| {
-                QueryPlannerError::CustomError(format!("Row in position {idx} is not found."))
+                SbroadError::NotFound(Entity::Node, format!("(Row) at position {idx}"))
             })?;
             Ok(*row_id)
         };
diff --git a/sbroad-core/src/ir/distribution.rs b/sbroad-core/src/ir/distribution.rs
index 2d6ef68cbb..0cbfbc0f26 100644
--- a/sbroad-core/src/ir/distribution.rs
+++ b/sbroad-core/src/ir/distribution.rs
@@ -6,7 +6,7 @@ use std::collections::{HashMap, HashSet};
 use serde::{Deserialize, Serialize};
 
 use crate::collection;
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 
 use super::expression::Expression;
 use super::operator::Relational;
@@ -219,15 +219,15 @@ impl Plan {
     /// Calculate and set tuple distribution.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when current expression is not a `Row` or contains broken references.
+    /// Returns `SbroadError` when current expression is not a `Row` or contains broken references.
     #[allow(clippy::too_many_lines)]
-    pub fn set_distribution(&mut self, row_id: usize) -> Result<(), QueryPlannerError> {
+    pub fn set_distribution(&mut self, row_id: usize) -> Result<(), SbroadError> {
         let row_children = self.get_expression_node(row_id)?.get_row_list()?;
 
         // There are two kinds of rows: constructed from aliases (projections)
         // and constructed from any other expressions (selection filters, join conditions, etc).
         // This closure returns the proper child id for the row.
-        let row_child_id = |col_id: usize| -> Result<usize, QueryPlannerError> {
+        let row_child_id = |col_id: usize| -> Result<usize, SbroadError> {
             match self.get_expression_node(col_id) {
                 Ok(Expression::Alias { child, .. }) => Ok(*child),
                 Ok(_) => Ok(col_id),
@@ -268,14 +268,17 @@ impl Plan {
                 {
                     // As the row is located in the branch relational node, the targets should be non-empty.
                     let targets = targets.as_ref().ok_or_else(|| {
-                        QueryPlannerError::CustomError("Reference targets are empty".to_string())
+                        SbroadError::UnexpectedNumberOfValues(
+                            "Reference targets are empty".to_string(),
+                        )
                     })?;
                     ref_map.reserve(targets.len());
                     ref_nodes.reserve(targets.len());
                     for target in targets {
                         let referred_id = *parent_children.get(*target).ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "The reference points to invalid column".to_string(),
+                            SbroadError::NotFound(
+                                Entity::Expression,
+                                "reference points to invalid column".to_string(),
                             )
                         })?;
                         ref_nodes.append(referred_id);
@@ -307,8 +310,9 @@ impl Plan {
                 ReferredNodes::None => {
                     // We should never get here as we have already handled the case
                     // when there are no references in the row (a row of constants).
-                    return Err(QueryPlannerError::CustomError(
-                        "The row contains no references".to_string(),
+                    return Err(SbroadError::Invalid(
+                        Entity::Expression,
+                        Some("the row contains no references".to_string()),
                     ));
                 }
                 ReferredNodes::Single(child_id) => {
@@ -329,7 +333,7 @@ impl Plan {
                     self.set_two_children_node_dist(&ref_map, n1, n2, parent_id, row_id)?;
                 }
                 ReferredNodes::Multiple(_) => {
-                    return Err(QueryPlannerError::CustomError(
+                    return Err(SbroadError::DuplicatedValue(
                         "Row contains multiple references to the same node (and in is not VALUES)"
                             .to_string(),
                     ));
@@ -346,8 +350,9 @@ impl Plan {
                 } = self.get_expression_node(child_id)?
                 {
                     if targets.is_some() {
-                        return Err(QueryPlannerError::CustomError(
-                            "References to the children targets in the leaf (relation scan) node are not supported".to_string(),
+                        return Err(SbroadError::Invalid(
+                            Entity::Expression,
+                            Some("References to the children targets in the leaf (relation scan) node are not supported".to_string()),
                         ));
                     }
                     table_map.insert(*position, pos);
@@ -358,8 +363,9 @@ impl Plan {
             let table_name: String = if let Relational::ScanRelation { relation, .. } = parent {
                 relation.clone()
             } else {
-                return Err(QueryPlannerError::CustomError(
-                    "The parent node is not a relation scan node".to_string(),
+                return Err(SbroadError::Invalid(
+                    Entity::Node,
+                    Some("the parent node is not a relation scan node".to_string()),
                 ));
             };
             self.set_scan_dist(&table_name, &table_map, row_id)?;
@@ -373,7 +379,7 @@ impl Plan {
         &self,
         child_rel_node: usize,
         child_pos_map: &HashMap<(usize, usize), usize, RandomState>,
-    ) -> Result<Distribution, QueryPlannerError> {
+    ) -> Result<Distribution, SbroadError> {
         if let Node::Relational(relational_op) = self.get_node(child_rel_node)? {
             if let Node::Expression(Expression::Row {
                 distribution: child_dist,
@@ -381,7 +387,12 @@ impl Plan {
             }) = self.get_node(relational_op.output())?
             {
                 match child_dist {
-                    None => return Err(QueryPlannerError::UninitializedDistribution),
+                    None => {
+                        return Err(SbroadError::Invalid(
+                            Entity::Distribution,
+                            Some("distribution is uninitialized".into()),
+                        ))
+                    }
                     Some(Distribution::Any) => return Ok(Distribution::Any),
                     Some(Distribution::Replicated) => return Ok(Distribution::Replicated),
                     Some(Distribution::Segment { keys }) => {
@@ -412,14 +423,14 @@ impl Plan {
                 }
             }
         }
-        Err(QueryPlannerError::InvalidRow)
+        Err(SbroadError::Invalid(Entity::Relational, None))
     }
 
     /// Sets row distribution to replicated.
     ///
     /// # Errors
     /// - Node is not of a row type.
-    pub fn set_const_dist(&mut self, row_id: usize) -> Result<(), QueryPlannerError> {
+    pub fn set_const_dist(&mut self, row_id: usize) -> Result<(), SbroadError> {
         if let Expression::Row {
             ref mut distribution,
             ..
@@ -430,8 +441,9 @@ impl Plan {
             }
             return Ok(());
         }
-        Err(QueryPlannerError::CustomError(
-            "The node is not a row type".to_string(),
+        Err(SbroadError::Invalid(
+            Entity::Expression,
+            Some("the node is not a row type".to_string()),
         ))
     }
 
@@ -440,11 +452,13 @@ impl Plan {
         table_name: &str,
         table_pos_map: &HashMap<usize, usize, RandomState>,
         row_id: usize,
-    ) -> Result<(), QueryPlannerError> {
-        let table: &Table = self
-            .relations
-            .get(table_name)
-            .ok_or(QueryPlannerError::InvalidRelation)?;
+    ) -> Result<(), SbroadError> {
+        let table: &Table = self.relations.get(table_name).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Table,
+                format!("{} among plan relations", table_name),
+            )
+        })?;
         let mut new_key: Key = Key::new(Vec::new());
         let all_found = table.key.positions.iter().all(|pos| {
             table_pos_map.get(pos).map_or(false, |v| {
@@ -472,7 +486,7 @@ impl Plan {
         right_id: usize,
         parent_id: usize,
         row_id: usize,
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         let left_dist = self.dist_from_child(left_id, child_pos_map)?;
         let right_dist = self.dist_from_child(right_id, child_pos_map)?;
 
@@ -483,8 +497,9 @@ impl Plan {
             }
             Relational::InnerJoin { .. } => Distribution::join(&left_dist, &right_dist),
             _ => {
-                return Err(QueryPlannerError::CustomError(
-                    "Invalid row: expected Except, UnionAll or InnerJoin".to_string(),
+                return Err(SbroadError::Invalid(
+                    Entity::Relational,
+                    Some("expected Except, UnionAll or InnerJoin".to_string()),
                 ))
             }
         };
@@ -496,8 +511,9 @@ impl Plan {
         {
             *distribution = Some(new_dist);
         } else {
-            return Err(QueryPlannerError::CustomError(
-                "Invalid row: expected Row".to_string(),
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("expected Row".to_string()),
             ));
         };
 
@@ -508,15 +524,19 @@ impl Plan {
     ///
     /// # Errors
     /// - Node is not of a row type.
-    pub fn get_distribution(&self, row_id: usize) -> Result<&Distribution, QueryPlannerError> {
+    pub fn get_distribution(&self, row_id: usize) -> Result<&Distribution, SbroadError> {
         match self.get_node(row_id)? {
             Node::Expression(expr) => expr.distribution(),
-            Node::Relational(_) => Err(QueryPlannerError::CustomError(
-                "Failed to get distribution for a relational node (try its row output tuple)."
-                    .to_string(),
+            Node::Relational(_) => Err(SbroadError::Invalid(
+                Entity::Distribution,
+                Some(
+                    "Failed to get distribution for a relational node (try its row output tuple)."
+                        .to_string(),
+                ),
             )),
-            Node::Parameter => Err(QueryPlannerError::CustomError(
-                "Failed to get distribution for a parameter node.".to_string(),
+            Node::Parameter => Err(SbroadError::Invalid(
+                Entity::Distribution,
+                Some("Failed to get distribution for a parameter node.".to_string()),
             )),
         }
     }
@@ -530,8 +550,8 @@ impl Plan {
     pub fn get_or_init_distribution(
         &mut self,
         row_id: usize,
-    ) -> Result<&Distribution, QueryPlannerError> {
-        if let Err(QueryPlannerError::UninitializedDistribution) = self.get_distribution(row_id) {
+    ) -> Result<&Distribution, SbroadError> {
+        if let Err(SbroadError::Invalid(Entity::Distribution, _)) = self.get_distribution(row_id) {
             self.set_distribution(row_id)?;
         }
         self.get_distribution(row_id)
diff --git a/sbroad-core/src/ir/explain.rs b/sbroad-core/src/ir/explain.rs
index 1a7edaaa7e..b3312ab4f4 100644
--- a/sbroad-core/src/ir/explain.rs
+++ b/sbroad-core/src/ir/explain.rs
@@ -5,7 +5,7 @@ use itertools::Itertools;
 use serde::Serialize;
 use traversal::DftPost;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::cast::Type as CastType;
 use crate::ir::expression::Expression;
 use crate::ir::operator::Relational;
@@ -56,7 +56,7 @@ impl Default for ColExpr {
 
 impl ColExpr {
     #[allow(dead_code)]
-    fn new(plan: &Plan, subtree_top: usize) -> Result<Self, QueryPlannerError> {
+    fn new(plan: &Plan, subtree_top: usize) -> Result<Self, SbroadError> {
         let dft_post = DftPost::new(&subtree_top, |node| plan.nodes.expr_iter(node, false));
         let mut stack: Vec<ColExpr> = Vec::new();
 
@@ -66,8 +66,8 @@ impl ColExpr {
             match &current_node {
                 Expression::Cast { to, .. } => {
                     let expr = stack.pop().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Failed to pop from stack while processing CAST expression".to_string(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "stack is empty while processing CAST expression".to_string(),
                         )
                     })?;
                     let cast_expr = ColExpr::Cast(Box::new(expr), to.clone());
@@ -91,13 +91,13 @@ impl ColExpr {
                 }
                 Expression::Concat { .. } => {
                     let right = stack.pop().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Failed to pop right child from stack while processing CONCAT expression".to_string(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "stack is empty while processing CONCAT expression".to_string(),
                         )
                     })?;
                     let left = stack.pop().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Failed to pop left child from stack while processing CONCAT expression".to_string(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "stack is empty while processing CONCAT expression".to_string(),
                         )
                     })?;
                     let concat_expr = ColExpr::Concat(Box::new(left), Box::new(right));
@@ -109,8 +109,8 @@ impl ColExpr {
                 }
                 Expression::StableFunction { name, .. } => {
                     let expr = stack.pop().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Failed to pop from stack while processing STABLE FUNCTION expression"
+                        SbroadError::UnexpectedNumberOfValues(
+                            "stack is empty while processing STABLE FUNCTION expression"
                                 .to_string(),
                         )
                     })?;
@@ -122,8 +122,8 @@ impl ColExpr {
                     let mut row: Vec<ColExpr> = Vec::with_capacity(len);
                     while len > 0 {
                         let expr = stack.pop().ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                format!("Failed to pop {len} element from stack while processing ROW expression"),
+                            SbroadError::UnexpectedNumberOfValues(
+                                format!("stack is empty, expected to pop {len} element while processing ROW expression"),
                             )
                         })?;
                         row.push(expr);
@@ -133,17 +133,19 @@ impl ColExpr {
                     stack.push(row_expr);
                 }
                 Expression::Alias { .. } | Expression::Bool { .. } | Expression::Unary { .. } => {
-                    return Err(QueryPlannerError::CustomError(format!(
-                        "Column expression node [{:?}] is not supported for yet",
-                        current_node
-                    )));
+                    return Err(SbroadError::Unsupported(
+                        Entity::Expression,
+                        Some(format!(
+                            "Column expression node [{current_node:?}] is not supported for yet"
+                        )),
+                    ));
                 }
             }
         }
 
         stack
             .pop()
-            .ok_or_else(|| QueryPlannerError::CustomError("Failed to pop from stack".to_string()))
+            .ok_or_else(|| SbroadError::UnexpectedNumberOfValues("stack is empty".to_string()))
     }
 }
 
@@ -158,7 +160,7 @@ struct Col {
 
 impl Col {
     #[allow(dead_code)]
-    fn new(plan: &Plan, subtree_top: usize) -> Result<Self, QueryPlannerError> {
+    fn new(plan: &Plan, subtree_top: usize) -> Result<Self, SbroadError> {
         let mut column = Col::default();
 
         let dft_post = DftPost::new(&subtree_top, |node| plan.nodes.expr_iter(node, true));
@@ -196,7 +198,7 @@ struct Projection {
 
 impl Projection {
     #[allow(dead_code)]
-    fn new(plan: &Plan, output_id: usize) -> Result<Self, QueryPlannerError> {
+    fn new(plan: &Plan, output_id: usize) -> Result<Self, SbroadError> {
         let mut result = Projection { cols: vec![] };
 
         let alias_list = plan.get_expression_node(output_id)?;
@@ -314,7 +316,7 @@ impl Row {
         plan: &Plan,
         node_ids: &[usize],
         ref_map: &HashMap<usize, usize>,
-    ) -> Result<Self, QueryPlannerError> {
+    ) -> Result<Self, SbroadError> {
         let mut row = Row::new();
 
         for child in node_ids {
@@ -337,8 +339,9 @@ impl Row {
                         row.add_col(RowVal::Column(col));
                     } else {
                         let sq_offset = ref_map.get(&rel_id).ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "The sub-query was not found in the map".into(),
+                            SbroadError::NotFound(
+                                Entity::SubQuery,
+                                format!("with index {rel_id} in the map"),
                             )
                         })?;
 
@@ -396,7 +399,7 @@ impl Selection {
         plan: &Plan,
         subtree_node_id: usize,
         ref_map: &HashMap<usize, usize>,
-    ) -> Result<Self, QueryPlannerError> {
+    ) -> Result<Self, SbroadError> {
         let current_node = plan.get_expression_node(subtree_node_id)?;
 
         let result = match current_node {
@@ -662,7 +665,7 @@ impl Display for FullExplain {
 impl FullExplain {
     #[allow(dead_code)]
     #[allow(clippy::too_many_lines)]
-    pub fn new(ir: &Plan, top_id: usize) -> Result<Self, QueryPlannerError> {
+    pub fn new(ir: &Plan, top_id: usize) -> Result<Self, SbroadError> {
         let mut stack: Vec<ExplainTreePart> = Vec::with_capacity(ir.nodes.relation_node_amount());
         let mut result = FullExplain::default();
 
@@ -676,7 +679,7 @@ impl FullExplain {
                         current_node.children.push(left);
                         current_node.children.push(right);
                     } else {
-                        return Err(QueryPlannerError::CustomError(
+                        return Err(SbroadError::UnexpectedNumberOfValues(
                             "Exception node must have exactly two children".into(),
                         ));
                     }
@@ -685,7 +688,7 @@ impl FullExplain {
                 Relational::Projection { output, .. } => {
                     // TODO: change this logic when we'll enable sub-queries in projection
                     let child = stack.pop().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
+                        SbroadError::UnexpectedNumberOfValues(
                             "Projection node must have exactly one child".into(),
                         )
                     })?;
@@ -710,8 +713,8 @@ impl FullExplain {
                     if let Some((_, other)) = children.split_first() {
                         for sq_id in other.iter().rev() {
                             let sq_node = stack.pop().ok_or_else(|| {
-                                QueryPlannerError::CustomError(
-                                    "Selection node failed to get a sub-query.".into(),
+                                SbroadError::UnexpectedNumberOfValues(
+                                    "Selection node failed to pop a sub-query.".into(),
                                 )
                             })?;
                             result.subqueries.push(sq_node);
@@ -719,13 +722,13 @@ impl FullExplain {
                             sq_ref_map.insert(*sq_id, offset);
                         }
                         let child = stack.pop().ok_or_else(|| {
-                            QueryPlannerError::CustomError(
+                            SbroadError::UnexpectedNumberOfValues(
                                 "Selection node must have exactly one child".into(),
                             )
                         })?;
                         current_node.children.push(child);
                     } else {
-                        return Err(QueryPlannerError::CustomError(
+                        return Err(SbroadError::UnexpectedNumberOfValues(
                             "Selection node doesn't have any children".into(),
                         ));
                     }
@@ -738,7 +741,7 @@ impl FullExplain {
                         current_node.children.push(left);
                         current_node.children.push(right);
                     } else {
-                        return Err(QueryPlannerError::CustomError(
+                        return Err(SbroadError::UnexpectedNumberOfValues(
                             "Union all node must have exactly two children".into(),
                         ));
                     }
@@ -746,7 +749,7 @@ impl FullExplain {
                 }
                 Relational::ScanSubQuery { alias, .. } => {
                     let child = stack.pop().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
+                        SbroadError::UnexpectedNumberOfValues(
                             "ScanSubQuery node must have exactly one child".into(),
                         )
                     })?;
@@ -761,7 +764,7 @@ impl FullExplain {
                     ..
                 } => {
                     let child = stack.pop().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
+                        SbroadError::UnexpectedNumberOfValues(
                             "Motion node must have exactly one child".into(),
                         )
                     })?;
@@ -770,8 +773,8 @@ impl FullExplain {
                     let p = match policy {
                         IrMotionPolicy::Segment(s) => {
                             let child_id = children.first().ok_or_else(|| {
-                                QueryPlannerError::CustomError(
-                                    "Current node should have exactly one child".to_string(),
+                                SbroadError::UnexpectedNumberOfValues(
+                                    "current node should have exactly one child".to_string(),
                                 )
                             })?;
 
@@ -784,9 +787,10 @@ impl FullExplain {
                                 .map(|r| match r {
                                     IrTarget::Reference(pos) => {
                                         let col_id = *col_list.get(*pos).ok_or_else(|| {
-                                            QueryPlannerError::CustomError(String::from(
-                                                "Invalid position in list",
-                                            ))
+                                            SbroadError::NotFound(
+                                                Entity::Target,
+                                                format!("reference with position {}", pos),
+                                            )
                                         })?;
                                         let col_name = ir
                                             .get_expression_node(col_id)?
@@ -814,7 +818,7 @@ impl FullExplain {
                     ..
                 } => {
                     if children.len() < 2 {
-                        return Err(QueryPlannerError::CustomError(
+                        return Err(SbroadError::UnexpectedNumberOfValues(
                             "Join must have at least two children".into(),
                         ));
                     }
@@ -824,8 +828,8 @@ impl FullExplain {
 
                     for sq_id in subquery_ids.iter().rev() {
                         let sq_node = stack.pop().ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Join node failed to get a sub-query.".into(),
+                            SbroadError::UnexpectedNumberOfValues(
+                                "Join node failed to pop a sub-query.".into(),
                             )
                         })?;
                         result.subqueries.push(sq_node);
@@ -837,7 +841,7 @@ impl FullExplain {
                         current_node.children.push(left);
                         current_node.children.push(right);
                     } else {
-                        return Err(QueryPlannerError::CustomError(
+                        return Err(SbroadError::UnexpectedNumberOfValues(
                             "Join node must have exactly two children".into(),
                         ));
                     }
@@ -851,8 +855,8 @@ impl FullExplain {
 
                     for sq_id in children.iter().rev() {
                         let sq_node = stack.pop().ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Insert node failed to get a sub-query.".into(),
+                            SbroadError::UnexpectedNumberOfValues(
+                                "Insert node failed to pop a sub-query.".into(),
                             )
                         })?;
 
@@ -871,8 +875,8 @@ impl FullExplain {
 
                     while amount_values > 0 {
                         let value_row = stack.pop().ok_or_else(|| {
-                            QueryPlannerError::CustomError(
-                                "Insert node failed to get a value row.".into(),
+                            SbroadError::UnexpectedNumberOfValues(
+                                "Insert node failed to pop a value row.".into(),
                             )
                         })?;
 
@@ -883,8 +887,8 @@ impl FullExplain {
                 }
                 Relational::Insert { relation, .. } => {
                     let values = stack.pop().ok_or_else(|| {
-                        QueryPlannerError::CustomError(
-                            "Insert node failed to get a value row.".into(),
+                        SbroadError::UnexpectedNumberOfValues(
+                            "Insert node failed to pop a value row.".into(),
                         )
                     })?;
 
@@ -897,7 +901,7 @@ impl FullExplain {
         }
         result.main_query = stack
             .pop()
-            .ok_or_else(|| QueryPlannerError::CustomError("Invalid explain top node.".into()))?;
+            .ok_or_else(|| SbroadError::NotFound(Entity::Node, "that is explain top".into()))?;
         Ok(result)
     }
 }
@@ -908,7 +912,7 @@ impl Plan {
     /// # Errors
     /// - Failed to get top node
     /// - Failed to build explain
-    pub fn as_explain(&self) -> Result<String, QueryPlannerError> {
+    pub fn as_explain(&self) -> Result<String, SbroadError> {
         let top_id = self.get_top()?;
         let explain = FullExplain::new(self, top_id)?;
         Ok(explain.to_string())
diff --git a/sbroad-core/src/ir/expression.rs b/sbroad-core/src/ir/expression.rs
index 496a14c5fe..ef8db278af 100644
--- a/sbroad-core/src/ir/expression.rs
+++ b/sbroad-core/src/ir/expression.rs
@@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
 use std::collections::{HashMap, HashSet};
 use traversal::DftPost;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::operator::{Bool, Relational};
 
 use super::distribution::Distribution;
@@ -137,27 +137,35 @@ impl Expression {
     /// Gets current row distribution.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the function is called on expression
+    /// Returns `SbroadError` when the function is called on expression
     /// other than `Row` or a node doesn't know its distribution yet.
-    pub fn distribution(&self) -> Result<&Distribution, QueryPlannerError> {
+    pub fn distribution(&self) -> Result<&Distribution, SbroadError> {
         if let Expression::Row { distribution, .. } = self {
             let dist = match distribution {
                 Some(d) => d,
-                None => return Err(QueryPlannerError::UninitializedDistribution),
+                None => {
+                    return Err(SbroadError::Invalid(
+                        Entity::Distribution,
+                        Some("distribution is uninitialized".into()),
+                    ))
+                }
             };
             return Ok(dist);
         }
-        Err(QueryPlannerError::InvalidRow)
+        Err(SbroadError::Invalid(Entity::Expression, None))
     }
 
     /// Clone the row children list.
     ///
     /// # Errors
     /// - node isn't `Row`
-    pub fn clone_row_list(&self) -> Result<Vec<usize>, QueryPlannerError> {
+    pub fn clone_row_list(&self) -> Result<Vec<usize>, SbroadError> {
         match self {
             Expression::Row { list, .. } => Ok(list.clone()),
-            _ => Err(QueryPlannerError::CustomError("Node isn't row type".into())),
+            _ => Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("node isn't Row type".into()),
+            )),
         }
     }
 
@@ -165,10 +173,13 @@ impl Expression {
     ///
     /// # Errors
     /// - node isn't `Row`
-    pub fn get_row_list(&self) -> Result<&[usize], QueryPlannerError> {
+    pub fn get_row_list(&self) -> Result<&[usize], SbroadError> {
         match self {
             Expression::Row { ref list, .. } => Ok(list),
-            _ => Err(QueryPlannerError::CustomError("Node isn't row type".into())),
+            _ => Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("node isn't Row type".into()),
+            )),
         }
     }
 
@@ -176,11 +187,12 @@ impl Expression {
     ///
     /// # Errors
     /// - node isn't `Alias`
-    pub fn get_alias_name(&self) -> Result<&str, QueryPlannerError> {
+    pub fn get_alias_name(&self) -> Result<&str, SbroadError> {
         match self {
             Expression::Alias { name, .. } => Ok(name.as_str()),
-            _ => Err(QueryPlannerError::CustomError(
-                "Node isn't alias type".into(),
+            _ => Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("node is not Alias type".into()),
             )),
         }
     }
@@ -189,7 +201,7 @@ impl Expression {
     ///
     /// # Errors
     /// - distribution isn't set
-    pub fn has_unknown_distribution(&self) -> Result<bool, QueryPlannerError> {
+    pub fn has_unknown_distribution(&self) -> Result<bool, SbroadError> {
         let d = self.distribution()?;
         Ok(d.is_unknown())
     }
@@ -199,13 +211,15 @@ impl Expression {
     /// # Errors
     /// - node isn't reference type
     /// - reference doesn't have a parent
-    pub fn get_parent(&self) -> Result<usize, QueryPlannerError> {
+    pub fn get_parent(&self) -> Result<usize, SbroadError> {
         if let Expression::Reference { parent, .. } = self {
-            return parent
-                .ok_or_else(|| QueryPlannerError::CustomError("Reference has no parent".into()));
+            return parent.ok_or_else(|| {
+                SbroadError::Invalid(Entity::Expression, Some("Reference has no parent".into()))
+            });
         }
-        Err(QueryPlannerError::CustomError(
-            "Node isn't reference type".into(),
+        Err(SbroadError::Invalid(
+            Entity::Expression,
+            Some("node is not Reference type".into()),
         ))
     }
 
@@ -231,12 +245,15 @@ impl Nodes {
     /// # Errors
     /// - child node is invalid
     /// - name is empty
-    pub fn add_alias(&mut self, name: &str, child: usize) -> Result<usize, QueryPlannerError> {
-        self.arena
-            .get(child)
-            .ok_or(QueryPlannerError::InvalidNode)?;
+    pub fn add_alias(&mut self, name: &str, child: usize) -> Result<usize, SbroadError> {
+        self.arena.get(child).ok_or_else(|| {
+            SbroadError::NotFound(Entity::Node, format!("from arena with index {}", child))
+        })?;
         if name.is_empty() {
-            return Err(QueryPlannerError::InvalidName);
+            return Err(SbroadError::Invalid(
+                Entity::Plan,
+                Some(String::from("name is empty")),
+            ));
         }
         let alias = Expression::Alias {
             name: String::from(name),
@@ -254,11 +271,25 @@ impl Nodes {
         left: usize,
         op: operator::Bool,
         right: usize,
-    ) -> Result<usize, QueryPlannerError> {
-        self.arena.get(left).ok_or(QueryPlannerError::InvalidNode)?;
-        self.arena
-            .get(right)
-            .ok_or(QueryPlannerError::InvalidNode)?;
+    ) -> Result<usize, SbroadError> {
+        self.arena.get(left).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!(
+                    "(left child of boolean node) from arena with index {}",
+                    left
+                ),
+            )
+        })?;
+        self.arena.get(right).ok_or_else(|| {
+            SbroadError::NotFound(
+                Entity::Node,
+                format!(
+                    "(right child of boolean node) from arena with index {}",
+                    right
+                ),
+            )
+        })?;
         Ok(self.push(Node::Expression(Expression::Bool { left, op, right })))
     }
 
@@ -293,23 +324,29 @@ impl Nodes {
         &mut self,
         list: Vec<usize>,
         distribution: Option<Distribution>,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let mut names: HashSet<String> = HashSet::with_capacity(list.len());
 
         for alias_node in &list {
-            if let Node::Expression(Expression::Alias { name, .. }) = self
-                .arena
-                .get(*alias_node)
-                .ok_or(QueryPlannerError::InvalidNode)?
+            if let Node::Expression(Expression::Alias { name, .. }) =
+                self.arena.get(*alias_node).ok_or_else(|| {
+                    SbroadError::NotFound(
+                        Entity::Node,
+                        format!("(Alias) from arena with index {}", alias_node),
+                    )
+                })?
             {
                 if !names.insert(String::from(name)) {
-                    return Err(QueryPlannerError::CustomError(format!(
-                        "Row can't be added because `{}` already has an alias",
+                    return Err(SbroadError::DuplicatedValue(format!(
+                        "row can't be added because `{}` already has an alias",
                         name
                     )));
                 }
             } else {
-                return Err(QueryPlannerError::InvalidRow);
+                return Err(SbroadError::Invalid(
+                    Entity::Node,
+                    Some("node is not Alias type".into()),
+                ));
             }
         }
         Ok(self.add_row(list, distribution))
@@ -323,9 +360,9 @@ impl Nodes {
         &mut self,
         op: operator::Unary,
         child: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         self.arena.get(child).ok_or_else(|| {
-            QueryPlannerError::CustomError(format!("Invalid node in the plan arena:{child}"))
+            SbroadError::NotFound(Entity::Node, format!("from arena with index {}", child))
         })?;
         Ok(self.push(Node::Expression(Expression::Unary { op, child })))
     }
@@ -340,7 +377,7 @@ impl Plan {
     /// appends the right child's one to it. Otherwise we build an output tuple
     /// only from the first (left) child.
     /// # Errors
-    /// Returns `QueryPlannerError`:
+    /// Returns `SbroadError`:
     /// - relation node contains invalid `Row` in the output
     /// - targets and children are inconsistent
     /// - column names don't exist
@@ -353,7 +390,7 @@ impl Plan {
         col_names: &[&str],
         need_aliases: bool,
         need_sharding_column: bool,
-    ) -> Result<Vec<usize>, QueryPlannerError> {
+    ) -> Result<Vec<usize>, SbroadError> {
         // We can pass two target children nodes only in case
         // of `UnionAll` and `InnerJoin`.
         // - For the `UnionAll` operator we need only the first
@@ -362,16 +399,16 @@ impl Plan {
         // to both children to give us additional information
         // during transformations.
         if (targets.len() > 2) || targets.is_empty() {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Invalid target length: {}",
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                "invalid target length: {}",
                 targets.len()
             )));
         }
 
         if let Some(max) = targets.iter().max() {
             if *max >= children.len() {
-                return Err(QueryPlannerError::CustomError(format!(
-                    "Invalid children length: {}",
+                return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                    "invalid children length: {}",
                     children.len()
                 )));
             }
@@ -384,15 +421,17 @@ impl Plan {
                 let target_child: usize = if let Some(target) = targets.get(*target_idx) {
                     *target
                 } else {
-                    return Err(QueryPlannerError::CustomError(
-                        "Failed to find the child node pointed by target index".into(),
+                    return Err(SbroadError::NotFound(
+                        Entity::Node,
+                        "(child) pointed by target index".into(),
                     ));
                 };
                 let child_node: usize = if let Some(child) = children.get(target_child) {
                     *child
                 } else {
-                    return Err(QueryPlannerError::CustomError(
-                        "Child node not found".into(),
+                    return Err(SbroadError::NotFound(
+                        Entity::Node,
+                        format!("pointed by target child {}", target_child),
                     ));
                 };
                 let relational_op = self.get_relation_node(child_node)?;
@@ -421,7 +460,7 @@ impl Plan {
                                 {
                                     *sel_child_id
                                 } else {
-                                    return Err(QueryPlannerError::CustomError(format!(
+                                    return Err(SbroadError::UnexpectedNumberOfValues(format!(
                                         "Selection node has invalid children: {:?}",
                                         sel_child_ids
                                     )));
@@ -438,10 +477,10 @@ impl Plan {
                         };
                         if let Some(relation) = table_name {
                             let table = self.get_relation(relation).ok_or_else(|| {
-                                QueryPlannerError::CustomError(format!(
-                                    "Failed to find table {} in the plan",
-                                    relation
-                                ))
+                                SbroadError::NotFound(
+                                    Entity::Table,
+                                    format!("{} among the plan relations", relation),
+                                )
                             })?;
                             let sharding_column_pos = table.get_bucket_id_position()?;
                             // Take an advantage of the fact that the output aliases
@@ -459,8 +498,9 @@ impl Plan {
                         }
                     }
                 } else {
-                    return Err(QueryPlannerError::CustomError(
-                        "Child node is not a row".into(),
+                    return Err(SbroadError::Invalid(
+                        Entity::Expression,
+                        Some("child node is not a row".into()),
                     ));
                 };
                 result.reserve(child_row_list.len());
@@ -471,8 +511,9 @@ impl Plan {
                         {
                             String::from(name)
                         } else {
-                            return Err(QueryPlannerError::CustomError(
-                                "Child node is not an alias".into(),
+                            return Err(SbroadError::Invalid(
+                                Entity::Expression,
+                                Some("child node is not an Alias".into()),
                             ));
                         };
                     let new_targets: Vec<usize> = if is_join {
@@ -501,13 +542,16 @@ impl Plan {
         let target_child: usize = if let Some(target) = targets.first() {
             *target
         } else {
-            return Err(QueryPlannerError::CustomError("Target is empty".into()));
+            return Err(SbroadError::UnexpectedNumberOfValues(
+                "Target is empty".into(),
+            ));
         };
         let child_node: usize = if let Some(child) = children.get(target_child) {
             *child
         } else {
-            return Err(QueryPlannerError::CustomError(
-                "Failed to get a child pointed by the target".into(),
+            return Err(SbroadError::NotFound(
+                Entity::Node,
+                "pointed by the target".into(),
             ));
         };
 
@@ -531,7 +575,7 @@ impl Plan {
                     continue;
                 }
                 if map.insert(name, pos).is_some() {
-                    return Err(QueryPlannerError::CustomError(format!(
+                    return Err(SbroadError::DuplicatedValue(format!(
                         "Duplicate column name {} at position {}",
                         name, pos
                     )));
@@ -539,8 +583,9 @@ impl Plan {
             }
             map
         } else {
-            return Err(QueryPlannerError::CustomError(
-                "Relational output tuple is not a row".into(),
+            return Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("Relational output tuple is not a row".into()),
             ));
         };
 
@@ -552,10 +597,10 @@ impl Plan {
             })
         });
         if !all_found {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Some of the columns {:?} were not found in the table",
-                col_names,
-            )));
+            return Err(SbroadError::NotFound(
+                Entity::Column,
+                format!("with name {:?}", col_names),
+            ));
         }
 
         for (col, new_targets, pos) in refs {
@@ -575,7 +620,7 @@ impl Plan {
     ///
     /// If column names are empty, copy all the columns from the child.
     /// # Errors
-    /// Returns `QueryPlannerError`:
+    /// Returns `SbroadError`:
     /// - child is an inconsistent relational node
     /// - column names don't exist
     pub fn add_row_for_output(
@@ -583,7 +628,7 @@ impl Plan {
         child: usize,
         col_names: &[&str],
         need_sharding_column: bool,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let list =
             self.new_columns(&[child], false, &[0], col_names, true, need_sharding_column)?;
         self.nodes.add_row_of_aliases(list, None)
@@ -592,13 +637,13 @@ impl Plan {
     /// New output row for union node.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError`:
+    /// Returns `SbroadError`:
     /// - children are inconsistent relational nodes
     pub fn add_row_for_union_except(
         &mut self,
         left: usize,
         right: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let list = self.new_columns(&[left, right], false, &[0, 1], &[], true, true)?;
         self.nodes.add_row_of_aliases(list, None)
     }
@@ -608,13 +653,9 @@ impl Plan {
     /// Contains all the columns from left and right children.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError`:
+    /// Returns `SbroadError`:
     /// - children are inconsistent relational nodes
-    pub fn add_row_for_join(
-        &mut self,
-        left: usize,
-        right: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    pub fn add_row_for_join(&mut self, left: usize, right: usize) -> Result<usize, SbroadError> {
         let list = self.new_columns(&[left, right], true, &[0, 1], &[], true, true)?;
         self.nodes.add_row_of_aliases(list, None)
     }
@@ -624,14 +665,14 @@ impl Plan {
     /// New columns don't have aliases. If column names are empty,
     /// copy all the columns from the child.
     /// # Errors
-    /// Returns `QueryPlannerError`:
+    /// Returns `SbroadError`:
     /// - child is an inconsistent relational node
     /// - column names don't exist
     pub fn add_row_from_child(
         &mut self,
         child: usize,
         col_names: &[&str],
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let list = self.new_columns(&[child], false, &[0], col_names, false, true)?;
         Ok(self.nodes.add_row(list, None))
     }
@@ -641,7 +682,7 @@ impl Plan {
     /// New columns don't have aliases. If column names are empty,
     /// copy all the columns from the child.
     /// # Errors
-    /// Returns `QueryPlannerError`:
+    /// Returns `SbroadError`:
     /// - children nodes are not a relational
     /// - column names don't exist
     pub fn add_row_from_sub_query(
@@ -649,7 +690,7 @@ impl Plan {
         children: &[usize],
         children_pos: usize,
         col_names: &[&str],
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let list = self.new_columns(children, false, &[children_pos], col_names, false, true)?;
         Ok(self.nodes.add_row(list, None))
     }
@@ -659,7 +700,7 @@ impl Plan {
     /// New columns don't have aliases. If column names are empty,
     /// copy all the columns from the left child.
     /// # Errors
-    /// Returns `QueryPlannerError`:
+    /// Returns `SbroadError`:
     /// - children are inconsistent relational nodes
     /// - column names don't exist
     pub fn add_row_from_left_branch(
@@ -667,7 +708,7 @@ impl Plan {
         left: usize,
         right: usize,
         col_names: &[&str],
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let list = self.new_columns(&[left, right], true, &[0], col_names, false, true)?;
         Ok(self.nodes.add_row(list, None))
     }
@@ -677,7 +718,7 @@ impl Plan {
     /// New columns don't have aliases. If column names are empty,
     /// copy all the columns from the right child.
     /// # Errors
-    /// Returns `QueryPlannerError`:
+    /// Returns `SbroadError`:
     /// - children are inconsistent relational nodes
     /// - column names don't exist
     pub fn add_row_from_right_branch(
@@ -685,7 +726,7 @@ impl Plan {
         left: usize,
         right: usize,
         col_names: &[&str],
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let list = self.new_columns(&[left, right], true, &[1], col_names, false, true)?;
         Ok(self.nodes.add_row(list, None))
     }
@@ -697,10 +738,7 @@ impl Plan {
     ///
     /// # Errors
     /// - reference is invalid
-    pub fn get_relational_from_reference_node(
-        &self,
-        ref_id: usize,
-    ) -> Result<&usize, QueryPlannerError> {
+    pub fn get_relational_from_reference_node(&self, ref_id: usize) -> Result<&usize, SbroadError> {
         if let Node::Expression(Expression::Reference {
             targets, parent, ..
         }) = self.get_node(ref_id)?
@@ -708,8 +746,9 @@ impl Plan {
             let referred_rel_id = if let Some(parent) = parent {
                 parent
             } else {
-                return Err(QueryPlannerError::CustomError(
-                    "Reference node has no parent".into(),
+                return Err(SbroadError::NotFound(
+                    Entity::Node,
+                    "that is Reference parent".into(),
                 ));
             };
             let rel = self.get_relation_node(*referred_rel_id)?;
@@ -718,7 +757,7 @@ impl Plan {
             } else if let Some(children) = rel.children() {
                 match targets {
                     None => {
-                        return Err(QueryPlannerError::CustomError(
+                        return Err(SbroadError::UnexpectedNumberOfValues(
                             "Reference node has no targets".into(),
                         ))
                     }
@@ -735,13 +774,13 @@ impl Plan {
                             if let Relational::Motion { .. } = rel {
                                 return Ok(referred_rel_id);
                             }
-                            return Err(QueryPlannerError::CustomError(format!(
-                                "Relational node {:?} has no child at first position",
+                            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                                "Relational node {:?} has no children",
                                 rel
                             )));
                         }
                         _ => {
-                            return Err(QueryPlannerError::CustomError(
+                            return Err(SbroadError::UnexpectedNumberOfValues(
                                 "Reference expected to point exactly a single relational node"
                                     .into(),
                             ))
@@ -750,7 +789,7 @@ impl Plan {
                 }
             }
         }
-        Err(QueryPlannerError::InvalidReference)
+        Err(SbroadError::Invalid(Entity::Expression, None))
     }
 
     /// Get relational nodes referenced in the row.
@@ -762,11 +801,14 @@ impl Plan {
     pub fn get_relational_from_row_nodes(
         &self,
         row_id: usize,
-    ) -> Result<HashSet<usize, RandomState>, QueryPlannerError> {
+    ) -> Result<HashSet<usize, RandomState>, SbroadError> {
         let row = self.get_expression_node(row_id)?;
         if let Expression::Row { .. } = row {
         } else {
-            return Err(QueryPlannerError::CustomError("Node is not a row".into()));
+            return Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("Node is not a row".into()),
+            ));
         }
         let post_tree = DftPost::new(&row_id, |node| self.nodes.expr_iter(node, false));
         let nodes: Vec<usize> = post_tree.map(|(_, id)| *id).collect();
@@ -778,8 +820,9 @@ impl Plan {
                 targets, parent, ..
             } = reference
             {
-                let referred_rel_id = parent.ok_or(QueryPlannerError::CustomError(
-                    "Reference node has no parent".into(),
+                let referred_rel_id = parent.ok_or(SbroadError::NotFound(
+                    Entity::Node,
+                    "that is Reference parent".into(),
                 ))?;
                 let rel = self.get_relation_node(referred_rel_id)?;
                 if let Some(children) = rel.children() {
@@ -831,7 +874,7 @@ impl Plan {
     ///
     /// # Errors
     /// - If node is not an expression.
-    pub fn is_trivalent(&self, expr_id: usize) -> Result<bool, QueryPlannerError> {
+    pub fn is_trivalent(&self, expr_id: usize) -> Result<bool, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         match expr {
             Expression::Bool { .. }
@@ -854,7 +897,7 @@ impl Plan {
     ///
     /// # Errors
     /// - If node is not an expression.
-    pub fn is_ref(&self, expr_id: usize) -> Result<bool, QueryPlannerError> {
+    pub fn is_ref(&self, expr_id: usize) -> Result<bool, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         match expr {
             Expression::Reference { .. } => return Ok(true),
@@ -879,18 +922,21 @@ impl Plan {
         &self,
         row_id: usize,
         child_num: usize,
-    ) -> Result<Value, QueryPlannerError> {
+    ) -> Result<Value, SbroadError> {
         let node = self.get_expression_node(row_id)?;
         if let Expression::Row { list, .. } = node {
-            let const_node_id = list.get(child_num).ok_or_else(|| {
-                QueryPlannerError::CustomError(format!("Child {child_num} wasn't found"))
-            })?;
+            let const_node_id = list
+                .get(child_num)
+                .ok_or_else(|| SbroadError::NotFound(Entity::Node, format!("{child_num}")))?;
 
             let v = self.get_expression_node(*const_node_id)?.as_const_value()?;
 
             return Ok(v);
         }
-        Err(QueryPlannerError::InvalidRow)
+        Err(SbroadError::Invalid(
+            Entity::Node,
+            Some("node is not Row type".into()),
+        ))
     }
 
     /// Replace parent for all references in the expression subtree of the current node.
@@ -903,7 +949,7 @@ impl Plan {
         node_id: usize,
         from_id: Option<usize>,
         to_id: Option<usize>,
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         let mut references: Vec<usize> = Vec::new();
         let subtree = DftPost::new(&node_id, |node| self.nodes.expr_iter(node, false));
         for (_, id) in subtree {
diff --git a/sbroad-core/src/ir/expression/cast.rs b/sbroad-core/src/ir/expression/cast.rs
index cf7117c37a..90ef2de506 100644
--- a/sbroad-core/src/ir/expression/cast.rs
+++ b/sbroad-core/src/ir/expression/cast.rs
@@ -1,6 +1,6 @@
 use std::fmt::{Display, Formatter};
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::frontend::sql::ast::Type as AstType;
 use crate::ir::expression::Expression;
 use crate::ir::{Node, Plan};
@@ -22,7 +22,7 @@ pub enum Type {
 }
 
 impl TryFrom<&AstType> for Type {
-    type Error = QueryPlannerError;
+    type Error = SbroadError;
 
     /// Pay attention that we can't build `Type::Varchar(length)` from string
     /// because it has an additional length parameter. It should be constructed
@@ -39,10 +39,10 @@ impl TryFrom<&AstType> for Type {
             AstType::TypeString => Ok(Type::String),
             AstType::TypeText => Ok(Type::Text),
             AstType::TypeUnsigned => Ok(Type::Unsigned),
-            _ => Err(QueryPlannerError::CustomError(format!(
-                "Unsupported type: {:?}",
-                ast_type
-            ))),
+            _ => Err(SbroadError::Unsupported(
+                Entity::Type,
+                Some(format!("{ast_type:?}")),
+            )),
         }
     }
 }
@@ -76,7 +76,7 @@ impl Plan {
     ///
     /// # Errors
     /// - Child node is not of the expression type.
-    pub fn add_cast(&mut self, expr_id: usize, to_type: Type) -> Result<usize, QueryPlannerError> {
+    pub fn add_cast(&mut self, expr_id: usize, to_type: Type) -> Result<usize, SbroadError> {
         self.get_expression_node(expr_id)?;
         let cast_expr = Expression::Cast {
             child: expr_id,
diff --git a/sbroad-core/src/ir/expression/concat.rs b/sbroad-core/src/ir/expression/concat.rs
index 30dda78f75..021ee508ce 100644
--- a/sbroad-core/src/ir/expression/concat.rs
+++ b/sbroad-core/src/ir/expression/concat.rs
@@ -1,4 +1,4 @@
-use crate::errors::QueryPlannerError;
+use crate::errors::SbroadError;
 use crate::ir::expression::Expression;
 use crate::ir::{Node, Plan};
 
@@ -7,11 +7,7 @@ impl Plan {
     ///
     /// # Errors
     /// - Left or right child nodes are not of the expression type.
-    pub fn add_concat(
-        &mut self,
-        left_id: usize,
-        right_id: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    pub fn add_concat(&mut self, left_id: usize, right_id: usize) -> Result<usize, SbroadError> {
         // Check that both children are of expression type.
         for child_id in &[left_id, right_id] {
             self.get_expression_node(*child_id)?;
diff --git a/sbroad-core/src/ir/expression/tests.rs b/sbroad-core/src/ir/expression/tests.rs
index 7af4fb63dd..d398544934 100644
--- a/sbroad-core/src/ir/expression/tests.rs
+++ b/sbroad-core/src/ir/expression/tests.rs
@@ -2,7 +2,7 @@ use pretty_assertions::assert_eq;
 
 use crate::ir::relation::{Column, ColumnRole, Table, Type};
 use crate::ir::value::Value;
-use crate::ir::{Plan, QueryPlannerError};
+use crate::ir::{Plan, SbroadError};
 
 #[test]
 fn row_duplicate_column_names() {
@@ -13,9 +13,7 @@ fn row_duplicate_column_names() {
     let c2 = plan.nodes.add_const(Value::from(2_u64));
     let c2_alias_a = plan.nodes.add_alias("a", c2).unwrap();
     assert_eq!(
-        QueryPlannerError::CustomError(
-            "Row can't be added because `a` already has an alias".into()
-        ),
+        SbroadError::DuplicatedValue("row can't be added because `a` already has an alias".into()),
         plan.nodes
             .add_row_of_aliases(vec![c1_alias_a, c2_alias_a], None)
             .unwrap_err()
diff --git a/sbroad-core/src/ir/function.rs b/sbroad-core/src/ir/function.rs
index d45bbea7ad..9413c23241 100644
--- a/sbroad-core/src/ir/function.rs
+++ b/sbroad-core/src/ir/function.rs
@@ -1,4 +1,4 @@
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::{Node, Plan};
 use serde::{Deserialize, Serialize};
@@ -46,12 +46,12 @@ impl Plan {
         &mut self,
         function: &Function,
         children: Vec<usize>,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         if !function.is_stable() {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Function {} is not stable",
-                function.name
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::SQLFunction,
+                Some(format!("function {} is not stable", function.name)),
+            ));
         }
         let func_expr = Expression::StableFunction {
             name: function.name.to_string(),
diff --git a/sbroad-core/src/ir/operator.rs b/sbroad-core/src/ir/operator.rs
index 62a9aec67f..91538ca33b 100644
--- a/sbroad-core/src/ir/operator.rs
+++ b/sbroad-core/src/ir/operator.rs
@@ -4,10 +4,11 @@
 
 use ahash::RandomState;
 use serde::{Deserialize, Serialize};
+use serde_yaml::mapping::Entry;
 use std::collections::{HashMap, HashSet};
 use std::fmt::{Display, Formatter};
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 
 use super::expression::Expression;
 use super::transformation::redistribution::{DataGeneration, MotionPolicy};
@@ -47,8 +48,8 @@ impl Bool {
     /// Creates `Bool` from the operator string.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the operator is invalid.
-    pub fn from(s: &str) -> Result<Self, QueryPlannerError> {
+    /// Returns `SbroadError` when the operator is invalid.
+    pub fn from(s: &str) -> Result<Self, SbroadError> {
         match s.to_lowercase().as_str() {
             "and" => Ok(Bool::And),
             "or" => Ok(Bool::Or),
@@ -60,7 +61,7 @@ impl Bool {
             "<=" => Ok(Bool::LtEq),
             "!=" | "<>" => Ok(Bool::NotEq),
             "not in" => Ok(Bool::NotIn),
-            _ => Err(QueryPlannerError::InvalidBool),
+            _ => Err(SbroadError::Unsupported(Entity::Operator, None)),
         }
     }
 }
@@ -98,15 +99,17 @@ impl Unary {
     /// Creates `Unary` from the operator string.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the operator is invalid.
-    pub fn from(s: &str) -> Result<Self, QueryPlannerError> {
+    /// Returns `SbroadError` when the operator is invalid.
+    pub fn from(s: &str) -> Result<Self, SbroadError> {
         match s.to_lowercase().as_str() {
             "is null" => Ok(Unary::IsNull),
             "is not null" => Ok(Unary::IsNotNull),
-            _ => Err(QueryPlannerError::CustomError(format!(
-                "Invalid unary operator: {}",
-                s
-            ))),
+            _ => Err(SbroadError::Invalid(
+                Entity::Operator,
+                Some(format!(
+                    "expected `is null` or `is not null`, got unary operator `{s}`"
+                )),
+            )),
         }
     }
 }
@@ -242,11 +245,11 @@ impl Relational {
     /// is a row of aliases with unique names.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the output tuple is invalid.
+    /// Returns `SbroadError` when the output tuple is invalid.
     pub fn output_alias_position_map<'rel_op, 'nodes>(
         &'rel_op self,
         nodes: &'nodes Nodes,
-    ) -> Result<HashMap<&'nodes str, usize, RandomState>, QueryPlannerError> {
+    ) -> Result<HashMap<&'nodes str, usize, RandomState>, SbroadError> {
         if let Some(Node::Expression(Expression::Row { list, .. })) = nodes.arena.get(self.output())
         {
             let state = RandomState::new();
@@ -267,12 +270,14 @@ impl Relational {
             if valid {
                 return Ok(map);
             }
-            return Err(QueryPlannerError::CustomError(
-                "Invalid output tuple".to_string(),
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("invalid output tuple".to_string()),
             ));
         }
-        Err(QueryPlannerError::CustomError(
-            "Failed to find an output tuple node in the arena".to_string(),
+        Err(SbroadError::NotFound(
+            Entity::Node,
+            "(an output tuple) from the arena".to_string(),
         ))
     }
 
@@ -390,7 +395,7 @@ impl Relational {
     ///
     /// # Errors
     /// - try to set children for the scan node (it is always a leaf node)
-    pub fn set_children(&mut self, children: Vec<usize>) -> Result<(), QueryPlannerError> {
+    pub fn set_children(&mut self, children: Vec<usize>) -> Result<(), SbroadError> {
         match self {
             Relational::Except {
                 children: ref mut old,
@@ -435,9 +440,10 @@ impl Relational {
                 *old = children;
                 Ok(())
             }
-            Relational::ScanRelation { .. } => Err(QueryPlannerError::CustomError(String::from(
-                "Scan is a leaf node",
-            ))),
+            Relational::ScanRelation { .. } => Err(SbroadError::Invalid(
+                Entity::Relational,
+                Some("Scan is a leaf node".into()),
+            )),
         }
     }
 
@@ -449,7 +455,7 @@ impl Relational {
         &'n self,
         plan: &'n Plan,
         position: usize,
-    ) -> Result<Option<&'n str>, QueryPlannerError> {
+    ) -> Result<Option<&'n str>, SbroadError> {
         match self {
             Relational::Insert { relation, .. } => Ok(Some(relation.as_str())),
             Relational::ScanRelation {
@@ -461,10 +467,10 @@ impl Relational {
                 let output_row = plan.get_expression_node(self.output())?;
                 let list = output_row.get_row_list()?;
                 let col_id = *list.get(position).ok_or_else(|| {
-                    QueryPlannerError::CustomError(format!(
-                        "Row doesn't has a column at position {}",
-                        position
-                    ))
+                    SbroadError::NotFound(
+                        Entity::Column,
+                        format!("at position {} of Row", position),
+                    )
                 })?;
                 let col_node = plan.get_expression_node(col_id)?;
                 if let Expression::Alias { child, .. } = col_node {
@@ -473,7 +479,7 @@ impl Relational {
                         let rel_id = *plan.get_relational_from_reference_node(*child)?;
                         let rel_node = plan.get_relation_node(rel_id)?;
                         if rel_node == self {
-                            return Err(QueryPlannerError::CustomError(format!(
+                            return Err(SbroadError::DuplicatedValue(format!(
                                 "Reference to the same node {:?} at position {}",
                                 rel_node, position
                             )));
@@ -481,9 +487,10 @@ impl Relational {
                         return rel_node.scan_name(plan, *pos);
                     }
                 } else {
-                    return Err(QueryPlannerError::CustomError(String::from(
-                        "Expected an alias in the output row",
-                    )));
+                    return Err(SbroadError::Invalid(
+                        Entity::Expression,
+                        Some("expected an alias in the output row".into()),
+                    ));
                 }
                 Ok(None)
             }
@@ -501,15 +508,16 @@ impl Relational {
     ///
     /// # Errors
     /// - relational node is not a scan.
-    pub fn set_scan_name(&mut self, name: Option<String>) -> Result<(), QueryPlannerError> {
+    pub fn set_scan_name(&mut self, name: Option<String>) -> Result<(), SbroadError> {
         match self {
             Relational::ScanRelation { ref mut alias, .. }
             | Relational::ScanSubQuery { ref mut alias, .. } => {
                 *alias = name;
                 Ok(())
             }
-            _ => Err(QueryPlannerError::CustomError(
-                "Relational node is not a scan.".into(),
+            _ => Err(SbroadError::Invalid(
+                Entity::Relational,
+                Some("Relational node is not a Scan.".into()),
             )),
         }
     }
@@ -522,8 +530,8 @@ impl Plan {
     /// - children nodes are not relational
     /// - children tuples are invalid
     /// - children tuples have mismatching structure
-    pub fn add_except(&mut self, left: usize, right: usize) -> Result<usize, QueryPlannerError> {
-        let child_row_len = |child: usize, plan: &Plan| -> Result<usize, QueryPlannerError> {
+    pub fn add_except(&mut self, left: usize, right: usize) -> Result<usize, SbroadError> {
+        let child_row_len = |child: usize, plan: &Plan| -> Result<usize, SbroadError> {
             let child_output = plan.get_relation_node(child)?.output();
             Ok(plan
                 .get_expression_node(child_output)?
@@ -534,8 +542,8 @@ impl Plan {
         let left_row_len = child_row_len(left, self)?;
         let right_row_len = child_row_len(right, self)?;
         if left_row_len != right_row_len {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Children tuples have mismatching amount of columns in except node: left {}, right {}",
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                "children tuples have mismatching amount of columns in except node: left {}, right {}",
                 left_row_len, right_row_len
             )));
         }
@@ -560,9 +568,9 @@ impl Plan {
         relation: &str,
         child: usize,
         columns: &[&str],
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let rel = self.relations.get(relation).ok_or_else(|| {
-            QueryPlannerError::CustomError(format!("Invalid relation: {relation}"))
+            SbroadError::NotFound(Entity::Table, format!("{relation} among plan relations"))
         })?;
         let columns: Vec<usize> = if columns.is_empty() {
             rel.columns
@@ -582,17 +590,13 @@ impl Plan {
                 match names.get(name) {
                     Some((&ColumnRole::User, pos)) => cols.push(*pos),
                     Some((&ColumnRole::Sharding, _)) => {
-                        return Err(QueryPlannerError::CustomError(format!(
-                            "System column {} cannot be inserted",
-                            name
-                        )))
-                    }
-                    None => {
-                        return Err(QueryPlannerError::CustomError(format!(
-                            "Column {} does not exist",
-                            name
-                        )))
+                        return Err(SbroadError::FailedTo(
+                            Action::Insert,
+                            Some(Entity::Column),
+                            format!("system column {name} cannot be inserted"),
+                        ))
                     }
+                    None => return Err(SbroadError::NotFound(Entity::Column, (*name).to_string())),
                 }
             }
             cols
@@ -602,13 +606,14 @@ impl Plan {
         let child_output_list_len = if let Expression::Row { list, .. } = child_output {
             list.len()
         } else {
-            return Err(QueryPlannerError::CustomError(String::from(
-                "Child output is not a row.",
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("child output is not a Row.".into()),
+            ));
         };
         if child_output_list_len != columns.len() {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Invalid number of values: {}. Table {} expects {} column(s).",
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                "invalid number of values: {}. Table {} expects {} column(s).",
                 child_output_list_len,
                 relation,
                 columns.len()
@@ -641,11 +646,7 @@ impl Plan {
     ///
     /// # Errors
     /// - relation is invalid
-    pub fn add_scan(
-        &mut self,
-        table: &str,
-        alias: Option<&str>,
-    ) -> Result<usize, QueryPlannerError> {
+    pub fn add_scan(&mut self, table: &str, alias: Option<&str>) -> Result<usize, SbroadError> {
         let nodes = &mut self.nodes;
 
         if let Some(rel) = self.relations.get(table) {
@@ -667,10 +668,10 @@ impl Plan {
             self.replace_parent_in_subtree(output_id, None, Some(scan_id))?;
             return Ok(scan_id);
         }
-        Err(QueryPlannerError::CustomError(format!(
-            "Failed to find relation {} among the plan relations",
-            table
-        )))
+        Err(SbroadError::NotFound(
+            Entity::Table,
+            format!("{} among the plan relations", table),
+        ))
     }
 
     /// Adds inner join node.
@@ -684,11 +685,12 @@ impl Plan {
         left: usize,
         right: usize,
         condition: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         if !self.is_trivalent(condition)? {
-            return Err(QueryPlannerError::CustomError(String::from(
-                "Condition is not a trivalent expression",
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("Condition is not a trivalent expression".into()),
+            ));
         }
 
         // For any child in a relational scan, we need to
@@ -707,10 +709,10 @@ impl Plan {
             {
                 // We'll need it later to update the condition expression (borrow checker).
                 let table = self.get_relation(relation).ok_or_else(|| {
-                    QueryPlannerError::CustomError(format!(
-                        "Failed to find relation {} in the plan",
-                        relation
-                    ))
+                    SbroadError::NotFound(
+                        Entity::Table,
+                        format!("{} among plan relations", relation),
+                    )
                 })?;
                 let sharding_column_pos = table.get_bucket_id_position()?;
 
@@ -761,8 +763,9 @@ impl Plan {
             self.replace_parent_in_subtree(output, None, Some(join_id))?;
             return Ok(join_id);
         }
-        Err(QueryPlannerError::CustomError(
-            "Invalid children for join".to_string(),
+        Err(SbroadError::Invalid(
+            Entity::Expression,
+            Some("invalid children for join".to_string()),
         ))
     }
 
@@ -776,11 +779,11 @@ impl Plan {
         child_id: usize,
         policy: &MotionPolicy,
         generation: &DataGeneration,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let alias = if let Node::Relational(rel) = self.get_node(child_id)? {
             rel.scan_name(self, 0)?.map(String::from)
         } else {
-            return Err(QueryPlannerError::InvalidRelation);
+            return Err(SbroadError::Invalid(Entity::Relational, None));
         };
 
         let output = self.add_row_for_output(child_id, &[], true)?;
@@ -805,11 +808,7 @@ impl Plan {
     /// - child node is not relational
     /// - child output tuple is invalid
     /// - column name do not match the ones in the child output tuple
-    pub fn add_proj(
-        &mut self,
-        child: usize,
-        col_names: &[&str],
-    ) -> Result<usize, QueryPlannerError> {
+    pub fn add_proj(&mut self, child: usize, col_names: &[&str]) -> Result<usize, SbroadError> {
         let output = self.add_row_for_output(child, col_names, false)?;
         let proj = Relational::Projection {
             children: vec![child],
@@ -831,7 +830,7 @@ impl Plan {
         &mut self,
         child: usize,
         columns: &[usize],
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let output = self.nodes.add_row_of_aliases(columns.to_vec(), None)?;
         let proj = Relational::Projection {
             children: vec![child],
@@ -850,26 +849,27 @@ impl Plan {
     /// - filter expression is not boolean
     /// - children nodes are not relational
     /// - first child output tuple is not valid
-    pub fn add_select(
-        &mut self,
-        children: &[usize],
-        filter: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    pub fn add_select(&mut self, children: &[usize], filter: usize) -> Result<usize, SbroadError> {
         let first_child: usize = match children.len() {
-            0 => return Err(QueryPlannerError::InvalidInput),
+            0 => {
+                return Err(SbroadError::UnexpectedNumberOfValues(
+                    "children list is empty".into(),
+                ))
+            }
             _ => children[0],
         };
 
         if !self.is_trivalent(filter)? {
-            return Err(QueryPlannerError::CustomError(
-                "Filter expression is not a trivalent expression.".into(),
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("filter expression is not a trivalent expression.".into()),
             ));
         }
 
         for child in children {
             if let Node::Relational(_) = self.get_node(*child)? {
             } else {
-                return Err(QueryPlannerError::InvalidRelation);
+                return Err(SbroadError::Invalid(Entity::Relational, None));
             }
         }
 
@@ -895,10 +895,10 @@ impl Plan {
         &mut self,
         child: usize,
         alias: Option<&str>,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let name: Option<String> = if let Some(name) = alias {
             if name.is_empty() {
-                return Err(QueryPlannerError::InvalidName);
+                return Err(SbroadError::Invalid(Entity::Name, None));
             }
             Some(String::from(name))
         } else {
@@ -923,8 +923,8 @@ impl Plan {
     /// - children nodes are not relational
     /// - children tuples are invalid
     /// - children tuples have mismatching structure
-    pub fn add_union_all(&mut self, left: usize, right: usize) -> Result<usize, QueryPlannerError> {
-        let child_row_len = |child: usize, plan: &Plan| -> Result<usize, QueryPlannerError> {
+    pub fn add_union_all(&mut self, left: usize, right: usize) -> Result<usize, SbroadError> {
+        let child_row_len = |child: usize, plan: &Plan| -> Result<usize, SbroadError> {
             let child_output = plan.get_relation_node(child)?.output();
             Ok(plan
                 .get_expression_node(child_output)?
@@ -935,8 +935,8 @@ impl Plan {
         let left_row_len = child_row_len(left, self)?;
         let right_row_len = child_row_len(right, self)?;
         if left_row_len != right_row_len {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Children tuples have mismatching amount of columns in union all node: left {}, right {}",
+            return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                "children tuples have mismatching amount of columns in union all node: left {}, right {}",
                 left_row_len, right_row_len
             )));
         }
@@ -960,7 +960,7 @@ impl Plan {
         &mut self,
         row_id: usize,
         col_idx: &mut usize,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let row = self.get_expression_node(row_id)?;
         let columns = row.clone_row_list()?;
         let mut aliases: Vec<usize> = Vec::with_capacity(columns.len());
@@ -988,21 +988,21 @@ impl Plan {
     /// # Errors
     /// - No child nodes
     /// - Child node is not relational
-    pub fn add_values(&mut self, children: Vec<usize>) -> Result<usize, QueryPlannerError> {
+    pub fn add_values(&mut self, children: Vec<usize>) -> Result<usize, SbroadError> {
         // Get the last row of the children list. We need it to
         // get the correct anonymous column names.
         let last_id = if let Some(last_id) = children.last() {
             *last_id
         } else {
-            return Err(QueryPlannerError::CustomError(
-                "Values node must have at least one child.".into(),
+            return Err(SbroadError::UnexpectedNumberOfValues(
+                "Values node has no children, expected at least one child.".into(),
             ));
         };
         let child_last = self.get_relation_node(last_id)?;
         let last_output_id = if let Relational::ValuesRow { output, .. } = child_last {
             *output
         } else {
-            return Err(QueryPlannerError::CustomError(
+            return Err(SbroadError::UnexpectedNumberOfValues(
                 "Values node must have at least one child row.".into(),
             ));
         };
@@ -1014,12 +1014,15 @@ impl Plan {
                 if let Expression::Alias { name, .. } = alias {
                     aliases.push(name.clone());
                 } else {
-                    return Err(QueryPlannerError::CustomError("Expected an alias".into()));
+                    return Err(SbroadError::Invalid(
+                        Entity::Expression,
+                        Some("Expected an alias".into()),
+                    ));
                 }
             }
             aliases
         } else {
-            return Err(QueryPlannerError::CustomError(
+            return Err(SbroadError::UnexpectedNumberOfValues(
                 "Values node must have at least one child row.".into(),
             ));
         };
@@ -1045,11 +1048,11 @@ impl Plan {
     ///
     /// # Errors
     /// - node is not relational
-    pub fn get_relational_output(&self, rel_id: usize) -> Result<usize, QueryPlannerError> {
+    pub fn get_relational_output(&self, rel_id: usize) -> Result<usize, SbroadError> {
         if let Node::Relational(rel) = self.get_node(rel_id)? {
             Ok(rel.output())
         } else {
-            Err(QueryPlannerError::InvalidRelation)
+            Err(SbroadError::Invalid(Entity::Relational, None))
         }
     }
 
@@ -1057,15 +1060,13 @@ impl Plan {
     ///
     /// # Errors
     /// - node is not relational
-    pub fn get_relational_children(
-        &self,
-        rel_id: usize,
-    ) -> Result<Option<&[usize]>, QueryPlannerError> {
+    pub fn get_relational_children(&self, rel_id: usize) -> Result<Option<&[usize]>, SbroadError> {
         if let Node::Relational(rel) = self.get_node(rel_id)? {
             Ok(rel.children())
         } else {
-            Err(QueryPlannerError::CustomError(
-                "Invalid relational node".into(),
+            Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("invalid relational node".into()),
             ))
         }
     }
@@ -1076,33 +1077,33 @@ impl Plan {
     /// - Node is not values row
     /// - Output and data tuples have different number of columns
     /// - Output is not a row of aliases
-    pub fn update_values_row(&mut self, id: usize) -> Result<(), QueryPlannerError> {
+    pub fn update_values_row(&mut self, id: usize) -> Result<(), SbroadError> {
         let values_row = self.get_node(id)?;
         let (output_id, data_id) =
             if let Node::Relational(Relational::ValuesRow { output, data, .. }) = values_row {
                 (*output, *data)
             } else {
-                return Err(QueryPlannerError::CustomError(format!(
-                    "Expected a values row: {:?}",
-                    values_row
-                )));
+                return Err(SbroadError::Invalid(
+                    Entity::Expression,
+                    Some(format!("Expected a values row: {values_row:?}")),
+                ));
             };
         let data = self.get_expression_node(data_id)?;
         let data_list = data.clone_row_list()?;
         let output = self.get_expression_node(output_id)?;
         let output_list = output.clone_row_list()?;
         for (pos, alias_id) in output_list.iter().enumerate() {
-            let new_child_id = *data_list.get(pos).ok_or_else(|| {
-                QueryPlannerError::CustomError(format!("Expected a child at position {pos}"))
-            })?;
+            let new_child_id = *data_list
+                .get(pos)
+                .ok_or_else(|| SbroadError::NotFound(Entity::Node, format!("at position {pos}")))?;
             let alias = self.get_mut_expression_node(*alias_id)?;
             if let Expression::Alias { ref mut child, .. } = alias {
                 *child = new_child_id;
             } else {
-                return Err(QueryPlannerError::CustomError(format!(
-                    "Expected an alias: {:?}",
-                    alias
-                )));
+                return Err(SbroadError::Invalid(
+                    Entity::Expression,
+                    Some(format!("expected an alias: {alias:?}")),
+                ));
             }
         }
         Ok(())
@@ -1117,17 +1118,19 @@ impl Plan {
         &mut self,
         rel_id: usize,
         children: Vec<usize>,
-    ) -> Result<(), QueryPlannerError> {
-        if let Node::Relational(ref mut rel) = self
-            .nodes
-            .arena
-            .get_mut(rel_id)
-            .ok_or(QueryPlannerError::ValueOutOfRange)?
+    ) -> Result<(), SbroadError> {
+        if let Node::Relational(ref mut rel) =
+            self.nodes.arena.get_mut(rel_id).ok_or_else(|| {
+                SbroadError::NotFound(
+                    Entity::Node,
+                    format!("(mutable) from arena with index {rel_id}"),
+                )
+            })?
         {
             rel.set_children(children)?;
             Ok(())
         } else {
-            Err(QueryPlannerError::InvalidRelation)
+            Err(SbroadError::Invalid(Entity::Relational, None))
         }
     }
 
@@ -1136,7 +1139,7 @@ impl Plan {
     /// # Errors
     /// - Failed to get plan top
     /// - Node returned by the relational iterator is not relational (bug)
-    pub fn is_additional_child(&self, node_id: usize) -> Result<bool, QueryPlannerError> {
+    pub fn is_additional_child(&self, node_id: usize) -> Result<bool, SbroadError> {
         let top_id = self.get_top()?;
         let rel_tree = Bft::new(&top_id, |node| self.nodes.rel_iter(node));
         for (_, id) in rel_tree {
@@ -1166,7 +1169,7 @@ impl Plan {
         &self,
         rel_id: usize,
         sq_id: usize,
-    ) -> Result<bool, QueryPlannerError> {
+    ) -> Result<bool, SbroadError> {
         let children = if let Some(children) = self.get_relational_children(rel_id)? {
             children
         } else {
diff --git a/sbroad-core/src/ir/operator/tests.rs b/sbroad-core/src/ir/operator/tests.rs
index 52a3b2739b..e3c12e58fc 100644
--- a/sbroad-core/src/ir/operator/tests.rs
+++ b/sbroad-core/src/ir/operator/tests.rs
@@ -4,7 +4,7 @@ use std::path::Path;
 use pretty_assertions::assert_eq;
 
 use crate::collection;
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::distribution::{Distribution, Key};
 use crate::ir::relation::{Column, ColumnRole, Table, Type};
 use crate::ir::value::Value;
@@ -103,21 +103,19 @@ fn projection() {
 
     // Invalid alias names in the output
     assert_eq!(
-        QueryPlannerError::CustomError(
-            r#"Some of the columns ["a", "e"] were not found in the table"#.into()
-        ),
+        SbroadError::NotFound(Entity::Column, r#"with name ["a", "e"]"#.into()),
         plan.add_proj(scan_id, &["a", "e"]).unwrap_err()
     );
 
     // Expression node instead of relational one
     assert_eq!(
-        QueryPlannerError::CustomError("Node isn't relational".into()),
+        SbroadError::Invalid(Entity::Node, Some("node is not Relational type".into())),
         plan.add_proj(1, &["a"]).unwrap_err()
     );
 
     // Try to build projection from the non-existing node
     assert_eq!(
-        QueryPlannerError::ValueOutOfRange,
+        SbroadError::NotFound(Entity::Node, format!("from arena with index 42")),
         plan.add_proj(42, &["a"]).unwrap_err()
     );
 }
@@ -164,15 +162,24 @@ fn selection() {
     // Correct Selection operator
     plan.add_select(&[scan_id], gt_id).unwrap();
 
+    // Invalid children list len
+    assert_eq!(
+        SbroadError::UnexpectedNumberOfValues("children list is empty".into(),),
+        plan.add_select(&[], gt_id).unwrap_err()
+    );
+
     // Non-trivalent filter
     assert_eq!(
-        QueryPlannerError::CustomError("Filter expression is not a trivalent expression.".into()),
+        SbroadError::Invalid(
+            Entity::Expression,
+            Some("filter expression is not a trivalent expression.".into())
+        ),
         plan.add_select(&[scan_id], const_row).unwrap_err()
     );
 
     // Non-relational child
     assert_eq!(
-        QueryPlannerError::InvalidRelation,
+        SbroadError::Invalid(Entity::Relational, None),
         plan.add_select(&[const_row], gt_id).unwrap_err()
     );
 }
@@ -189,6 +196,109 @@ fn selection_serialize() {
     Plan::from_yaml(&s).unwrap();
 }
 
+#[test]
+fn except() {
+    let mut valid_plan = Plan::default();
+
+    let t1 = Table::new_seg(
+        "t1",
+        vec![Column::new("a", Type::Unsigned, ColumnRole::User)],
+        &["a"],
+    )
+    .unwrap();
+    let t1_copy = t1.clone();
+    valid_plan.add_rel(t1);
+    let scan_t1_id = valid_plan.add_scan("t1", None).unwrap();
+
+    let t2 = Table::new_seg(
+        "t2",
+        vec![Column::new("a", Type::Unsigned, ColumnRole::User)],
+        &["a"],
+    )
+    .unwrap();
+    valid_plan.add_rel(t2);
+    let scan_t2_id = valid_plan.add_scan("t2", None).unwrap();
+
+    // Correct Except operator
+    valid_plan.add_except(scan_t1_id, scan_t2_id).unwrap();
+
+    let mut invalid_plan = Plan::default();
+
+    invalid_plan.add_rel(t1_copy);
+    let scan_t1_id = invalid_plan.add_scan("t1", None).unwrap();
+
+    let t3 = Table::new_seg(
+        "t3",
+        vec![
+            Column::new("a", Type::Unsigned, ColumnRole::User),
+            Column::new("b", Type::Unsigned, ColumnRole::User),
+        ],
+        &["a"],
+    )
+    .unwrap();
+    invalid_plan.add_rel(t3);
+    let scan_t3_id = invalid_plan.add_scan("t3", None).unwrap();
+
+    assert_eq!(
+        SbroadError::UnexpectedNumberOfValues(
+            "children tuples have mismatching amount of columns in except node: left 1, right 2"
+                .into()
+        ),
+        invalid_plan.add_except(scan_t1_id, scan_t3_id).unwrap_err()
+    );
+}
+
+#[test]
+fn insert() {
+    let mut plan = Plan::default();
+
+    let t1 = Table::new_seg(
+        "t1",
+        vec![Column::new("a", Type::Unsigned, ColumnRole::User)],
+        &["a"],
+    )
+    .unwrap();
+
+    plan.add_rel(t1);
+    let scan_t1_id = plan.add_scan("t1", None).unwrap();
+
+    let t2 = Table::new_seg(
+        "t2",
+        vec![
+            Column::new("a", Type::Unsigned, ColumnRole::User),
+            Column::new("b", Type::Unsigned, ColumnRole::User),
+            Column::new("c", Type::Unsigned, ColumnRole::Sharding),
+        ],
+        &["a"],
+    )
+    .unwrap();
+    plan.add_rel(t2);
+
+    assert_eq!(
+        SbroadError::NotFound(Entity::Table, "t4 among plan relations".into()),
+        plan.add_insert("t4", scan_t1_id, &["a"]).unwrap_err()
+    );
+
+    assert_eq!(
+        SbroadError::FailedTo(
+            Action::Insert,
+            Some(Entity::Column),
+            "system column c cannot be inserted".into(),
+        ),
+        plan.add_insert("t2", scan_t1_id, &["a", "b", "c"])
+            .unwrap_err()
+    );
+
+    assert_eq!(
+        SbroadError::UnexpectedNumberOfValues(
+            "invalid number of values: 1. Table t2 expects 2 column(s).".into()
+        ),
+        plan.add_insert("t2", scan_t1_id, &["a", "b"]).unwrap_err()
+    );
+
+    plan.add_insert("t1", scan_t1_id, &["a"]).unwrap();
+}
+
 #[test]
 fn union_all() {
     let mut plan = Plan::default();
@@ -242,8 +352,8 @@ fn union_all_col_amount_mismatch() {
 
     let scan_t2_id = plan.add_scan("t2", None).unwrap();
     assert_eq!(
-        QueryPlannerError::CustomError(
-            "Children tuples have mismatching amount of columns in union all node: left 1, right 2"
+        SbroadError::UnexpectedNumberOfValues(
+            "children tuples have mismatching amount of columns in union all node: left 1, right 2"
                 .into()
         ),
         plan.add_union_all(scan_t2_id, scan_t1_id).unwrap_err()
@@ -271,13 +381,13 @@ fn sub_query() {
     // Non-relational child node
     let a = 1;
     assert_eq!(
-        QueryPlannerError::CustomError("Node isn't relational".into()),
+        SbroadError::Invalid(Entity::Node, Some("node is not Relational type".into())),
         plan.add_sub_query(a, Some("sq")).unwrap_err()
     );
 
     // Invalid name
     assert_eq!(
-        QueryPlannerError::InvalidName,
+        SbroadError::Invalid(Entity::Name, None),
         plan.add_sub_query(scan_id, Some("")).unwrap_err()
     );
 }
@@ -445,9 +555,7 @@ fn join_duplicate_columns() {
         .unwrap();
     let condition = plan.nodes.add_bool(a_row, Bool::Eq, d_row).unwrap();
     assert_eq!(
-        QueryPlannerError::CustomError(
-            "Row can't be added because `a` already has an alias".into()
-        ),
+        SbroadError::DuplicatedValue("row can't be added because `a` already has an alias".into()),
         plan.add_join(scan_t1, scan_t2, condition).unwrap_err()
     );
 }
diff --git a/sbroad-core/src/ir/relation.rs b/sbroad-core/src/ir/relation.rs
index 00f5f0a67a..9f935ce918 100644
--- a/sbroad-core/src/ir/relation.rs
+++ b/sbroad-core/src/ir/relation.rs
@@ -8,7 +8,7 @@ use serde::de::{Error, MapAccess, Visitor};
 use serde::ser::{Serialize as SerSerialize, SerializeMap, Serializer};
 use serde::{Deserialize, Deserializer, Serialize};
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 use crate::ir::value::Value;
 
 use super::distribution::Key;
@@ -34,8 +34,8 @@ impl Type {
     /// Type constructor
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the input arguments are invalid.
-    pub fn new(s: &str) -> Result<Self, QueryPlannerError> {
+    /// Returns `SbroadError` when the input arguments are invalid.
+    pub fn new(s: &str) -> Result<Self, SbroadError> {
         match s.to_string().to_lowercase().as_str() {
             "boolean" => Ok(Type::Boolean),
             "decimal" => Ok(Type::Decimal),
@@ -46,10 +46,7 @@ impl Type {
             "string" => Ok(Type::String),
             "unsigned" => Ok(Type::Unsigned),
             "array" => Ok(Type::Array),
-            v => Err(QueryPlannerError::CustomError(format!(
-                "type `{}` not implemented",
-                v
-            ))),
+            v => Err(SbroadError::NotImplemented(Entity::Type, v.to_string())),
         }
     }
 }
@@ -199,12 +196,8 @@ impl Table {
     /// Table segment constructor.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the input arguments are invalid.
-    pub fn new_seg(
-        name: &str,
-        columns: Vec<Column>,
-        keys: &[&str],
-    ) -> Result<Self, QueryPlannerError> {
+    /// Returns `SbroadError` when the input arguments are invalid.
+    pub fn new_seg(name: &str, columns: Vec<Column>, keys: &[&str]) -> Result<Self, SbroadError> {
         let mut pos_map: HashMap<&str, usize> = HashMap::new();
         let no_duplicates = &columns
             .iter()
@@ -212,7 +205,7 @@ impl Table {
             .all(|(pos, col)| matches!(pos_map.insert(&col.name, pos), None));
 
         if !no_duplicates {
-            return Err(QueryPlannerError::CustomError(
+            return Err(SbroadError::DuplicatedValue(
                 "Table has duplicated columns and couldn't be loaded".into(),
             ));
         }
@@ -221,7 +214,7 @@ impl Table {
             .iter()
             .map(|name| match pos_map.get(*name) {
                 Some(pos) => Ok(*pos),
-                None => Err(QueryPlannerError::InvalidShardingKey),
+                None => Err(SbroadError::Invalid(Entity::ShardingKey, None)),
             })
             .collect::<Result<Vec<usize>, _>>()?;
 
@@ -235,11 +228,17 @@ impl Table {
     /// Table segment from YAML.
     ///
     /// # Errors
-    /// Returns `QueryPlannerError` when the YAML-serialized table is invalid.
-    pub fn seg_from_yaml(s: &str) -> Result<Self, QueryPlannerError> {
+    /// Returns `SbroadError` when the YAML-serialized table is invalid.
+    pub fn seg_from_yaml(s: &str) -> Result<Self, SbroadError> {
         let ts: Table = match serde_yaml::from_str(s) {
             Ok(t) => t,
-            Err(_) => return Err(QueryPlannerError::Serialization),
+            Err(e) => {
+                return Err(SbroadError::FailedTo(
+                    Action::Serialize,
+                    Some(Entity::Table),
+                    format!("{e:?}"),
+                ))
+            }
         };
         let mut uniq_cols: HashSet<&str> = HashSet::new();
         let cols = ts.columns.clone();
@@ -247,7 +246,7 @@ impl Table {
         let no_duplicates = cols.iter().all(|col| uniq_cols.insert(&col.name));
 
         if !no_duplicates {
-            return Err(QueryPlannerError::CustomError(
+            return Err(SbroadError::DuplicatedValue(
                 "Table contains duplicate columns. Unable to convert to YAML.".into(),
             ));
         }
@@ -255,7 +254,10 @@ impl Table {
         let in_range = ts.key.positions.iter().all(|pos| *pos < cols.len());
 
         if !in_range {
-            return Err(QueryPlannerError::ValueOutOfRange);
+            return Err(SbroadError::Invalid(
+                Entity::Value,
+                Some(format!("key positions must be less than {}", cols.len())),
+            ));
         }
 
         Ok(ts)
@@ -265,7 +267,7 @@ impl Table {
     ///
     /// # Errors
     /// - Table doesn't have an exactly one `bucket_id` column.
-    pub fn get_bucket_id_position(&self) -> Result<usize, QueryPlannerError> {
+    pub fn get_bucket_id_position(&self) -> Result<usize, SbroadError> {
         let positions: Vec<usize> = self
             .columns
             .iter()
@@ -275,10 +277,10 @@ impl Table {
             .collect();
         match positions.len().cmp(&1) {
             Ordering::Equal => Ok(positions[0]),
-            Ordering::Greater => Err(QueryPlannerError::CustomError(
+            Ordering::Greater => Err(SbroadError::UnexpectedNumberOfValues(
                 "Table has more than one bucket_id column".into(),
             )),
-            Ordering::Less => Err(QueryPlannerError::CustomError(
+            Ordering::Less => Err(SbroadError::UnexpectedNumberOfValues(
                 "Table has no bucket_id columns".into(),
             )),
         }
@@ -288,17 +290,20 @@ impl Table {
     ///
     /// # Errors
     /// - Table internal inconsistency.
-    pub fn get_sharding_column_names(&self) -> Result<Vec<String>, QueryPlannerError> {
+    pub fn get_sharding_column_names(&self) -> Result<Vec<String>, SbroadError> {
         let mut names: Vec<String> = Vec::with_capacity(self.key.positions.len());
         for pos in &self.key.positions {
             names.push(
                 self.columns
                     .get(*pos)
                     .ok_or_else(|| {
-                        QueryPlannerError::CustomError(format!(
-                            "Table {} has no distribution column at position {}",
-                            self.name, *pos
-                        ))
+                        SbroadError::NotFound(
+                            Entity::Column,
+                            format!(
+                                "(distribution column) at position {} for Table {}",
+                                *pos, self.name
+                            ),
+                        )
                     })?
                     .name
                     .clone(),
diff --git a/sbroad-core/src/ir/relation/tests.rs b/sbroad-core/src/ir/relation/tests.rs
index 96bbc00fcc..0d2c467080 100644
--- a/sbroad-core/src/ir/relation/tests.rs
+++ b/sbroad-core/src/ir/relation/tests.rs
@@ -61,9 +61,44 @@ fn table_seg_duplicate_columns() {
             &["b", "a"],
         )
         .unwrap_err(),
-        QueryPlannerError::CustomError(
-            "Table has duplicated columns and couldn't be loaded".into()
-        )
+        SbroadError::DuplicatedValue("Table has duplicated columns and couldn't be loaded".into())
+    );
+}
+
+#[test]
+fn table_seg_dno_bucket_id_column() {
+    let t1 = Table::new_seg(
+        "t",
+        vec![
+            Column::new("a", Type::Boolean, ColumnRole::User),
+            Column::new("b", Type::Unsigned, ColumnRole::User),
+            Column::new("c", Type::String, ColumnRole::User),
+        ],
+        &["b", "a"],
+    )
+    .unwrap();
+
+    assert_eq!(
+        SbroadError::UnexpectedNumberOfValues("Table has no bucket_id columns".into()),
+        t1.get_bucket_id_position().unwrap_err()
+    );
+
+    let t2 = Table::new_seg(
+        "t",
+        vec![
+            Column::new("a", Type::Boolean, ColumnRole::User),
+            Column::new("b", Type::Unsigned, ColumnRole::User),
+            Column::new("c", Type::String, ColumnRole::User),
+            Column::new("bucket_id", Type::String, ColumnRole::Sharding),
+            Column::new("bucket_id2", Type::String, ColumnRole::Sharding),
+        ],
+        &["b", "a"],
+    )
+    .unwrap();
+
+    assert_eq!(
+        SbroadError::UnexpectedNumberOfValues("Table has more than one bucket_id column".into()),
+        t2.get_bucket_id_position().unwrap_err()
     );
 }
 
@@ -81,7 +116,7 @@ fn table_seg_wrong_key() {
             &["a", "e"],
         )
         .unwrap_err(),
-        QueryPlannerError::InvalidShardingKey
+        SbroadError::Invalid(Entity::ShardingKey, None)
     );
 }
 
@@ -122,7 +157,7 @@ fn table_seg_serialized_duplicate_columns() {
     let s = fs::read_to_string(path).unwrap();
     assert_eq!(
         Table::seg_from_yaml(&s).unwrap_err(),
-        QueryPlannerError::CustomError(
+        SbroadError::DuplicatedValue(
             "Table contains duplicate columns. Unable to convert to YAML.".into()
         )
     );
@@ -139,7 +174,10 @@ fn table_seg_serialized_out_of_range_key() {
     let s = fs::read_to_string(path).unwrap();
     assert_eq!(
         Table::seg_from_yaml(&s).unwrap_err(),
-        QueryPlannerError::ValueOutOfRange
+        SbroadError::Invalid(
+            Entity::Value,
+            Some("key positions must be less than 1".into())
+        )
     );
 }
 
@@ -153,7 +191,11 @@ fn table_seg_serialized_no_key() {
         .join("table_seg_serialized_no_key.yaml");
     let s = fs::read_to_string(path).unwrap();
     let t = Table::seg_from_yaml(&s);
-    assert_eq!(t.unwrap_err(), QueryPlannerError::Serialization);
+    assert_eq!(t.unwrap_err(), SbroadError::FailedTo(
+        Action::Serialize,
+        Some(Entity::Table),
+        "Message(\"invalid type: unit value, expected struct Key\", Some(Pos { marker: Marker { index: 52, line: 5, col: 5 }, path: \"key\" }))".into())
+    );
 }
 
 #[test]
@@ -167,7 +209,11 @@ fn table_seg_serialized_no_columns() {
     let s = fs::read_to_string(path).unwrap();
     assert_eq!(
         Table::seg_from_yaml(&s).unwrap_err(),
-        QueryPlannerError::Serialization
+        SbroadError::FailedTo(
+            Action::Serialize,
+            Some(Entity::Table),
+            "Message(\"invalid type: unit value, expected a sequence\", Some(Pos { marker: Marker { index: 9, line: 1, col: 9 }, path: \"columns\" }))".into()
+        )
     );
 }
 
diff --git a/sbroad-core/src/ir/tests.rs b/sbroad-core/src/ir/tests.rs
index d88ab46284..e7d8f69e27 100644
--- a/sbroad-core/src/ir/tests.rs
+++ b/sbroad-core/src/ir/tests.rs
@@ -13,7 +13,7 @@ fn plan_no_top() {
         .join("plan_no_top.yaml");
     let s = fs::read_to_string(path).unwrap();
     assert_eq!(
-        QueryPlannerError::InvalidPlan,
+        SbroadError::Invalid(Entity::Plan, Some("plan tree top is None".into())),
         Plan::from_yaml(&s).unwrap_err()
     );
 }
@@ -27,7 +27,7 @@ fn plan_oor_top() {
         .join("plan_oor_top.yaml");
     let s = fs::read_to_string(path).unwrap();
     assert_eq!(
-        QueryPlannerError::ValueOutOfRange,
+        SbroadError::NotFound(Entity::Node, "from arena with index 42".into()),
         Plan::from_yaml(&s).unwrap_err()
     );
 }
@@ -59,7 +59,7 @@ fn get_node() {
 fn get_node_oor() {
     let plan = Plan::default();
     assert_eq!(
-        QueryPlannerError::ValueOutOfRange,
+        SbroadError::NotFound(Entity::Node, "from arena with index 42".into()),
         plan.get_node(42).unwrap_err()
     );
 }
diff --git a/sbroad-core/src/ir/transformation.rs b/sbroad-core/src/ir/transformation.rs
index 28cb3401a3..654333e688 100644
--- a/sbroad-core/src/ir/transformation.rs
+++ b/sbroad-core/src/ir/transformation.rs
@@ -9,7 +9,7 @@ pub mod merge_tuples;
 pub mod redistribution;
 pub mod split_columns;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::operator::{Bool, Relational};
 use crate::ir::Plan;
@@ -25,18 +25,24 @@ impl Plan {
         &mut self,
         left_expr_id: usize,
         right_expr_id: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         if !self.is_trivalent(left_expr_id)? {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Left expression is not a boolean expression or NULL: {:?}",
-                self.get_expression_node(left_expr_id)?
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some(format!(
+                    "Left expression is not a boolean expression or NULL: {:?}",
+                    self.get_expression_node(left_expr_id)?
+                )),
+            ));
         }
         if !self.is_trivalent(right_expr_id)? {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Right expression is not a boolean expression or NULL: {:?}",
-                self.get_expression_node(right_expr_id)?
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some(format!(
+                    "Right expression is not a boolean expression or NULL: {:?}",
+                    self.get_expression_node(right_expr_id)?
+                )),
+            ));
         }
         self.add_cond(left_expr_id, Bool::And, right_expr_id)
     }
@@ -49,18 +55,24 @@ impl Plan {
         &mut self,
         left_expr_id: usize,
         right_expr_id: usize,
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         if !self.is_trivalent(left_expr_id)? {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Left expression is not a boolean expression or NULL: {:?}",
-                self.get_expression_node(left_expr_id)?
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some(format!(
+                    "left expression is not a boolean expression or NULL: {:?}",
+                    self.get_expression_node(left_expr_id)?
+                )),
+            ));
         }
         if !self.is_trivalent(right_expr_id)? {
-            return Err(QueryPlannerError::CustomError(format!(
-                "Right expression is not a boolean expression or NULL: {:?}",
-                self.get_expression_node(right_expr_id)?
-            )));
+            return Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some(format!(
+                    "right expression is not a boolean expression or NULL: {:?}",
+                    self.get_expression_node(right_expr_id)?
+                )),
+            ));
         }
         self.add_cond(left_expr_id, Bool::Or, right_expr_id)
     }
@@ -75,8 +87,8 @@ impl Plan {
     /// - If failed to transform the expression subtree.
     pub fn transform_expr_trees(
         &mut self,
-        f: &dyn Fn(&mut Plan, usize) -> Result<usize, QueryPlannerError>,
-    ) -> Result<(), QueryPlannerError> {
+        f: &dyn Fn(&mut Plan, usize) -> Result<usize, SbroadError>,
+    ) -> Result<(), SbroadError> {
         let top_id = self.get_top()?;
         let ir_tree = DftPost::new(&top_id, |node| self.nodes.rel_iter(node));
         let nodes: Vec<usize> = ir_tree.map(|(_, id)| *id).collect();
@@ -124,9 +136,9 @@ impl Plan {
     pub fn expr_tree_replace_bool(
         &mut self,
         top_id: usize,
-        f: &dyn Fn(&mut Plan, usize) -> Result<usize, QueryPlannerError>,
+        f: &dyn Fn(&mut Plan, usize) -> Result<usize, SbroadError>,
         ops: &[Bool],
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let mut map: HashMap<usize, usize> = HashMap::new();
         let subtree = DftPost::new(&top_id, |node| self.nodes.expr_iter(node, false));
         let nodes: Vec<usize> = subtree.map(|(_, id)| *id).collect();
diff --git a/sbroad-core/src/ir/transformation/bool_in.rs b/sbroad-core/src/ir/transformation/bool_in.rs
index 4ca5fc8ad0..5142e1ab49 100644
--- a/sbroad-core/src/ir/transformation/bool_in.rs
+++ b/sbroad-core/src/ir/transformation/bool_in.rs
@@ -10,7 +10,7 @@
 //! SELECT * FROM t WHERE (a = 1) or (a = 2) or (a = 3)
 //! ```
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::operator::Bool;
 use crate::ir::Plan;
@@ -18,17 +18,17 @@ use crate::otm::child_span;
 use sbroad_proc::otm_child_span;
 
 /// Replace IN operator with the chain of the OR-ed equalities in the expression tree.
-fn call_expr_tree_replace_in(plan: &mut Plan, top_id: usize) -> Result<usize, QueryPlannerError> {
+fn call_expr_tree_replace_in(plan: &mut Plan, top_id: usize) -> Result<usize, SbroadError> {
     plan.expr_tree_replace_bool(top_id, &call_from_in, &[Bool::In])
 }
 
-fn call_from_in(plan: &mut Plan, top_id: usize) -> Result<usize, QueryPlannerError> {
+fn call_from_in(plan: &mut Plan, top_id: usize) -> Result<usize, SbroadError> {
     plan.in_to_or(top_id)
 }
 
 impl Plan {
     /// Convert the IN operator to the chain of the OR-ed equalities.
-    fn in_to_or(&mut self, expr_id: usize) -> Result<usize, QueryPlannerError> {
+    fn in_to_or(&mut self, expr_id: usize) -> Result<usize, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         let (left_id, right_id) = match expr {
             Expression::Bool {
@@ -38,10 +38,10 @@ impl Plan {
                 ..
             } => (*left, *right),
             _ => {
-                return Err(QueryPlannerError::CustomError(format!(
-                    "Node is not a boolean IN expression: {:?}",
-                    expr
-                )));
+                return Err(SbroadError::Invalid(
+                    Entity::Expression,
+                    Some(format!("Node is not a boolean IN expression: {expr:?}")),
+                ));
             }
         };
 
@@ -85,7 +85,7 @@ impl Plan {
     /// # Errors
     /// - If the plan tree is invalid (doesn't contain correct nodes where we expect it to).
     #[otm_child_span("plan.transformation.replace_in_operator")]
-    pub fn replace_in_operator(&mut self) -> Result<(), QueryPlannerError> {
+    pub fn replace_in_operator(&mut self) -> Result<(), SbroadError> {
         self.transform_expr_trees(&call_expr_tree_replace_in)
     }
 }
diff --git a/sbroad-core/src/ir/transformation/dnf.rs b/sbroad-core/src/ir/transformation/dnf.rs
index e7ed0ba594..ca485b6de9 100644
--- a/sbroad-core/src/ir/transformation/dnf.rs
+++ b/sbroad-core/src/ir/transformation/dnf.rs
@@ -70,7 +70,7 @@
 //! └─────┘          └─────┘
 //! ```
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::operator::Bool;
 use crate::ir::{Node, Plan};
@@ -101,7 +101,7 @@ impl Chain {
 
     /// Append a new node to the chain. Keep AND and OR nodes in the back,
     /// while other nodes in the front of the chain double-ended queue.
-    fn push(&mut self, expr_id: usize, plan: &Plan) -> Result<(), QueryPlannerError> {
+    fn push(&mut self, expr_id: usize, plan: &Plan) -> Result<(), SbroadError> {
         let expr = plan.get_expression_node(expr_id)?;
         if let Expression::Bool {
             op: Bool::And | Bool::Or,
@@ -116,7 +116,7 @@ impl Chain {
     }
 
     /// Pop AND and OR nodes (we append them to the back).
-    fn pop_back(&mut self, plan: &Plan) -> Result<Option<usize>, QueryPlannerError> {
+    fn pop_back(&mut self, plan: &Plan) -> Result<Option<usize>, SbroadError> {
         if let Some(expr_id) = self.nodes.back() {
             let expr = plan.get_expression_node(*expr_id)?;
             if let Expression::Bool {
@@ -136,7 +136,7 @@ impl Chain {
     }
 
     /// Convert a chain to a new expression tree (reuse trivalent expressions).
-    fn as_plan(&mut self, plan: &mut Plan) -> Result<usize, QueryPlannerError> {
+    fn as_plan(&mut self, plan: &mut Plan) -> Result<usize, SbroadError> {
         let mut top_id: Option<usize> = None;
         while let Some(expr_id) = self.pop_front() {
             match top_id {
@@ -149,8 +149,8 @@ impl Chain {
                 }
             }
         }
-        let new_top_id =
-            top_id.ok_or_else(|| QueryPlannerError::CustomError("Empty chain".into()))?;
+        let new_top_id = top_id
+            .ok_or_else(|| SbroadError::Invalid(Entity::Chain, Some("Empty chain".into())))?;
         Ok(new_top_id)
     }
 
@@ -160,7 +160,7 @@ impl Chain {
     }
 }
 
-fn call_expr_tree_to_dnf(plan: &mut Plan, top_id: usize) -> Result<usize, QueryPlannerError> {
+fn call_expr_tree_to_dnf(plan: &mut Plan, top_id: usize) -> Result<usize, SbroadError> {
     plan.expr_tree_to_dnf(top_id)
 }
 
@@ -170,7 +170,7 @@ impl Plan {
     /// # Errors
     /// - If the expression tree is not a trivalent expression.
     /// - Failed to append node to the AND chain.
-    pub fn get_dnf_chains(&self, top_id: usize) -> Result<VecDeque<Chain>, QueryPlannerError> {
+    pub fn get_dnf_chains(&self, top_id: usize) -> Result<VecDeque<Chain>, SbroadError> {
         let capacity: usize = self.nodes.arena.iter().fold(0_usize, |acc, node| {
             acc + match node {
                 Node::Expression(Expression::Bool {
@@ -212,8 +212,9 @@ impl Plan {
                         chain.push(*left, self)?;
                     }
                     _ => {
-                        return Err(QueryPlannerError::CustomError(
-                            "Chain returned unexpected boolean operator".into(),
+                        return Err(SbroadError::Invalid(
+                            Entity::Chain,
+                            Some("Chain returned unexpected boolean operator".into()),
                         ))
                     }
                 }
@@ -230,7 +231,7 @@ impl Plan {
     /// - Failed to retrieve DNF chains.
     /// - Failed to convert the AND chain to a new expression tree.
     /// - Failed to concatenate the AND expression trees to the OR tree.
-    pub fn expr_tree_to_dnf(&mut self, top_id: usize) -> Result<usize, QueryPlannerError> {
+    pub fn expr_tree_to_dnf(&mut self, top_id: usize) -> Result<usize, SbroadError> {
         let mut result = self.get_dnf_chains(top_id)?;
 
         let mut new_top_id: Option<usize> = None;
@@ -242,8 +243,9 @@ impl Plan {
             }
         }
 
-        new_top_id
-            .ok_or_else(|| QueryPlannerError::CustomError("Chain returned no expressions".into()))
+        new_top_id.ok_or_else(|| {
+            SbroadError::Invalid(Entity::Chain, Some("Chain returned no expressions".into()))
+        })
     }
 
     /// Convert an expression tree of trivalent nodes to a conjunctive
@@ -254,7 +256,7 @@ impl Plan {
     /// - If the plan doesn't contain relational operators where expected.
     /// - If failed to convert an expression tree to a CNF.
     #[otm_child_span("plan.transformation.set_dnf")]
-    pub fn set_dnf(&mut self) -> Result<(), QueryPlannerError> {
+    pub fn set_dnf(&mut self) -> Result<(), SbroadError> {
         self.transform_expr_trees(&call_expr_tree_to_dnf)
     }
 }
diff --git a/sbroad-core/src/ir/transformation/equality_propagation.rs b/sbroad-core/src/ir/transformation/equality_propagation.rs
index aec7b8d1f1..48c4d3d046 100644
--- a/sbroad-core/src/ir/transformation/equality_propagation.rs
+++ b/sbroad-core/src/ir/transformation/equality_propagation.rs
@@ -89,7 +89,7 @@
 //! 6. Finally, we transform the "AND"-ed chains into a plan subtree and attach them back
 //!    to the plan tree.
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::helpers::RepeatableState;
 use crate::ir::operator::Bool;
@@ -110,7 +110,7 @@ struct EqClassRef {
 }
 
 impl EqClassRef {
-    fn from_ref(expr: &Expression) -> Result<Self, QueryPlannerError> {
+    fn from_ref(expr: &Expression) -> Result<Self, SbroadError> {
         if let Expression::Reference {
             targets: expr_tgt,
             position: expr_pos,
@@ -123,7 +123,7 @@ impl EqClassRef {
                 parent: *expr_prt,
             });
         }
-        Err(QueryPlannerError::InvalidReference)
+        Err(SbroadError::Invalid(Entity::Expression, None))
     }
 
     fn to_single_col_row(&self, plan: &mut Plan) -> usize {
@@ -147,13 +147,16 @@ struct EqClassConst {
 impl Eq for EqClassConst {}
 
 impl EqClassConst {
-    fn from_const(expr: &Expression) -> Result<Self, QueryPlannerError> {
+    fn from_const(expr: &Expression) -> Result<Self, SbroadError> {
         if let Expression::Constant { value: expr_value } = expr {
             return Ok(EqClassConst {
                 value: expr_value.clone(),
             });
         }
-        Err(QueryPlannerError::InvalidConstant)
+        Err(SbroadError::Invalid(
+            Entity::Expression,
+            Some("invaid Constant".into()),
+        ))
     }
 
     fn to_const(&self, plan: &mut Plan) -> usize {
@@ -339,17 +342,14 @@ impl EqClassChain {
 }
 
 /// Replace IN operator with the chain of the OR-ed equalities in the expression tree.
-fn call_expr_tree_derive_equalities(
-    plan: &mut Plan,
-    top_id: usize,
-) -> Result<usize, QueryPlannerError> {
+fn call_expr_tree_derive_equalities(plan: &mut Plan, top_id: usize) -> Result<usize, SbroadError> {
     plan.expr_tree_modify_and_chains(top_id, &call_build_and_chains, &call_as_plan)
 }
 
 fn call_build_and_chains(
     plan: &mut Plan,
     nodes: &[usize],
-) -> Result<HashMap<usize, Chain, RepeatableState>, QueryPlannerError> {
+) -> Result<HashMap<usize, Chain, RepeatableState>, SbroadError> {
     let mut chains = plan.populate_and_chains(nodes)?;
     for chain in chains.values_mut() {
         chain.extend_equality_operator(plan)?;
@@ -357,19 +357,19 @@ fn call_build_and_chains(
     Ok(chains)
 }
 
-fn call_as_plan(chain: &Chain, plan: &mut Plan) -> Result<usize, QueryPlannerError> {
+fn call_as_plan(chain: &Chain, plan: &mut Plan) -> Result<usize, SbroadError> {
     chain.as_plan_ecs(plan)
 }
 
 impl Chain {
-    fn extend_equality_operator(&mut self, plan: &mut Plan) -> Result<(), QueryPlannerError> {
+    fn extend_equality_operator(&mut self, plan: &mut Plan) -> Result<(), SbroadError> {
         if let Some((left_vec, right_vec)) = self.get_grouped().get(&Bool::Eq) {
             let mut eq_classes = EqClassChain::new();
 
             for (left_id, right_id) in left_vec.iter().zip(right_vec.iter()) {
                 let left_eqe = plan.try_to_eq_class_expr(*left_id);
                 let right_eqe = plan.try_to_eq_class_expr(*right_id);
-                if let (Err(QueryPlannerError::DoSkip), _) | (_, Err(QueryPlannerError::DoSkip)) =
+                if let (Err(SbroadError::DoSkip), _) | (_, Err(SbroadError::DoSkip)) =
                     (&left_eqe, &right_eqe)
                 {
                     continue;
@@ -397,7 +397,7 @@ impl Chain {
         Ok(())
     }
 
-    fn as_plan_ecs(&self, plan: &mut Plan) -> Result<usize, QueryPlannerError> {
+    fn as_plan_ecs(&self, plan: &mut Plan) -> Result<usize, SbroadError> {
         let other_top_id = match self.get_other().split_first() {
             Some((first, other)) => {
                 let mut top_id = *first;
@@ -459,8 +459,8 @@ impl Chain {
             }
             (Some(grouped_top_id), None) => Ok(grouped_top_id),
             (None, Some(other_top_id)) => Ok(other_top_id),
-            (None, None) => Err(QueryPlannerError::CustomError(
-                "No expressions to merge".to_string(),
+            (None, None) => Err(SbroadError::UnexpectedNumberOfValues(
+                "no expressions to merge, expected one or two".to_string(),
             )),
         }
     }
@@ -469,7 +469,7 @@ impl Chain {
 impl Plan {
     // DoSkip is a special case of an error - nothing bad had happened, the target node doesn't contain
     // anything interesting for us, skip it without any serious error.
-    fn try_to_eq_class_expr(&self, expr_id: usize) -> Result<EqClassExpr, QueryPlannerError> {
+    fn try_to_eq_class_expr(&self, expr_id: usize) -> Result<EqClassExpr, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         match expr {
             Expression::Constant { .. } => {
@@ -483,10 +483,10 @@ impl Plan {
                     self.try_to_eq_class_expr(*col_id)
                 } else {
                     // We don't support more than a single column in a row.
-                    Err(QueryPlannerError::DoSkip)
+                    Err(SbroadError::DoSkip)
                 }
             }
-            _ => Err(QueryPlannerError::DoSkip),
+            _ => Err(SbroadError::DoSkip),
         }
     }
 
@@ -495,7 +495,7 @@ impl Plan {
     /// # Errors
     /// - If the plan tree is invalid (doesn't contain correct nodes where we expect it to).
     #[otm_child_span("plan.transformation.derive_equalities")]
-    pub fn derive_equalities(&mut self) -> Result<(), QueryPlannerError> {
+    pub fn derive_equalities(&mut self) -> Result<(), SbroadError> {
         self.transform_expr_trees(&call_expr_tree_derive_equalities)
     }
 }
diff --git a/sbroad-core/src/ir/transformation/merge_tuples.rs b/sbroad-core/src/ir/transformation/merge_tuples.rs
index 2c0e8242eb..9bbd1b1e4e 100644
--- a/sbroad-core/src/ir/transformation/merge_tuples.rs
+++ b/sbroad-core/src/ir/transformation/merge_tuples.rs
@@ -10,7 +10,7 @@
 //! select * from t where (a, b, c) = (1, 2, 3)
 //! ```
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::helpers::RepeatableState;
 use crate::ir::operator::Bool;
@@ -20,18 +20,18 @@ use sbroad_proc::otm_child_span;
 use std::collections::{hash_map::Entry, HashMap, HashSet};
 use traversal::Bft;
 
-fn call_expr_tree_merge_tuples(plan: &mut Plan, top_id: usize) -> Result<usize, QueryPlannerError> {
+fn call_expr_tree_merge_tuples(plan: &mut Plan, top_id: usize) -> Result<usize, SbroadError> {
     plan.expr_tree_modify_and_chains(top_id, &call_build_and_chains, &call_as_plan)
 }
 
 fn call_build_and_chains(
     plan: &mut Plan,
     nodes: &[usize],
-) -> Result<HashMap<usize, Chain, RepeatableState>, QueryPlannerError> {
+) -> Result<HashMap<usize, Chain, RepeatableState>, SbroadError> {
     plan.populate_and_chains(nodes)
 }
 
-fn call_as_plan(chain: &Chain, plan: &mut Plan) -> Result<usize, QueryPlannerError> {
+fn call_as_plan(chain: &Chain, plan: &mut Plan) -> Result<usize, SbroadError> {
     chain.as_plan(plan)
 }
 
@@ -61,15 +61,17 @@ impl Chain {
     /// - Failed if the node is not an expression.
     /// - Failed if expression is not an "AND" or "OR".
     /// - There is something wrong with our sub-queries.
-    pub fn insert(&mut self, plan: &mut Plan, expr_id: usize) -> Result<(), QueryPlannerError> {
+    pub fn insert(&mut self, plan: &mut Plan, expr_id: usize) -> Result<(), SbroadError> {
         let bool_expr = plan.get_expression_node(expr_id)?;
         if let Expression::Bool { left, op, right } = bool_expr {
             if let Bool::And | Bool::Or = op {
                 // We don't expect nested AND/OR expressions in DNF.
-                return Err(QueryPlannerError::CustomError(format!(
-                    "AND/OR expressions are not supported: {:?}",
-                    bool_expr
-                )));
+                return Err(SbroadError::Unsupported(
+                    Entity::Operator,
+                    Some(format!(
+                        "AND/OR expressions are not supported: {bool_expr:?}"
+                    )),
+                ));
             }
 
             // Merge expression into tuples only for equality operators.
@@ -125,7 +127,7 @@ impl Chain {
         Ok(())
     }
 
-    fn as_plan(&self, plan: &mut Plan) -> Result<usize, QueryPlannerError> {
+    fn as_plan(&self, plan: &mut Plan) -> Result<usize, SbroadError> {
         let other_top_id = match self.other.split_first() {
             Some((first, other)) => {
                 let mut top_id = *first;
@@ -163,8 +165,8 @@ impl Chain {
             }
             (Some(grouped_top_id), None) => Ok(grouped_top_id),
             (None, Some(other_top_id)) => Ok(other_top_id),
-            (None, None) => Err(QueryPlannerError::CustomError(
-                "No expressions to merge".to_string(),
+            (None, None) => Err(SbroadError::UnexpectedNumberOfValues(
+                "no expressions to merge, expected one or twoe".to_string(),
             )),
         }
     }
@@ -187,7 +189,7 @@ impl Chain {
 }
 
 impl Plan {
-    fn get_columns_or_self(&self, expr_id: usize) -> Result<Vec<usize>, QueryPlannerError> {
+    fn get_columns_or_self(&self, expr_id: usize) -> Result<Vec<usize>, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         match expr {
             Expression::Row { list, .. } => Ok(list.clone()),
@@ -203,7 +205,7 @@ impl Plan {
     pub fn populate_and_chains(
         &mut self,
         nodes: &[usize],
-    ) -> Result<HashMap<usize, Chain, RepeatableState>, QueryPlannerError> {
+    ) -> Result<HashMap<usize, Chain, RepeatableState>, SbroadError> {
         let mut visited: HashSet<usize> = HashSet::with_capacity(self.nodes.next_id());
         let mut chains: HashMap<usize, Chain, RepeatableState> =
             HashMap::with_capacity_and_hasher(nodes.len(), RepeatableState);
@@ -267,12 +269,10 @@ impl Plan {
         f_build_chains: &dyn Fn(
             &mut Plan,
             &[usize],
-        ) -> Result<
-            HashMap<usize, Chain, RepeatableState>,
-            QueryPlannerError,
-        >,
-        f_to_plan: &dyn Fn(&Chain, &mut Plan) -> Result<usize, QueryPlannerError>,
-    ) -> Result<usize, QueryPlannerError> {
+        )
+            -> Result<HashMap<usize, Chain, RepeatableState>, SbroadError>,
+        f_to_plan: &dyn Fn(&Chain, &mut Plan) -> Result<usize, SbroadError>,
+    ) -> Result<usize, SbroadError> {
         let tree = Bft::new(&expr_id, |node| self.nodes.expr_iter(node, false));
         let nodes: Vec<usize> = tree.map(|(_, id)| *id).collect();
         let chains = f_build_chains(self, &nodes)?;
@@ -293,10 +293,10 @@ impl Plan {
                         {
                             *child_id = new_child_id;
                         } else {
-                            return Err(QueryPlannerError::CustomError(format!(
-                                "Expected alias expression: {:?}",
-                                expr_mut
-                            )));
+                            return Err(SbroadError::Invalid(
+                                Entity::Expression,
+                                Some(format!("expected alias expression: {expr_mut:?}")),
+                            ));
                         }
                     }
                 }
@@ -319,10 +319,10 @@ impl Plan {
                                     *right_id = new_child_id;
                                 }
                             } else {
-                                return Err(QueryPlannerError::CustomError(format!(
-                                    "Expected boolean expression: {:?}",
-                                    expr_mut
-                                )));
+                                return Err(SbroadError::Invalid(
+                                    Entity::Expression,
+                                    Some(format!("expected boolean expression: {expr_mut:?}")),
+                                ));
                             }
                         }
                     }
@@ -338,16 +338,16 @@ impl Plan {
                                 if let Some(child_id) = list.get_mut(pos) {
                                     *child_id = new_child_id;
                                 } else {
-                                    return Err(QueryPlannerError::CustomError(format!(
-                                        "Expected a column at position {} in the row {:?}",
+                                    return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                                        "expected a column at position {} in the row {:?}",
                                         pos, expr_mut
                                     )));
                                 }
                             } else {
-                                return Err(QueryPlannerError::CustomError(format!(
-                                    "Expected row expression: {:?}",
-                                    expr_mut
-                                )));
+                                return Err(SbroadError::Invalid(
+                                    Entity::Expression,
+                                    Some(format!("expected row expression: {expr_mut:?}")),
+                                ));
                             }
                         }
                     }
@@ -371,7 +371,7 @@ impl Plan {
     /// # Errors
     /// - If the plan tree is invalid (doesn't contain correct nodes where we expect it to).
     #[otm_child_span("plan.transformation.merge_tuples")]
-    pub fn merge_tuples(&mut self) -> Result<(), QueryPlannerError> {
+    pub fn merge_tuples(&mut self) -> Result<(), SbroadError> {
         self.transform_expr_trees(&call_expr_tree_merge_tuples)
     }
 }
diff --git a/sbroad-core/src/ir/transformation/redistribution.rs b/sbroad-core/src/ir/transformation/redistribution.rs
index 4ae9e1758a..537de38813 100644
--- a/sbroad-core/src/ir/transformation/redistribution.rs
+++ b/sbroad-core/src/ir/transformation/redistribution.rs
@@ -7,7 +7,7 @@ use std::collections::{hash_map::Entry, HashMap, HashSet};
 use std::fmt::{Display, Formatter};
 use traversal::{Bft, DftPost};
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Action, Entity, SbroadError};
 use crate::ir::distribution::{Distribution, Key, KeySet};
 use crate::ir::expression::Expression;
 use crate::ir::operator::{Bool, Relational};
@@ -100,7 +100,7 @@ struct BoolOp {
 }
 
 impl BoolOp {
-    fn from_expr(plan: &Plan, expr_id: usize) -> Result<Self, QueryPlannerError> {
+    fn from_expr(plan: &Plan, expr_id: usize) -> Result<Self, SbroadError> {
         if let Expression::Bool {
             left, op, right, ..
         } = plan.get_expression_node(expr_id)?
@@ -111,7 +111,7 @@ impl BoolOp {
                 right: *right,
             })
         } else {
-            Err(QueryPlannerError::InvalidBool)
+            Err(SbroadError::Invalid(Entity::Operator, None))
         }
     }
 }
@@ -155,7 +155,7 @@ impl Plan {
     ///
     /// # Errors
     /// - plan doesn't contain the top node
-    fn get_relational_nodes_dfs_post(&self) -> Result<Vec<usize>, QueryPlannerError> {
+    fn get_relational_nodes_dfs_post(&self) -> Result<Vec<usize>, SbroadError> {
         let top = self.get_top()?;
         let post_tree = DftPost::new(&top, |node| self.nodes.rel_iter(node));
         let nodes: Vec<usize> = post_tree.map(|(_, id)| *id).collect();
@@ -169,7 +169,7 @@ impl Plan {
     pub(crate) fn get_bool_nodes_with_row_children(
         &self,
         top: usize,
-    ) -> Result<Vec<usize>, QueryPlannerError> {
+    ) -> Result<Vec<usize>, SbroadError> {
         let mut nodes: Vec<usize> = Vec::new();
 
         let post_tree = DftPost::new(&top, |node| self.nodes.expr_iter(node, false));
@@ -197,10 +197,7 @@ impl Plan {
     /// # Errors
     /// - Row node is not of a row type
     /// There are more than one sub-queries in the row node.
-    pub fn get_sub_query_from_row_node(
-        &self,
-        row_id: usize,
-    ) -> Result<Option<usize>, QueryPlannerError> {
+    pub fn get_sub_query_from_row_node(&self, row_id: usize) -> Result<Option<usize>, SbroadError> {
         let rel_ids = self.get_relational_from_row_nodes(row_id)?;
         self.get_sub_query_among_rel_nodes(&rel_ids)
     }
@@ -213,7 +210,7 @@ impl Plan {
     pub fn get_sub_query_among_rel_nodes(
         &self,
         rel_nodes: &HashSet<usize, RandomState>,
-    ) -> Result<Option<usize>, QueryPlannerError> {
+    ) -> Result<Option<usize>, SbroadError> {
         let mut sq_set: HashSet<usize, RandomState> = HashSet::with_hasher(RandomState::new());
         for rel_id in rel_nodes {
             if let Node::Relational(Relational::ScanSubQuery { .. }) = self.get_node(*rel_id)? {
@@ -223,7 +220,7 @@ impl Plan {
         match sq_set.len().cmp(&1) {
             Ordering::Equal => sq_set.iter().next().map_or_else(
                 || {
-                    Err(QueryPlannerError::CustomError(format!(
+                    Err(SbroadError::UnexpectedNumberOfValues(format!(
                         "Failed to get the first sub-query node from the list of relational nodes: {:?}.",
                         rel_nodes
                     )))
@@ -231,7 +228,7 @@ impl Plan {
                 |sq_id| Ok(Some(*sq_id)),
             ),
             Ordering::Less => Ok(None),
-            Ordering::Greater => Err(QueryPlannerError::CustomError(format!(
+            Ordering::Greater => Err(SbroadError::UnexpectedNumberOfValues(format!(
                 "Found multiple sub-queries in a list of relational nodes: {:?}.",
                 rel_nodes
             ))),
@@ -243,7 +240,7 @@ impl Plan {
     /// # Errors
     /// - Row node is not of a row type
     /// - There are more than one motion nodes in the row node
-    pub fn get_motion_from_row(&self, node_id: usize) -> Result<Option<usize>, QueryPlannerError> {
+    pub fn get_motion_from_row(&self, node_id: usize) -> Result<Option<usize>, SbroadError> {
         let rel_nodes = self.get_relational_from_row_nodes(node_id)?;
         self.get_motion_among_rel_nodes(&rel_nodes)
     }
@@ -256,7 +253,7 @@ impl Plan {
     pub fn get_motion_among_rel_nodes(
         &self,
         rel_nodes: &HashSet<usize, RandomState>,
-    ) -> Result<Option<usize>, QueryPlannerError> {
+    ) -> Result<Option<usize>, SbroadError> {
         let mut motion_set: HashSet<usize> = HashSet::new();
 
         for child in rel_nodes {
@@ -268,14 +265,14 @@ impl Plan {
         match motion_set.len().cmp(&1) {
             Ordering::Equal => {
                 let motion_id = motion_set.iter().next().ok_or_else(|| {
-                    QueryPlannerError::CustomError(
-                        "Failed to get the first motion node from the set.".into(),
+                    SbroadError::UnexpectedNumberOfValues(
+                        "failed to get the first Motion node from the set.".into(),
                     )
                 })?;
                 Ok(Some(*motion_id))
             }
             Ordering::Less => Ok(None),
-            Ordering::Greater => Err(QueryPlannerError::CustomError(
+            Ordering::Greater => Err(SbroadError::UnexpectedNumberOfValues(
                 "Node must contain only a single motion".into(),
             )),
         }
@@ -291,7 +288,7 @@ impl Plan {
         outer_id: usize,
         inner_id: usize,
         op: &Bool,
-    ) -> Result<MotionPolicy, QueryPlannerError> {
+    ) -> Result<MotionPolicy, SbroadError> {
         let outer_dist = self.get_distribution(outer_id)?;
         let inner_dist = self.get_distribution(inner_id)?;
         if Bool::Eq == *op || Bool::In == *op || Bool::NotEq == *op || Bool::NotIn == *op {
@@ -311,7 +308,7 @@ impl Plan {
                 // Redistribute the inner tuples using the first key from the outer tuple.
                 return keys_outer.iter().next().map_or_else(
                     || {
-                        Err(QueryPlannerError::CustomError(String::from(
+                        Err(SbroadError::UnexpectedNumberOfValues(String::from(
                             "Failed to get the first distribution key from the outer row.",
                         )))
                     },
@@ -327,13 +324,15 @@ impl Plan {
         &mut self,
         rel_id: usize,
         strategy: &Strategy,
-    ) -> Result<(), QueryPlannerError> {
+    ) -> Result<(), SbroadError> {
         let children: Vec<usize> = if let Some(children) = self.get_relational_children(rel_id)? {
             children.to_vec()
         } else {
-            return Err(QueryPlannerError::CustomError(String::from(
-                "Trying to add motions under the leaf relational node.",
-            )));
+            return Err(SbroadError::FailedTo(
+                Action::Add,
+                Some(Entity::Motion),
+                "trying to add motions under the leaf relational node".into(),
+            ));
         };
 
         // Check that all children we need to add motions exist in the current relational node.
@@ -342,9 +341,11 @@ impl Plan {
             .iter()
             .all(|(node, _)| children_set.get(node).is_some())
         {
-            return Err(QueryPlannerError::CustomError(String::from(
-                "Trying to add motions for non-existing children in relational node.",
-            )));
+            return Err(SbroadError::FailedTo(
+                Action::Add,
+                Some(Entity::Motion),
+                "trying to add motions for non-existing children in relational node".into(),
+            ));
         }
 
         // Add motions.
@@ -368,7 +369,7 @@ impl Plan {
         &self,
         rel_id: usize,
         row_id: usize,
-    ) -> Result<Option<usize>, QueryPlannerError> {
+    ) -> Result<Option<usize>, SbroadError> {
         if self.get_expression_node(row_id)?.is_row() {
             if let Some(sq_id) = self.get_sub_query_from_row_node(row_id)? {
                 if self.is_additional_child_of_rel(rel_id, sq_id)? {
@@ -383,7 +384,7 @@ impl Plan {
         &self,
         rel_id: usize,
         node_id: usize,
-    ) -> Result<Vec<(usize, MotionPolicy)>, QueryPlannerError> {
+    ) -> Result<Vec<(usize, MotionPolicy)>, SbroadError> {
         let mut strategies: Vec<(usize, MotionPolicy)> = Vec::new();
         let bool_op = BoolOp::from_expr(self, node_id)?;
         let left = self.get_additional_sq(rel_id, bool_op.left)?;
@@ -431,7 +432,7 @@ impl Plan {
         &mut self,
         rel_id: usize,
         expr_id: usize,
-    ) -> Result<Strategy, QueryPlannerError> {
+    ) -> Result<Strategy, SbroadError> {
         let nodes = self.get_bool_nodes_with_row_children(expr_id)?;
         for node in &nodes {
             let bool_op = BoolOp::from_expr(self, *node)?;
@@ -454,16 +455,17 @@ impl Plan {
     /// # Errors
     /// - If the node is not a join node.
     /// - Join node has no children.
-    fn get_join_children(&self, join_id: usize) -> Result<&[usize], QueryPlannerError> {
+    fn get_join_children(&self, join_id: usize) -> Result<&[usize], SbroadError> {
         let join = self.get_relation_node(join_id)?;
         if let Relational::InnerJoin { .. } = join {
         } else {
-            return Err(QueryPlannerError::CustomError(
-                "Join node is not an inner join.".into(),
+            return Err(SbroadError::Invalid(
+                Entity::Relational,
+                Some("Join node is not an inner join.".into()),
             ));
         }
         let children = join.children().ok_or_else(|| {
-            QueryPlannerError::CustomError("Join node doesn't have any children.".into())
+            SbroadError::UnexpectedNumberOfValues("Join node has no children.".into())
         })?;
         Ok(children)
     }
@@ -474,40 +476,38 @@ impl Plan {
         key: &Key,
         row_map: &HashMap<usize, usize>,
         join_children: &[usize],
-    ) -> Result<usize, QueryPlannerError> {
+    ) -> Result<usize, SbroadError> {
         let mut children_set: HashSet<usize> = HashSet::new();
         for pos in &key.positions {
             let column_id = *row_map.get(pos).ok_or_else(|| {
-                QueryPlannerError::CustomError(format!(
-                    "Column {} not found in row map {:?}.",
-                    pos, row_map
-                ))
+                SbroadError::NotFound(Entity::Column, format!("{} in row map {:?}", pos, row_map))
             })?;
             if let Expression::Reference { targets, .. } = self.get_expression_node(column_id)? {
                 if let Some(targets) = targets {
                     for target in targets {
                         let child_id = *join_children.get(*target).ok_or_else(|| {
-                            QueryPlannerError::CustomError(format!(
-                                "Target {} not found in join children {:?}.",
-                                target, join_children
-                            ))
+                            SbroadError::NotFound(
+                                Entity::Target,
+                                format!("{} in join children {:?}", target, join_children),
+                            )
                         })?;
                         children_set.insert(child_id);
                     }
                 }
             } else {
-                return Err(QueryPlannerError::CustomError(
-                    "Row column is not a reference.".into(),
+                return Err(SbroadError::Invalid(
+                    Entity::Expression,
+                    Some("Row column is not a reference.".into()),
                 ));
             }
         }
         if children_set.len() > 1 {
-            return Err(QueryPlannerError::CustomError(
+            return Err(SbroadError::UnexpectedNumberOfValues(
                 "Distribution key in the join condition has more than one child.".into(),
             ));
         }
         children_set.iter().next().copied().ok_or_else(|| {
-            QueryPlannerError::CustomError(
+            SbroadError::UnexpectedNumberOfValues(
                 "Distribution key in the join condition has no children.".into(),
             )
         })
@@ -517,7 +517,7 @@ impl Plan {
     ///
     /// # Errors
     /// - If the node is not a row node.
-    fn build_row_map(&self, row_id: usize) -> Result<HashMap<usize, usize>, QueryPlannerError> {
+    fn build_row_map(&self, row_id: usize) -> Result<HashMap<usize, usize>, SbroadError> {
         let columns = self.get_expression_node(row_id)?.get_row_list()?;
         let mut map: HashMap<usize, usize> = HashMap::new();
         for (pos, col) in columns.iter().enumerate() {
@@ -538,16 +538,16 @@ impl Plan {
         join_id: usize,
         keys: &[Key],
         row_map: &HashMap<usize, usize>,
-    ) -> Result<(Vec<Key>, Vec<Key>), QueryPlannerError> {
+    ) -> Result<(Vec<Key>, Vec<Key>), SbroadError> {
         let mut outer_keys: Vec<Key> = Vec::new();
         let mut inner_keys: Vec<Key> = Vec::new();
 
         let children = self.get_join_children(join_id)?;
         let outer_child = *children.first().ok_or_else(|| {
-            QueryPlannerError::CustomError("Join node doesn't have an outer child.".into())
+            SbroadError::UnexpectedNumberOfValues("Join node has no children.".into())
         })?;
         let inner_child = *children.get(1).ok_or_else(|| {
-            QueryPlannerError::CustomError("Join node doesn't have an inner child.".into())
+            SbroadError::NotFound(Entity::Node, "that is Join node inner child".into())
         })?;
 
         for key in keys {
@@ -558,8 +558,12 @@ impl Plan {
                 inner_keys.push(key.clone());
             } else {
                 // It can be only a sub-query, but we have already processed it.
-                return Err(QueryPlannerError::CustomError(
-                    "Distribution key doesn't correspond  to inner or outer join children.".into(),
+                return Err(SbroadError::Invalid(
+                    Entity::DistributionKey,
+                    Some(
+                        "distribution key doesn't correspond to inner or outer join children."
+                            .into(),
+                    ),
                 ));
             }
         }
@@ -577,13 +581,13 @@ impl Plan {
         join_id: usize,
         left_row_id: usize,
         right_row_id: usize,
-    ) -> Result<MotionPolicy, QueryPlannerError> {
+    ) -> Result<MotionPolicy, SbroadError> {
         let left_dist = self.get_distribution(left_row_id)?;
         let right_dist = self.get_distribution(right_row_id)?;
 
         let get_policy_for_one_side_segment = |row_map: &HashMap<usize, usize>,
                                                keys_set: &KeySet|
-         -> Result<MotionPolicy, QueryPlannerError> {
+         -> Result<MotionPolicy, SbroadError> {
             let keys = keys_set.iter().map(Clone::clone).collect::<Vec<_>>();
             let (outer_keys, _) =
                 self.split_join_keys_to_inner_and_outer(join_id, &keys, row_map)?;
@@ -655,7 +659,7 @@ impl Plan {
         &mut self,
         rel_id: usize,
         expr_id: usize,
-    ) -> Result<Strategy, QueryPlannerError> {
+    ) -> Result<Strategy, SbroadError> {
         // First, we need to set the motion policy for each boolean expression in the join condition.
         let nodes = self.get_bool_nodes_with_row_children(expr_id)?;
         for node in &nodes {
@@ -672,14 +676,17 @@ impl Plan {
                 strategy.insert(*child_id, (MotionPolicy::Full, DataGeneration::None));
             }
         } else {
-            return Err(QueryPlannerError::CustomError(
+            return Err(SbroadError::UnexpectedNumberOfValues(
                 "Join node doesn't have any children.".into(),
             ));
         }
 
         // Let's improve the full motion policy for the join children (sub-queries and the inner child).
         let inner_child = *join_children.get(1).ok_or_else(|| {
-            QueryPlannerError::CustomError("Join node doesn't have an inner child.".into())
+            SbroadError::NotFound(
+                Entity::Node,
+                "that is Join node inner child with index 1.".into(),
+            )
         })?;
         let mut inner_map: HashMap<usize, MotionPolicy> = HashMap::new();
         let mut new_inner_policy = MotionPolicy::Full;
@@ -722,8 +729,9 @@ impl Plan {
                         Bool::And => join_policy_for_and(&left_policy, &right_policy),
                         Bool::Or => join_policy_for_or(&left_policy, &right_policy),
                         _ => {
-                            return Err(QueryPlannerError::CustomError(
-                                "Unsupported boolean operation".into(),
+                            return Err(SbroadError::Unsupported(
+                                Entity::Operator,
+                                Some("unsupported boolean operation, expected And or Or".into()),
                             ))
                         }
                     }
@@ -736,8 +744,9 @@ impl Plan {
                         Bool::Gt | Bool::GtEq | Bool::Lt | Bool::LtEq => MotionPolicy::Full,
                         Bool::And | Bool::Or => {
                             // "a and 1" or "a or 1" expressions make no sense.
-                            return Err(QueryPlannerError::CustomError(
-                                "Unsupported boolean operation".into(),
+                            return Err(SbroadError::Unsupported(
+                                Entity::Operator,
+                                Some("unsupported boolean operation And or Or".into()),
                             ));
                         }
                     }
@@ -751,8 +760,9 @@ impl Plan {
                     .cloned()
                     .unwrap_or(MotionPolicy::Full),
                 _ => {
-                    return Err(QueryPlannerError::CustomError(
-                        "Unsupported boolean operation".into(),
+                    return Err(SbroadError::Unsupported(
+                        Entity::Operator,
+                        Some("unsupported boolean operation".into()),
                     ))
                 }
             };
@@ -762,7 +772,8 @@ impl Plan {
         Ok(strategy)
     }
 
-    fn resolve_insert_conflicts(&mut self, rel_id: usize) -> Result<Strategy, QueryPlannerError> {
+    #[allow(clippy::too_many_lines)]
+    fn resolve_insert_conflicts(&mut self, rel_id: usize) -> Result<Strategy, SbroadError> {
         let mut map: Strategy = HashMap::new();
         match self.get_relation_node(rel_id)? {
             Relational::Insert {
@@ -775,7 +786,7 @@ impl Plan {
                 {
                     *child
                 } else {
-                    return Err(QueryPlannerError::CustomError(
+                    return Err(SbroadError::UnexpectedNumberOfValues(
                         "Insert node doesn't have exactly a single child.".into(),
                     ));
                 };
@@ -787,12 +798,16 @@ impl Plan {
                 {
                     (list, distribution)
                 } else {
-                    return Err(QueryPlannerError::CustomError(
-                        "Insert child node has an invalid node instead of the output row".into(),
+                    return Err(SbroadError::Invalid(
+                        Entity::Node,
+                        Some(
+                            "Insert child node has an invalid node instead of the output row"
+                                .into(),
+                        ),
                     ));
                 };
                 if list.len() != columns.len() {
-                    return Err(QueryPlannerError::CustomError(format!(
+                    return Err(SbroadError::UnexpectedNumberOfValues(format!(
                         "Insert node expects {} columns instead of {}",
                         list.len(),
                         columns.len()
@@ -805,7 +820,7 @@ impl Plan {
                     .collect::<HashMap<_, _>>();
                 let mut motion_key: MotionKey = MotionKey::new();
                 let rel = self.get_relation(relation).ok_or_else(|| {
-                    QueryPlannerError::CustomError(format!("Relation {relation} not found"))
+                    SbroadError::NotFound(Entity::Table, format!("{relation} among plan relations"))
                 })?;
                 for pos in &rel.key.positions {
                     if let Some(child_pos) = columns_map.get(pos) {
@@ -816,9 +831,10 @@ impl Plan {
                     } else {
                         // Check that the column exists on the requested position.
                         rel.columns.get(*pos).ok_or_else(|| {
-                            QueryPlannerError::CustomError(format!(
-                                "Column {pos} not found in relation {relation}"
-                            ))
+                            SbroadError::NotFound(
+                                Entity::Column,
+                                format!("{pos} in relation {relation}"),
+                            )
                         })?;
                         // We need a default value for the key column.
                         motion_key
@@ -827,10 +843,10 @@ impl Plan {
                     }
                 }
                 if distribution.is_none() {
-                    return Err(QueryPlannerError::CustomError(format!(
-                        "Insert node child {} has no distribution",
-                        child
-                    )));
+                    return Err(SbroadError::Invalid(
+                        Entity::Distribution,
+                        Some(format!("Insert node child {child} has no distribution")),
+                    ));
                 }
 
                 // At the moment we always add a segment motion policy under the
@@ -849,8 +865,9 @@ impl Plan {
                 );
             }
             _ => {
-                return Err(QueryPlannerError::CustomError(
-                    "Expected insert node".into(),
+                return Err(SbroadError::Invalid(
+                    Entity::Relational,
+                    Some("expected insert node".into()),
                 ))
             }
         }
@@ -860,12 +877,13 @@ impl Plan {
         let sharding_pos =
             if let Relational::Insert { relation, .. } = self.get_relation_node(rel_id)? {
                 let rel = self.get_relation(relation).ok_or_else(|| {
-                    QueryPlannerError::CustomError(format!("Relation {relation} not found"))
+                    SbroadError::NotFound(Entity::Table, format!("{relation} among plan relations"))
                 })?;
                 rel.get_bucket_id_position()?
             } else {
-                return Err(QueryPlannerError::CustomError(
-                    "Expected insert node".into(),
+                return Err(SbroadError::Invalid(
+                    Entity::Relational,
+                    Some("expected insert node".into()),
                 ));
             };
         if let Relational::Insert {
@@ -878,7 +896,7 @@ impl Plan {
         Ok(map)
     }
 
-    fn resolve_except_conflicts(&mut self, rel_id: usize) -> Result<Strategy, QueryPlannerError> {
+    fn resolve_except_conflicts(&mut self, rel_id: usize) -> Result<Strategy, SbroadError> {
         let mut map: Strategy = HashMap::new();
         match self.get_relation_node(rel_id)? {
             Relational::Except { children, .. } => {
@@ -892,7 +910,7 @@ impl Plan {
                     let right_output_row =
                         self.get_expression_node(right_output_id)?.get_row_list()?;
                     if left_output_row.len() != right_output_row.len() {
-                        return Err(QueryPlannerError::CustomError(format!(
+                        return Err(SbroadError::UnexpectedNumberOfValues(format!(
                             "Except node children have different row lengths: left {}, right {}",
                             left_output_row.len(),
                             right_output_row.len()
@@ -913,8 +931,9 @@ impl Plan {
                                     return Ok(map);
                                 }
                             }
-                            let key = left_keys.iter().next().ok_or_else(|| QueryPlannerError::CustomError(
-                                "Left child's segment distribution is invalid: no keys found in the set".into()
+                            let key = left_keys.iter().next().ok_or_else(|| SbroadError::Invalid(
+                                Entity::Distribution,
+                                Some("left child's segment distribution is invalid: no keys found in the set".into())
                             ))?;
                             map.insert(
                                 *right,
@@ -927,12 +946,13 @@ impl Plan {
                     }
                     return Ok(map);
                 }
-                Err(QueryPlannerError::CustomError(
+                Err(SbroadError::UnexpectedNumberOfValues(
                     "Except node doesn't have exactly two children.".into(),
                 ))
             }
-            _ => Err(QueryPlannerError::CustomError(
-                "Expected except node".into(),
+            _ => Err(SbroadError::Invalid(
+                Entity::Relational,
+                Some("expected Except node".into()),
             )),
         }
     }
@@ -944,7 +964,7 @@ impl Plan {
     /// - failed to resolve distribution conflicts
     /// - failed to set distribution
     #[otm_child_span("plan.transformation.add_motions")]
-    pub fn add_motions(&mut self) -> Result<(), QueryPlannerError> {
+    pub fn add_motions(&mut self) -> Result<(), SbroadError> {
         let nodes = self.get_relational_nodes_dfs_post()?;
         for id in &nodes {
             match self.get_relation_node(*id)?.clone() {
@@ -962,7 +982,7 @@ impl Plan {
                 Relational::Motion { .. } => {
                     // We can apply this transformation only once,
                     // i.e. to the plan without any motion nodes.
-                    return Err(QueryPlannerError::CustomError(String::from(
+                    return Err(SbroadError::DuplicatedValue(String::from(
                         "IR already has Motion nodes.",
                     )));
                 }
diff --git a/sbroad-core/src/ir/transformation/redistribution/tests.rs b/sbroad-core/src/ir/transformation/redistribution/tests.rs
index 5e42e14b31..19fec9f4c3 100644
--- a/sbroad-core/src/ir/transformation/redistribution/tests.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/tests.rs
@@ -1,6 +1,6 @@
 use super::*;
 use crate::collection;
-use crate::errors::QueryPlannerError;
+use crate::errors::SbroadError;
 use crate::ir::distribution::{Distribution, Key};
 use crate::ir::operator::Relational;
 use crate::ir::relation::{Column, ColumnRole, Table, Type};
@@ -65,7 +65,10 @@ fn segment_motion_for_sub_query() {
     assert_eq!(Some(sq_id), plan.get_sub_query_from_row_node(b_id).unwrap());
 
     assert_eq!(
-        QueryPlannerError::UninitializedDistribution,
+        SbroadError::Invalid(
+            Entity::Distribution,
+            Some("distribution is uninitialized".into()),
+        ),
         plan.resolve_sub_query_conflicts(select_id, eq_id)
             .unwrap_err()
     );
diff --git a/sbroad-core/src/ir/transformation/split_columns.rs b/sbroad-core/src/ir/transformation/split_columns.rs
index 6ce693ae38..b712c2d1a8 100644
--- a/sbroad-core/src/ir/transformation/split_columns.rs
+++ b/sbroad-core/src/ir/transformation/split_columns.rs
@@ -12,17 +12,14 @@
 //!   select a from t where (a) = (1) and (2) = (b)
 //! ```
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::Expression;
 use crate::ir::operator::Bool;
 use crate::ir::Plan;
 use crate::otm::child_span;
 use sbroad_proc::otm_child_span;
 
-fn call_expr_tree_split_columns(
-    plan: &mut Plan,
-    top_id: usize,
-) -> Result<usize, QueryPlannerError> {
+fn call_expr_tree_split_columns(plan: &mut Plan, top_id: usize) -> Result<usize, SbroadError> {
     plan.expr_tree_replace_bool(
         top_id,
         &call_split_bool,
@@ -37,7 +34,7 @@ fn call_expr_tree_split_columns(
     )
 }
 
-fn call_split_bool(plan: &mut Plan, top_id: usize) -> Result<usize, QueryPlannerError> {
+fn call_split_bool(plan: &mut Plan, top_id: usize) -> Result<usize, SbroadError> {
     plan.split_bool(top_id)
 }
 
@@ -49,17 +46,17 @@ impl Plan {
     /// - If the operator is not a boolean operator.
     /// - If left and right tuples have different number of columns.
     /// - If the plan is invalid for some unknown reason.
-    fn split_bool(&mut self, expr_id: usize) -> Result<usize, QueryPlannerError> {
+    fn split_bool(&mut self, expr_id: usize) -> Result<usize, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         let (left_id, right_id, op) = match expr {
             Expression::Bool {
                 left, op, right, ..
             } => (*left, *right, op.clone()),
             _ => {
-                return Err(QueryPlannerError::CustomError(format!(
-                    "Node is not a boolean expression: {:?}",
-                    expr
-                )));
+                return Err(SbroadError::Invalid(
+                    Entity::Expression,
+                    Some(format!("node is not a boolean expression: {expr:?}")),
+                ));
             }
         };
         let left_expr = self.get_expression_node(left_id)?;
@@ -74,8 +71,8 @@ impl Plan {
         ) = (left_expr, right_expr)
         {
             if left_list.len() != right_list.len() {
-                return Err(QueryPlannerError::CustomError(format!(
-                    "Left and right rows have different number of columns: {:?}, {:?}",
+                return Err(SbroadError::UnexpectedNumberOfValues(format!(
+                    "left and right rows have different number of columns: {:?}, {:?}",
                     left_expr, right_expr
                 )));
             }
@@ -111,7 +108,7 @@ impl Plan {
     /// # Errors
     /// - If the plan tree is invalid (doesn't contain correct nodes where we expect it to).
     #[otm_child_span("plan.transformation.split_columns")]
-    pub fn split_columns(&mut self) -> Result<(), QueryPlannerError> {
+    pub fn split_columns(&mut self) -> Result<(), SbroadError> {
         self.transform_expr_trees(&call_expr_tree_split_columns)
     }
 }
diff --git a/sbroad-core/src/ir/transformation/split_columns/tests.rs b/sbroad-core/src/ir/transformation/split_columns/tests.rs
index 4afc205575..7725f9503d 100644
--- a/sbroad-core/src/ir/transformation/split_columns/tests.rs
+++ b/sbroad-core/src/ir/transformation/split_columns/tests.rs
@@ -50,8 +50,9 @@ fn split_columns3() {
     let plan_err = plan.split_columns().unwrap_err();
     assert_eq!(
         format!(
-            "{} {} {}",
-            r#"Left and right rows have different number of columns:"#,
+            "{} {} {} {}",
+            r#"unexpected number of values:"#,
+            r#"left and right rows have different number of columns:"#,
             r#"Row { list: [12, 13, 14], distribution: None },"#,
             r#"Row { list: [16, 17], distribution: None }"#,
         ),
diff --git a/sbroad-core/src/ir/value/double.rs b/sbroad-core/src/ir/value/double.rs
index cc7ac85b9a..e332144c91 100644
--- a/sbroad-core/src/ir/value/double.rs
+++ b/sbroad-core/src/ir/value/double.rs
@@ -5,7 +5,7 @@ use std::hash::{Hash, Hasher};
 use std::num::NonZeroI32;
 use std::str::FromStr;
 
-use crate::errors::QueryPlannerError;
+use crate::errors::{Entity, SbroadError};
 use serde::{Deserialize, Serialize};
 use tarantool::tlua;
 
@@ -51,13 +51,13 @@ impl From<u64> for Double {
 }
 
 impl FromStr for Double {
-    type Err = QueryPlannerError;
+    type Err = SbroadError;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         Ok(Double {
-            value: s
-                .parse::<f64>()
-                .map_err(|_| QueryPlannerError::CustomError(format!("{s} is not a valid f64")))?,
+            value: s.parse::<f64>().map_err(|_| {
+                SbroadError::ParsingError(Entity::Value, format!("{s} is not a valid f64"))
+            })?,
         })
     }
 }
diff --git a/sbroad-core/src/otm/statistics.rs b/sbroad-core/src/otm/statistics.rs
index 9bbad3b7f6..e6e41b0ccc 100644
--- a/sbroad-core/src/otm/statistics.rs
+++ b/sbroad-core/src/otm/statistics.rs
@@ -83,7 +83,7 @@ impl SpanProcessor for StatCollector {
                 let tuple = (id.to_string(), query_sql.to_string(), 2);
                 query_space.borrow_mut().upsert(tuple);
             });
-            debug!(Option::from("on start"), &format!("query: {}", query_sql));
+            debug!(Option::from("on start"), &format!("query: {query_sql}"));
         }
 
         // Register current span mapping (span id: span name).
@@ -92,7 +92,7 @@ impl SpanProcessor for StatCollector {
             let value = SpanName::from(span_data.name.clone());
             debug!(
                 Option::from("on start"),
-                &format!("key: {:?}, value: {:?}", key, value)
+                &format!("key: {key:?}, value: {value:?}")
             );
             span_table.borrow_mut().push(key, value);
         });
diff --git a/sbroad-core/src/otm/statistics/eviction.rs b/sbroad-core/src/otm/statistics/eviction.rs
index bb1bfe76c9..e2e4aa6532 100644
--- a/sbroad-core/src/otm/statistics/eviction.rs
+++ b/sbroad-core/src/otm/statistics/eviction.rs
@@ -6,7 +6,7 @@
 //! space.
 
 use crate::debug;
-use crate::errors::QueryPlannerError;
+use crate::errors::SbroadError;
 use crate::executor::lru::{Cache, LRUCache};
 use crate::otm::statistics::table::{TarantoolSpace, QUERY};
 use std::cell::RefCell;
@@ -19,12 +19,12 @@ pub struct TrackedQueries {
 }
 
 #[allow(clippy::unnecessary_wraps)]
-fn remove_query(query_id: &mut String) -> Result<(), QueryPlannerError> {
+fn remove_query(query_id: &mut String) -> Result<(), SbroadError> {
     QUERY.with(|query_space| {
         let mut query_space = query_space.borrow_mut();
         debug!(
             Option::from("tracked queries"),
-            &format!("remove query: {}", query_id)
+            &format!("remove query: {query_id}")
         );
         let key = std::mem::take(query_id);
         query_space.delete(&(key,));
@@ -54,7 +54,7 @@ impl TrackedQueries {
     ///
     /// # Errors
     /// - Internal error in the eviction function.
-    pub fn push(&mut self, key: String) -> Result<(), QueryPlannerError> {
+    pub fn push(&mut self, key: String) -> Result<(), SbroadError> {
         self.queries.put(key.clone(), key)
     }
 }
-- 
GitLab