Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
6f91aa6
Allow JOIN filter pushdown when side column names do not match the jo…
ianton-ru Aug 21, 2026
af87b90
Copy left-only WHERE into IStorageCluster JOIN wraps so icebergCluste…
ianton-ru Aug 21, 2026
4e1b754
Reuse existing left-only predicate helper for IStorageCluster JOIN wraps
ianton-ru Aug 21, 2026
a54a78a
Do not copy wrap predicates onto the null-producing side of an outer …
ianton-ru Aug 24, 2026
dc2f772
Do not copy nondeterministic wrap predicates that would run twice
ianton-ru Aug 24, 2026
78a27b3
Remove unnecessary `no-parallel-replicas` tag from JOIN filter pushdo…
ianton-ru Aug 24, 2026
28b3de8
Do not copy wrap predicates onto `ASOF` right or `PASTE` JOIN sides
ianton-ru Aug 24, 2026
0e14162
Do not copy stateful wrap predicates that would run twice
ianton-ru Aug 24, 2026
707dcdc
Share JOIN prefilter side rules between wrap copy and filter pushdown
ianton-ru Aug 24, 2026
2018169
Pin JOIN filter pushdown EXPLAIN test against parallel replicas and r…
ianton-ru Aug 24, 2026
a1d4502
Drop non-function wrap predicates that depend on the other JOIN side
ianton-ru Aug 24, 2026
cdc4b28
Keep equivalent-key JOIN filter pushdown separate from side prefilter
ianton-ru Aug 24, 2026
d9ebb0b
Rebuild `IStorageCluster` listing when a later `applyFilters` predica…
ianton-ru Aug 24, 2026
cf210cb
Do not copy server-constant wrap predicates such as `hostName`
ianton-ru Aug 24, 2026
18a667b
Do not copy node-local wrap predicates such as `dictGet`
ianton-ru Aug 25, 2026
523f4a0
Merge branch 'antalya-26.6' into fix/join-filter-pushdown-through-rename
ianton-ru Aug 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 90 additions & 10 deletions src/Analyzer/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <Storages/IStorage.h>

#include <Interpreters/Context.h>
#include <Interpreters/misc.h>

#include <Analyzer/ArrayJoinNode.h>
#include <Analyzer/ColumnNode.h>
Expand All @@ -52,6 +53,7 @@

#include <Core/Streaming/CursorTree_fwd.h>

#include <functional>
#include <ranges>

namespace DB
Expand Down Expand Up @@ -1168,24 +1170,82 @@ bool hasUnknownColumn(const QueryTreeNodePtr & node, QueryTreeNodePtr table_expr
return false;
}

