diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs
index c76de1c43f5fb911ccc865d1898bacc228dca6a0..266a56642e63e8c4b5ab2519adf2978863c17c02 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -1201,63 +1201,6 @@ fn parse_option<M: Metadata>(
     Ok(value)
 }
 
-fn parse_cast_expr<M: Metadata>(
-    pair: Pair<Rule>,
-    referred_relation_ids: &[usize],
-    worker: &mut ExpressionsWorker<M>,
-    plan: &mut Plan,
-) -> Result<ParseExpression, SbroadError> {
-    let mut inner_pairs = pair.into_inner();
-    let expr_pair = inner_pairs.next().expect("Cast has no expr child.");
-    let child_parse_expr =
-        parse_expr_pratt(expr_pair.into_inner(), referred_relation_ids, worker, plan)?;
-
-    let mut cast_types = Vec::with_capacity(inner_pairs.len());
-    for type_pairs in inner_pairs {
-        let cast_type = if type_pairs.as_rule() == Rule::ColumnDefType {
-            let mut column_def_type_pairs = type_pairs.into_inner();
-            let column_def_type = column_def_type_pairs
-                .next()
-                .expect("concrete type expected under ColumnDefType");
-            if column_def_type.as_rule() == Rule::TypeVarchar {
-                let mut type_pairs_inner = column_def_type.into_inner();
-                let varchar_length = type_pairs_inner
-                    .next()
-                    .expect("Length is missing under Varchar");
-                let len = varchar_length.as_str().parse::<usize>().map_err(|e| {
-                    SbroadError::ParsingError(
-                        Entity::Value,
-                        format_smolstr!("failed to parse varchar length: {e:?}"),
-                    )
-                })?;
-                Ok(CastType::Varchar(len))
-            } else {
-                CastType::try_from(&column_def_type.as_rule())
-            }
-        } else {
-            // TypeAny.
-            CastType::try_from(&type_pairs.as_rule())
-        }?;
-
-        cast_types.push(cast_type);
-    }
-
-    assert!(!cast_types.is_empty(), "cast expression has no cast types");
-
-    if let ParseExpression::PlanId { plan_id } = child_parse_expr {
-        let node = plan.get_mut_node(plan_id)?;
-        if let Node::Parameter(..) = node {
-            // Assign parameter type from the cast, just like Postgres.
-            *node = Node::Parameter(Some(cast_types[0].as_relation_type()));
-        }
-    }
-
-    Ok(ParseExpression::Cast {
-        cast_types,
-        child: Box::new(child_parse_expr),
-    })
-}
-
 enum ParameterSource<'parameter> {
     AstNode {
         ast: &'parameter AbstractSyntaxTree,
@@ -1372,7 +1315,7 @@ fn parse_param<M: Metadata>(
 lazy_static::lazy_static! {
     static ref PRATT_PARSER: PrattParser<Rule> = {
         use pest::pratt_parser::{Assoc::{Left, Right}, Op};
-        use Rule::{Add, And, Between, ConcatInfixOp, Divide, Eq, Gt, GtEq, In, IsNullPostfix, Lt, LtEq, Multiply, NotEq, Or, Subtract, UnaryNot};
+        use Rule::{Add, And, Between, ConcatInfixOp, Divide, Eq, Gt, GtEq, In, IsNullPostfix, CastPostfix, Lt, LtEq, Multiply, NotEq, Or, Subtract, UnaryNot};
 
         // Precedence is defined lowest to highest.
         PrattParser::new()
@@ -1388,6 +1331,7 @@ lazy_static::lazy_static! {
             .op(Op::infix(Add, Left) | Op::infix(Subtract, Left))
             .op(Op::infix(Multiply, Left) | Op::infix(Divide, Left) | Op::infix(ConcatInfixOp, Left))
             .op(Op::postfix(IsNullPostfix))
+            .op(Op::postfix(CastPostfix))
     };
 }
 
@@ -1527,7 +1471,7 @@ enum ParseExpression {
         child: Box<ParseExpression>,
     },
     Cast {
-        cast_types: Vec<CastType>,
+        cast_type: CastType,
         child: Box<ParseExpression>,
     },
     Case {
@@ -1643,12 +1587,9 @@ impl ParseExpression {
                     plan.add_covered_with_parentheses(child_plan_id)
                 }
             }
-            ParseExpression::Cast { cast_types, child } => {
-                let mut child_plan_id = child.populate_plan(plan, worker)?;
-                for cast_type in cast_types {
-                    child_plan_id = plan.add_cast(child_plan_id, cast_type.clone())?;
-                }
-                child_plan_id
+            ParseExpression::Cast { cast_type, child } => {
+                let child_plan_id = child.populate_plan(plan, worker)?;
+                plan.add_cast(child_plan_id, *cast_type)?
             }
             ParseExpression::Case {
                 search_expr,
@@ -1953,6 +1894,33 @@ fn find_interim_between(mut expr: &mut ParseExpression) -> Option<(&mut ParseExp
     }
 }
 
+fn cast_type_from_pair(type_pair: Pair<Rule>) -> Result<CastType, SbroadError> {
+    if type_pair.as_rule() != Rule::ColumnDefType {
+        // TypeAny.
+        return CastType::try_from(&type_pair.as_rule());
+    }
+
+    let mut column_def_type_pairs = type_pair.into_inner();
+    let column_def_type = column_def_type_pairs
+        .next()
+        .expect("concrete type expected under ColumnDefType");
+    if column_def_type.as_rule() != Rule::TypeVarchar {
+        return CastType::try_from(&column_def_type.as_rule());
+    }
+
+    let mut type_pairs_inner = column_def_type.into_inner();
+    let varchar_length = type_pairs_inner
+        .next()
+        .expect("Length is missing under Varchar");
+    let len = varchar_length.as_str().parse::<usize>().map_err(|e| {
+        SbroadError::ParsingError(
+            Entity::Value,
+            format_smolstr!("Failed to parse varchar length: {e:?}."),
+        )
+    })?;
+    Ok(CastType::Varchar(len))
+}
+
 /// Function responsible for parsing expressions using Pratt parser.
 ///
 /// Parameters:
@@ -2202,8 +2170,19 @@ where
                     ParseExpression::Exists { is_not: first_is_not, child: Box::new(child_parse_expr)}
                 }
                 Rule::Trim => parse_trim(primary, referred_relation_ids, worker, plan)?,
-                Rule::CastOp | Rule::CastExpr => {
-                    parse_cast_expr(primary, referred_relation_ids, worker, plan)?
+                Rule::CastOp => {
+                    let mut inner_pairs = primary.into_inner();
+                    let expr_pair = inner_pairs.next().expect("Cast has no expr child.");
+                    let child_parse_expr = parse_expr_pratt(
+                        expr_pair.into_inner(),
+                        referred_relation_ids,
+                        worker,
+                        plan
+                    )?;
+                    let type_pair = inner_pairs.next().expect("CastOp has no type child");
+                    let cast_type = cast_type_from_pair(type_pair)?;
+
+                    ParseExpression::Cast { cast_type, child: Box::new(child_parse_expr) }
                 }
                 Rule::Case => {
                     let mut inner_pairs = primary.into_inner();
@@ -2358,14 +2337,21 @@ where
             Ok(ParseExpression::Prefix { op, child: Box::new(child?)})
         })
         .map_postfix(|child, op| {
+            let child = child?;
             match op.as_rule() {
+                Rule::CastPostfix => {
+                    let ty_pair = op.into_inner().next()
+                        .expect("Expected ColumnDefType under CastPostfix.");
+                    let cast_type = cast_type_from_pair(ty_pair)?;
+                    Ok(ParseExpression::Cast { child: Box::new(child), cast_type })
+                }
                 Rule::IsNullPostfix => {
                     let is_not = match op.into_inner().len() {
                         1 => true,
                         0 => false,
                         _ => unreachable!("IsNull must have 0 or 1 children")
                     };
-                    Ok(ParseExpression::IsNull { is_not, child: Box::new(child?)})
+                    Ok(ParseExpression::IsNull { is_not, child: Box::new(child)})
                 },
                 rule => unreachable!("Expr::parse expected postfix operator, found {:?}", rule),
             }
diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest
index 65a9e16a811cd4b3ee076216bef04921b3d08348..151a4bd00f5e492f3ab7073313d7e9d4eaf0a6d9 100644
--- a/sbroad-core/src/frontend/sql/query.pest
+++ b/sbroad-core/src/frontend/sql/query.pest
@@ -267,8 +267,9 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* }
             LtEq  = { "<=" }
             NotEq = { "<>" | "!=" }
             In    = { NotFlag? ~ ^"in" }
-    ExprAtomValue = _{ CastExpr | (UnaryNot* ~ AtomicExpr ~ IsNullPostfix?) }
+    ExprAtomValue = _{ UnaryNot* ~ AtomicExpr ~ CastPostfix* ~ IsNullPostfix? }
         UnaryNot   = @{ NotFlag }
+        CastPostfix = { "::" ~ ColumnDefType }
         IsNullPostfix = { ^"is" ~ NotFlag? ~ ^"null" }
         AtomicExpr = _{ Literal | Parameter | CastOp | Trim | CurrentDate | IdentifierWithOptionalContinuation | ExpressionInParentheses | UnaryOperator | Case | SubQuery | Row }
             Literal = { True | False | Null | Double | Decimal | Unsigned | Integer | SingleQuotedString }
@@ -330,10 +331,6 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* }
             UnaryOperator = _{ Exists }
                 Exists = { NotFlag? ~ ^"exists" ~ SubQuery }
             Row = { "(" ~ Expr ~ ("," ~ Expr)* ~ ")" }
-        CastExpr = { AtomicExprWrapped ~ ("::" ~ ColumnDefType)+ }
-            // In CastOp rule, AtomicExpr is stored as a child of Expr, so we try to imitate
-            // this in order parse CastExpr and CastOp rules in the same way.
-            AtomicExprWrapped = { AtomicExpr }
 
 Distinct = { ^"distinct" }
 NotFlag = { ^"not" }
diff --git a/sbroad-core/src/ir/explain.rs b/sbroad-core/src/ir/explain.rs
index c832e3bc038e875c3f336dfb1671e5e0435c18ca..da65e2d744421bb9b3c2b55f742f9f1306132160 100644
--- a/sbroad-core/src/ir/explain.rs
+++ b/sbroad-core/src/ir/explain.rs
@@ -135,7 +135,7 @@ impl ColExpr {
                             "stack is empty while processing CAST expression".to_smolstr(),
                         )
                     })?;
-                    let cast_expr = ColExpr::Cast(Box::new(expr), to.clone());
+                    let cast_expr = ColExpr::Cast(Box::new(expr), *to);
                     stack.push((cast_expr, id));
                 }
                 Expression::Case {
diff --git a/sbroad-core/src/ir/expression/cast.rs b/sbroad-core/src/ir/expression/cast.rs
index 807c364c71781b3ce9755c4c25567d2a2ddc398c..4005fad34298bdc0bc59da625bf448850622f8d4 100644
--- a/sbroad-core/src/ir/expression/cast.rs
+++ b/sbroad-core/src/ir/expression/cast.rs
@@ -8,7 +8,7 @@ use crate::ir::{Node, Plan};
 use serde::{Deserialize, Serialize};
 use smol_str::{format_smolstr, SmolStr, ToSmolStr};
 
-#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
+#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
 pub enum Type {
     Any,
     Map,
@@ -136,6 +136,12 @@ impl Plan {
             to: to_type,
         };
         let cast_id = self.nodes.push(Node::Expression(cast_expr));
+
+        let child_plan_node = self.get_mut_node(expr_id)?;
+        if let Node::Parameter(ref mut ty) = child_plan_node {
+            *ty = Some(to_type.as_relation_type());
+        }
+
         Ok(cast_id)
     }
 }