diff --git a/src/include/duckdb/planner/expression.hpp b/src/include/duckdb/planner/expression.hpp index a7f9045f989..97094bbde70 100644 --- a/src/include/duckdb/planner/expression.hpp +++ b/src/include/duckdb/planner/expression.hpp @@ -32,10 +32,12 @@ class Expression : public BaseExpression { bool HasSubquery() const override; bool IsScalar() const override; bool HasParameter() const override; + virtual bool IsVolatile() const; virtual bool IsConsistent() const; virtual bool PropagatesNullValues() const; virtual bool IsFoldable() const; + virtual bool CanThrow() const; hash_t Hash() const override; diff --git a/src/include/duckdb/planner/expression/bound_cast_expression.hpp b/src/include/duckdb/planner/expression/bound_cast_expression.hpp index d45ac502f35..c625fb4f9d9 100644 --- a/src/include/duckdb/planner/expression/bound_cast_expression.hpp +++ b/src/include/duckdb/planner/expression/bound_cast_expression.hpp @@ -54,6 +54,8 @@ class BoundCastExpression : public Expression { unique_ptr Copy() const override; + bool CanThrow() const override; + void Serialize(Serializer &serializer) const override; static unique_ptr Deserialize(Deserializer &deserializer); diff --git a/src/optimizer/pushdown/pushdown_projection.cpp b/src/optimizer/pushdown/pushdown_projection.cpp index ec82b39cd4f..f66ce66fd89 100644 --- a/src/optimizer/pushdown/pushdown_projection.cpp +++ b/src/optimizer/pushdown/pushdown_projection.cpp @@ -3,6 +3,8 @@ #include "duckdb/planner/expression_iterator.hpp" #include "duckdb/planner/operator/logical_empty_result.hpp" #include "duckdb/planner/operator/logical_projection.hpp" +#include "duckdb/planner/expression/bound_cast_expression.hpp" +#include "duckdb/common/types.hpp" namespace duckdb { @@ -51,7 +53,7 @@ unique_ptr FilterPushdown::PushdownProjection(unique_ptrCanThrow()) { // We can't push down related expressions if the column in the // expression is generated by the functions which have side effects remain_expressions.push_back(std::move(f.filter)); diff --git a/src/planner/expression.cpp b/src/planner/expression.cpp index 9fa426b8a37..3e9539b0c4a 100644 --- a/src/planner/expression.cpp +++ b/src/planner/expression.cpp @@ -58,6 +58,12 @@ bool Expression::IsConsistent() const { return is_consistent; } +bool Expression::CanThrow() const { + bool can_throw = false; + ExpressionIterator::EnumerateChildren(*this, [&](const Expression &child) { can_throw |= child.CanThrow(); }); + return can_throw; +} + bool Expression::PropagatesNullValues() const { if (type == ExpressionType::OPERATOR_IS_NULL || type == ExpressionType::OPERATOR_IS_NOT_NULL || type == ExpressionType::COMPARE_NOT_DISTINCT_FROM || type == ExpressionType::COMPARE_DISTINCT_FROM || diff --git a/src/planner/expression/bound_cast_expression.cpp b/src/planner/expression/bound_cast_expression.cpp index 1c8dc951872..2cd3869d57b 100644 --- a/src/planner/expression/bound_cast_expression.cpp +++ b/src/planner/expression/bound_cast_expression.cpp @@ -2,6 +2,7 @@ #include "duckdb/planner/expression/bound_default_expression.hpp" #include "duckdb/planner/expression/bound_parameter_expression.hpp" #include "duckdb/planner/expression/bound_constant_expression.hpp" +#include "duckdb/planner/expression_iterator.hpp" #include "duckdb/function/cast_rules.hpp" #include "duckdb/function/cast/cast_function_set.hpp" #include "duckdb/main/config.hpp" @@ -217,4 +218,15 @@ unique_ptr BoundCastExpression::Copy() const { return std::move(copy); } +bool BoundCastExpression::CanThrow() const { + const auto child_type = child->return_type; + if (return_type.id() != child_type.id() && + LogicalType::ForceMaxLogicalType(return_type, child_type) == child_type.id()) { + return true; + } + bool changes_type = false; + ExpressionIterator::EnumerateChildren(*this, [&](const Expression &child) { changes_type |= child.CanThrow(); }); + return changes_type; +} + } // namespace duckdb diff --git a/test/sql/optimizer/test_no_pushdown_cast_into_cte.test b/test/sql/optimizer/test_no_pushdown_cast_into_cte.test new file mode 100644 index 00000000000..121240a812e --- /dev/null +++ b/test/sql/optimizer/test_no_pushdown_cast_into_cte.test @@ -0,0 +1,74 @@ +# name: test/sql/optimizer/test_no_pushdown_cast_into_cte.test +# description: No Pushdown cast into cte +# group: [optimizer] + +statement ok +pragma explain_output='optimized_only'; + +query II +WITH t(a, b) AS ( + SELECT a :: int, b :: int + FROM (VALUES + ('1', '4'), + ('5', '3'), + ('2', '*'), + ('3', '8'), + ('7', '*')) AS _(a, b) + WHERE position('*' in b) = 0 +) +SELECT a, b +FROM t +WHERE a < b; +---- +1 4 +3 8 + + +# check filter is above projection that casts the varchar to int +query II +EXPLAIN WITH t(a, b) AS ( + SELECT a :: int, b :: int + FROM (VALUES + ('1', '4'), + ('5', '3'), + ('2', '*'), + ('3', '8'), + ('7', '*')) AS _(a, b) + WHERE position('*' in b) = 0 +) +SELECT a, b +FROM t +WHERE a < b; +---- +logical_opt :.*FILTER.*CAST\(a AS INTEGER.*<.*b AS INTEGER\).*PROJECTION.*FILTER.*position.* + + +# INT can always be cast to varchar, so the filter a[1] = '1' +# can be pushed down +query II +with t(a, b) as ( + select a :: varchar, b :: varchar + FROM VALUES + (1, 2), + (3, 3), + (5, 6), + (7, 6) as + _(a, b) where a <= b +) select a, b from t where a[1] = '1'; +---- +1 2 + + +# we should not see two filters, since the filter can be pushed to just above the column data scan +query II +explain with t(a, b) as ( + select a :: varchar, b :: varchar + FROM VALUES + (1, 2), + (3, 3), + (5, 6), + (7, 6) as + _(a, b) where a <= b +) select a, b from t where a[1] = '1'; +---- +logical_opt :.*FILTER.*PROJECTION.*FILTER.*