Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions src/binder/bind/bind_projection_clause.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <algorithm>
#include <unordered_set>
#include <utility>

#include "binder/binder.h"
#include "binder/expression/expression_util.h"
Expand Down Expand Up @@ -124,6 +125,9 @@ static expression_vector getAggregateExpressions(const std::shared_ptr<Expressio
}

BoundProjectionList Binder::bindProjectionList(const ProjectionBody& projectionBody) {
auto& config = expressionBinder.config;
auto outerAggregateNames = std::exchange(config.projectionAggregateNames, {});
auto outerBindingProjectionList = std::exchange(config.bindingProjectionList, true);
expression_vector projectionExprs;
std::vector<std::string> aliases;
std::vector<const ParsedExpression*> parsedItems;
Expand Down Expand Up @@ -162,6 +166,8 @@ BoundProjectionList Binder::bindProjectionList(const ProjectionBody& projectionB
parsedItems.push_back(parsedExpr.get());
}
}
config.bindingProjectionList = outerBindingProjectionList;
config.projectionAggregateNames = std::move(outerAggregateNames);
return {std::move(projectionExprs), std::move(aliases), std::move(parsedItems)};
}

Expand Down
23 changes: 23 additions & 0 deletions src/binder/bind_expression/bind_function_expression.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#include <algorithm>

#include "binder/binder.h"
#include "binder/expression/aggregate_function_expression.h"
#include "binder/expression/scalar_function_expression.h"
#include "binder/expression_binder.h"
#include "binder/expression_visitor.h"
#include "catalog/catalog.h"
#include "common/exception/binder.h"
#include "function/arithmetic/vector_arithmetic_functions.h"
Expand All @@ -23,6 +26,14 @@ using namespace lbug::catalog;
namespace lbug {
namespace binder {

static bool containsExpressionNamed(const Expression& expression, const std::string& uniqueName) {
if (expression.getUniqueName() == uniqueName) {
return true;
}
return std::ranges::any_of(ExpressionChildrenCollector::collectChildren(expression),
[&](const auto& child) { return containsExpressionNamed(*child, uniqueName); });
}

std::shared_ptr<Expression> ExpressionBinder::bindFunctionExpression(const ParsedExpression& expr) {
auto funcExpr = expr.constPtrCast<ParsedFunctionExpression>();
auto functionName = funcExpr->getNormalizedFunctionName();
Expand Down Expand Up @@ -180,6 +191,18 @@ std::shared_ptr<Expression> ExpressionBinder::bindAggregateFunctionExpression(
AggregateFunctionExpression::getUniqueName(function.name, children, function.isDistinct);
if (children.empty()) {
uniqueExpressionName = binder->getUniqueExpressionName(uniqueExpressionName);
} else if (config.bindingProjectionList &&
std::ranges::any_of(binder->scope.getExpressions(), [&](const auto& expr) {
return containsExpressionNamed(*expr, uniqueExpressionName);
})) {
// The name is structural, so this aggregate would share it, and its column, with the
// earlier clause's result, e.g. the group key c in WITH x, count(x) AS c RETURN c,
// count(x), or count(x) inside c in WITH x, count(x) + x AS c RETURN c, count(x) + x.
auto [entry, isNew] = config.projectionAggregateNames.try_emplace(uniqueExpressionName);
if (isNew) {
entry->second = binder->getUniqueExpressionName(uniqueExpressionName);
}
uniqueExpressionName = entry->second;
}
std::unique_ptr<FunctionBindData> bindData;
if (function.bindFunc) {
Expand Down
7 changes: 7 additions & 0 deletions src/binder/bind_expression/bind_lambda_expression.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <utility>

#include "binder/binder.h"
#include "binder/expression/expression_util.h"
#include "binder/expression/lambda_expression.h"
Expand All @@ -20,8 +22,13 @@ void ExpressionBinder::bindLambdaExpression(const Expression& lambdaInput,
for (auto& varName : parsedLambdaExpr.getVarNames()) {
binder->createVariable(varName, listChildType);
}
// The projection does not compute aggregates inside a lambda body, so they keep their
// structural names and read the earlier clause's column, as before.
auto& config = binder->getExpressionBinder()->config;
auto bindingProjectionList = std::exchange(config.bindingProjectionList, false);
auto funcExpr =
binder->getExpressionBinder()->bindExpression(*parsedLambdaExpr.getFunctionExpr());
config.bindingProjectionList = bindingProjectionList;
binder->restoreScope(std::move(prevScope));
boundLambdaExpr.cast(funcExpr->getDataType().copy());
boundLambdaExpr.setFunctionExpr(std::move(funcExpr));
Expand Down
5 changes: 5 additions & 0 deletions src/include/binder/expression_binder.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ struct ExpressionBinderConfig {
// may lead to empty result.
// e.g. [* (r, n | WHERE label(n)='dummy') ]
bool disableLabelFunctionLiteralRewrite = false;
// Set while a projection list is bound: an aggregate there is computed by that projection, so
// one whose structural name an earlier clause's expression has gets a fresh name, the same one
// for each occurrence in the list. In WHERE or ORDER BY it keeps the structural name.
bool bindingProjectionList = false;
std::unordered_map<std::string, std::string> projectionAggregateNames;
};

class ExpressionBinder {
Expand Down
32 changes: 32 additions & 0 deletions test/test_files/agg/multi_query_part.test
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,35 @@ Bob|Dan|3
Dan|Alice|3
Dan|Bob|3
Dan|Carol|3

-CASE RepeatedAggregateAfterWithGroupKey
-STATEMENT UNWIND [1, 1, 2] AS x WITH x, count(x) AS c RETURN c, count(x) AS n
---- 2
2|1
1|1
-STATEMENT UNWIND [1, 1, 2] AS x WITH x, sum(x) AS s RETURN s, sum(x) AS t
---- 1
2|3
-STATEMENT UNWIND [1, 1, 2] AS x WITH x, count(DISTINCT x) AS c RETURN c, count(DISTINCT x) AS n
---- 1
1|2
-STATEMENT UNWIND [1, 1, 2] AS x WITH x, count(x) AS c RETURN c, count(x) AS n, count(x) + 1 AS m
---- 2
2|1|2
1|1|2
-STATEMENT UNWIND [1, 1, 2] AS x WITH x, count(x) + x AS c RETURN x, c, count(x) + x AS d
---- 2
1|3|2
2|3|3
-STATEMENT UNWIND [1, 1, 2] AS x WITH x, count(x) AS c WHERE count(x) > 1 RETURN x, c
---- 1
1|2
-STATEMENT UNWIND [1, 1, 2] AS x WITH x, count(x) AS c RETURN c ORDER BY count(x)
-CHECK_ORDER
---- 2
1
2
-STATEMENT UNWIND [1, 1, 2] AS x WITH x, count(x) AS c RETURN c, all(i IN [1] WHERE i < count(x)) AS a
---- 2
1|False
2|True
Loading