Skip to content

Commit

Permalink
Merge pull request #2796 from chenxi8611/master
Browse files Browse the repository at this point in the history
Addition of QUALIFY clause
  • Loading branch information
Mytherin committed Dec 21, 2021
2 parents ca857a9 + 8fe8c8c commit f9e6e86
Show file tree
Hide file tree
Showing 22 changed files with 17,351 additions and 16,862 deletions.
2 changes: 2 additions & 0 deletions src/include/duckdb/parser/query_node/select_node.hpp
Expand Up @@ -39,6 +39,8 @@ class SelectNode : public QueryNode {
GroupByNode groups;
//! HAVING clause
unique_ptr<ParsedExpression> having;
//! QUALIFY clause
unique_ptr<ParsedExpression> qualify;
//! Aggregate handling during binding
AggregateHandling aggregate_handling;
//! The SAMPLE clause
Expand Down
32 changes: 32 additions & 0 deletions src/include/duckdb/planner/expression_binder/qualify_binder.hpp
@@ -0,0 +1,32 @@
//===----------------------------------------------------------------------===//
// DuckDB
//
// duckdb/planner/expression_binder/qualify_binder.hpp
//
//
//===----------------------------------------------------------------------===//

#pragma once

#include "duckdb/planner/expression_binder/select_binder.hpp"
#include "duckdb/planner/expression_binder/column_alias_binder.hpp"

namespace duckdb {

//! The QUALIFY binder is responsible for binding an expression within the QUALIFY clause of a SQL statement
class QualifyBinder : public SelectBinder {
public:
QualifyBinder(Binder &binder, ClientContext &context, BoundSelectNode &node, BoundGroupInformation &info,
unordered_map<string, idx_t> &alias_map);

protected:
BindResult BindExpression(unique_ptr<ParsedExpression> *expr_ptr, idx_t depth,
bool root_expression = false) override;

private:
BindResult BindColumnRef(unique_ptr<ParsedExpression> *expr_ptr, idx_t depth, bool root_expression);

ColumnAliasBinder column_alias_binder;
};

} // namespace duckdb
2 changes: 2 additions & 0 deletions src/include/duckdb/planner/query_node/bound_select_node.hpp
Expand Up @@ -45,6 +45,8 @@ class BoundSelectNode : public BoundQueryNode {
BoundGroupByNode groups;
//! HAVING clause
unique_ptr<Expression> having;
//! QUALIFY clause
unique_ptr<Expression> qualify;
//! SAMPLE clause
unique_ptr<SampleOptions> sample_options;

Expand Down
3 changes: 3 additions & 0 deletions src/parser/parsed_expression_iterator.cpp
Expand Up @@ -209,6 +209,9 @@ void ParsedExpressionIterator::EnumerateQueryNodeChildren(
if (sel_node.having) {
callback(sel_node.having);
}
if (sel_node.qualify) {
callback(sel_node.qualify);
}

EnumerateTableRefChildren(*sel_node.from_table.get(), callback);
break;
Expand Down
9 changes: 9 additions & 0 deletions src/parser/query_node/select_node.cpp
Expand Up @@ -45,6 +45,10 @@ bool SelectNode::Equals(const QueryNode *other_p) const {
if (!BaseExpression::Equals(having.get(), other->having.get())) {
return false;
}
// QUALIFY
if (!BaseExpression::Equals(qualify.get(), other->qualify.get())) {
return false;
}
return true;
}

Expand All @@ -61,6 +65,7 @@ unique_ptr<QueryNode> SelectNode::Copy() {
}
result->groups.grouping_sets = groups.grouping_sets;
result->having = having ? having->Copy() : nullptr;
result->qualify = qualify ? qualify->Copy() : nullptr;
result->sample = sample ? sample->Copy() : nullptr;
this->CopyProperties(*result);
return move(result);
Expand All @@ -86,6 +91,8 @@ void SelectNode::Serialize(Serializer &serializer) {
// having / sample
serializer.WriteOptional(having);
serializer.WriteOptional(sample);
// qualify
serializer.WriteOptional(qualify);
}

unique_ptr<QueryNode> SelectNode::Deserialize(Deserializer &source) {
Expand All @@ -110,6 +117,8 @@ unique_ptr<QueryNode> SelectNode::Deserialize(Deserializer &source) {
// having / sample
result->having = source.ReadOptional<ParsedExpression>();
result->sample = source.ReadOptional<SampleOptions>();
// qualify
result->qualify = source.ReadOptional<ParsedExpression>();
return move(result);
}

Expand Down
2 changes: 2 additions & 0 deletions src/parser/transform/statement/transform_select_node.cpp
Expand Up @@ -69,6 +69,8 @@ unique_ptr<QueryNode> Transformer::TransformSelectNode(duckdb_libpgquery::PGSele
TransformGroupBy(stmt->groupClause, result->groups);
// having
result->having = TransformExpression(stmt->havingClause);
// qualify
result->qualify = TransformExpression(stmt->qualifyClause);
// sample
result->sample = TransformSampleOptions(stmt->sampleOptions);
break;
Expand Down
14 changes: 14 additions & 0 deletions src/planner/binder/query_node/bind_select_node.cpp
Expand Up @@ -13,6 +13,7 @@
#include "duckdb/planner/expression_binder/constant_binder.hpp"
#include "duckdb/planner/expression_binder/group_binder.hpp"
#include "duckdb/planner/expression_binder/having_binder.hpp"
#include "duckdb/planner/expression_binder/qualify_binder.hpp"
#include "duckdb/planner/expression_binder/order_binder.hpp"
#include "duckdb/planner/expression_binder/select_binder.hpp"
#include "duckdb/planner/expression_binder/where_binder.hpp"
Expand Down Expand Up @@ -309,6 +310,13 @@ unique_ptr<BoundQueryNode> Binder::BindNode(SelectNode &statement) {
result->having = having_binder.Bind(statement.having);
}

// bind the QUALIFY clause, if any
if (statement.qualify) {
QualifyBinder qualify_binder(*this, context, *result, info, alias_map);
ExpressionBinder::QualifyColumnNames(*this, statement.qualify);
result->qualify = qualify_binder.Bind(statement.qualify);
}

// after that, we bind to the SELECT list
SelectBinder select_binder(*this, context, *result, info);
vector<LogicalType> internal_sql_types;
Expand Down Expand Up @@ -356,6 +364,12 @@ unique_ptr<BoundQueryNode> Binder::BindNode(SelectNode &statement) {
}
}

// QUALIFY clause requires at least one window function to be specified in at least one of the SELECT column list or
// the filter predicate of the QUALIFY clause
if (statement.qualify && result->windows.empty()) {
throw BinderException("at least one window function must appear in the SELECT column or QUALIFY clause");
}

// now that the SELECT list is bound, we set the types of DISTINCT/ORDER BY expressions
BindModifierTypes(*result, internal_sql_types, result->projection_index);
return move(result);
Expand Down
8 changes: 8 additions & 0 deletions src/planner/binder/query_node/plan_select_node.cpp
Expand Up @@ -80,6 +80,14 @@ unique_ptr<LogicalOperator> Binder::CreatePlan(BoundSelectNode &statement) {
root = move(win);
}

if (statement.qualify) {
PlanSubqueries(&statement.qualify, &root);
auto qualify = make_unique<LogicalFilter>(move(statement.qualify));

qualify->AddChild(move(root));
root = move(qualify);
}

if (!statement.unnests.empty()) {
auto unnest = make_unique<LogicalUnnest>(statement.unnest_index);
unnest->expressions = move(statement.unnests);
Expand Down
1 change: 1 addition & 0 deletions src/planner/expression_binder/CMakeLists.txt
Expand Up @@ -8,6 +8,7 @@ add_library_unity(
constant_binder.cpp
group_binder.cpp
having_binder.cpp
qualify_binder.cpp
index_binder.cpp
insert_binder.cpp
order_binder.cpp
Expand Down
50 changes: 50 additions & 0 deletions src/planner/expression_binder/qualify_binder.cpp
@@ -0,0 +1,50 @@
#include "duckdb/planner/expression_binder/qualify_binder.hpp"

#include "duckdb/parser/expression/columnref_expression.hpp"
#include "duckdb/planner/binder.hpp"
#include "duckdb/planner/expression_binder/aggregate_binder.hpp"
#include "duckdb/common/string_util.hpp"
#include "duckdb/planner/query_node/bound_select_node.hpp"

namespace duckdb {

QualifyBinder::QualifyBinder(Binder &binder, ClientContext &context, BoundSelectNode &node, BoundGroupInformation &info,
unordered_map<string, idx_t> &alias_map)
: SelectBinder(binder, context, node, info), column_alias_binder(node, alias_map) {
target_type = LogicalType(LogicalTypeId::BOOLEAN);
}

BindResult QualifyBinder::BindColumnRef(unique_ptr<ParsedExpression> *expr_ptr, idx_t depth, bool root_expression) {
auto &expr = (ColumnRefExpression &)**expr_ptr;
auto result = duckdb::SelectBinder::BindExpression(expr_ptr, depth);
if (!result.HasError()) {
return result;
}

auto alias_result = column_alias_binder.BindAlias(*this, expr, depth, root_expression);
if (!alias_result.HasError()) {
return alias_result;
}

return BindResult(StringUtil::Format("Referenced column %s not found in FROM clause and can't find in alias map.",
expr.ToString()));
}

BindResult QualifyBinder::BindExpression(unique_ptr<ParsedExpression> *expr_ptr, idx_t depth, bool root_expression) {
auto &expr = **expr_ptr;
// check if the expression binds to one of the groups
auto group_index = TryBindGroup(expr, depth);
if (group_index != DConstants::INVALID_INDEX) {
return BindGroup(expr, depth, group_index);
}
switch (expr.expression_class) {
case ExpressionClass::WINDOW:
return BindWindow((WindowExpression &)expr, depth);
case ExpressionClass::COLUMN_REF:
return BindColumnRef(expr_ptr, depth, root_expression);
default:
return duckdb::SelectBinder::BindExpression(expr_ptr, depth);
}
}

} // namespace duckdb
2 changes: 1 addition & 1 deletion src/storage/storage_info.cpp
Expand Up @@ -2,6 +2,6 @@

namespace duckdb {

const uint64_t VERSION_NUMBER = 29;
const uint64_t VERSION_NUMBER = 30;

} // namespace duckdb

0 comments on commit f9e6e86

Please sign in to comment.