void removeExpressionsThatDoNotDependOnTableIdentifiers(
namespace
{

template <typename KeepFunction>
bool walkOrdinaryFunctions(const QueryTreeNodePtr & node, KeepFunction && keep_function)
{
QueryTreeNodes stack = {node};
while (!stack.empty())
{
auto current = std::move(stack.back());
stack.pop_back();
if (!current)
continue;

const auto type = current->getNodeType();
if (type == QueryTreeNodeType::QUERY || type == QueryTreeNodeType::UNION)
return false;

if (const auto * function = current->as<FunctionNode>())
{
if (function->isWindowFunction() || function->isAggregateFunction())
return false;
if (function->isOrdinaryFunction())
{
auto function_base = function->getFunction();
if (!function_base || !keep_function(function_base))
return false;
}
}

for (const auto & child : current->getChildren())
{
if (child)
stack.push_back(child);
}
}
return true;
}

bool isSafeToDuplicateInQueryTree(const QueryTreeNodePtr & node)
{
return walkOrdinaryFunctions(
node,
[](const FunctionBasePtr & function_base)
{
return function_base->isDeterministic()
&& function_base->isDeterministicInScopeOfQuery()
&& !function_base->isStateful()
&& !function_base->isServerConstant()
&& !functionIsDictGet(function_base->getName())
&& !functionIsJoinGet(function_base->getName());
});
}

void filterConjunctions(
QueryTreeNodePtr & expression,
const QueryTreeNodePtr & table_expression,
const std::function<bool(const QueryTreeNodePtr &)> & keep,
const ContextPtr & context)
{
auto * function = expression->as<FunctionNode>();
if (!function)
{
if (!keep(expression))
expression = {};
return;
}

if (function->getFunctionName() != "and")
{
if (hasUnknownColumn(expression, table_expression))
expression = nullptr;
if (!keep(expression))
expression = {};
return;
}

QueryTreeNodesDeque conjunctions;
QueryTreeNodesDeque processing{ expression };
QueryTreeNodesDeque processing{expression};

while (!processing.empty())
{
Expand All @@ -1195,10 +1255,7 @@ void removeExpressionsThatDoNotDependOnTableIdentifiers(
if (auto * function_node = node->as<FunctionNode>())
{
if (function_node->getFunctionName() == "and")
std::ranges::copy(
function_node->getArguments(),
std::back_inserter(processing)
);
std::ranges::copy(function_node->getArguments(), std::back_inserter(processing));
else
conjunctions.push_back(node);
}
Expand All @@ -1212,7 +1269,7 @@ void removeExpressionsThatDoNotDependOnTableIdentifiers(

for (const auto & node : processing)
{
if (!hasUnknownColumn(node, table_expression))
if (keep(node))
conjunctions.push_back(node);
}

Expand All @@ -1234,6 +1291,29 @@ void removeExpressionsThatDoNotDependOnTableIdentifiers(
function->resolveAsFunction(function_impl->build(function->getArgumentColumns()));
}

}

void removeExpressionsThatDoNotDependOnTableIdentifiers(
QueryTreeNodePtr & expression,
const QueryTreeNodePtr & table_expression,
const ContextPtr & context)
{
filterConjunctions(
expression,
[&](const QueryTreeNodePtr & node) { return !hasUnknownColumn(node, table_expression); },
context);
}

void removeExpressionsThatAreUnsafeToDuplicate(
QueryTreeNodePtr & expression,
const ContextPtr & context)
{
if (!expression)
return;

filterConjunctions(expression, isSafeToDuplicateInQueryTree, context);
}

namespace
{

Expand Down
16 changes: 15 additions & 1 deletion src/Analyzer/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,27 @@ bool hasUnknownColumn(
/** Suppose we have a table x with columns a, c, d and
* a an expression like x.a > 2 AND y.b > 3 AND x.c + 1 == x.d
* This method will remove the part y.b > 3 from it since it depends
* on unknown columns from a different table.
* on unknown columns from a different table. A non-function root such as
* `WHERE y.b` is dropped the same way.
*/
void removeExpressionsThatDoNotDependOnTableIdentifiers(
QueryTreeNodePtr & expression,
const QueryTreeNodePtr & replacement_table_expression,
const ContextPtr & context);

/** Remove conjuncts that are unsafe to copy into another query tree (not deterministic, not
* deterministic in this query, stateful, or server-constant). Nested `and` is flattened the same
* way as `removeExpressionsThatDoNotDependOnTableIdentifiers`. Window and aggregate functions are
* also dropped. JOIN filter pushdown refuses stateful predicates via
* `ActionsDAG::hasStatefulFunctions`.
*
* The wrap `WHERE` is sent to remote cluster nodes. Node-local functions such as `hostName`,
* `dictGet`, `joinGet`, `FQDN`, and `queryID` must stay on the initiator: remotes can miss the
* dictionary, see different data, or return a different server-local value.
*/
void removeExpressionsThatAreUnsafeToDuplicate(
QueryTreeNodePtr & expression,
const ContextPtr & context);

Field getFieldFromColumnForASTLiteral(const ColumnPtr & column, size_t row, const DataTypePtr & data_type);

Expand Down
19 changes: 19 additions & 0 deletions src/Core/Joins.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,25 @@ enum class JoinTableSide : uint8_t

const char * toString(JoinTableSide join_table_side);

/** Whether ordinary columns from this side of a JOIN can be used as filter inputs
* before the JOIN. Skip the null-producing side of an outer JOIN, the right side
* of an `ASOF JOIN`, and both sides of a `PASTE JOIN` or `FULL JOIN`.
* Attaching an equivalent-key filter to the other child, and dictionary / lookup
* fill, are separate (`JoinStep::allowPushDownToRight`).
*/
constexpr bool canPrefilterJoinSide(JoinKind kind, JoinStrictness strictness, JoinTableSide side)
{
if (isPaste(kind) || isFull(kind))
return false;
if (strictness == JoinStrictness::Asof && side == JoinTableSide::Right)
return false;
if (isLeft(kind) && side == JoinTableSide::Right)
return false;
if (isRight(kind) && side == JoinTableSide::Left)
return false;
return true;
}

enum class JoinOrderAlgorithm : uint8_t
{
GREEDY = 0,
Expand Down
2 changes: 2 additions & 0 deletions src/Functions/FunctionJoinGet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class FunctionJoinGet final : public IFunctionBase

String getName() const override { return function_name; }

bool isDeterministic() const override { return false; }

bool isSuitableForShortCircuitArgumentsExecution(const DataTypesWithConstInfo & /*arguments*/) const override { return true; }

const DataTypes & getArgumentTypes() const override { return argument_types; }
Expand Down
10 changes: 10 additions & 0 deletions src/Planner/Planner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ void checkStoragesSupportTransactions(const PlannerContextPtr & planner_context)
}
}

}

namespace
{

/** Storages can rely that filters that for storage will be available for analysis before
* getQueryProcessingStage method will be called.
*
Expand Down Expand Up @@ -390,6 +395,8 @@ FiltersForTableExpressionMap collectFiltersForAnalysis(const QueryTreeNodePtr &
return res;
}

}

FiltersForTableExpressionMap collectFiltersForAnalysis(const QueryTreeNodePtr & query_tree_node, const SelectQueryOptions & select_query_options, const ActionsDAG * post_filter)
{
if (select_query_options.only_analyze)
Expand All @@ -411,6 +418,9 @@ FiltersForTableExpressionMap collectFiltersForAnalysis(const QueryTreeNodePtr &
return collectFiltersForAnalysis(query_tree_node, table_expressions_nodes, context, post_filter);
}

namespace
{

/// Extend lifetime of query context, storages, and table locks
void extendQueryContextAndStoragesLifetime(QueryPlan & query_plan, const PlannerContextPtr & planner_context)
{
Expand Down
6 changes: 6 additions & 0 deletions src/Planner/Planner.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <Processors/QueryPlan/QueryPlan.h>
#include <Storages/SelectQueryInfo.h>
#include <Planner/PlannerContext.h>

namespace DB
{
Expand Down Expand Up @@ -89,4 +90,9 @@ class Planner
QueryNodeToPlanStepMapping query_node_to_plan_step_mapping;
};

FiltersForTableExpressionMap collectFiltersForAnalysis(
const QueryTreeNodePtr & query_tree_node,
const SelectQueryOptions & select_query_options,
const ActionsDAG * post_filter);

}
Loading
Loading