1#include "duckdb/parser/expression/columnref_expression.hpp"
2#include "duckdb/parser/statement/vacuum_statement.hpp"
3#include "duckdb/planner/binder.hpp"
4#include "duckdb/planner/operator/logical_get.hpp"
5#include "duckdb/planner/operator/logical_projection.hpp"
6#include "duckdb/planner/operator/logical_simple.hpp"
7#include "duckdb/catalog/catalog_entry/table_catalog_entry.hpp"
8
9namespace duckdb {
10
11BoundStatement Binder::Bind(VacuumStatement &stmt) {
12 BoundStatement result;
13
14 unique_ptr<LogicalOperator> root;
15
16 if (stmt.info->has_table) {
17 D_ASSERT(!stmt.info->table);
18 D_ASSERT(stmt.info->column_id_map.empty());
19 auto bound_table = Bind(ref&: *stmt.info->ref);
20 if (bound_table->type != TableReferenceType::BASE_TABLE) {
21 throw InvalidInputException("Can only vacuum/analyze base tables!");
22 }
23 auto ref = unique_ptr_cast<BoundTableRef, BoundBaseTableRef>(src: std::move(bound_table));
24 auto &table = ref->table;
25 stmt.info->table = &table;
26
27 auto &columns = stmt.info->columns;
28 vector<unique_ptr<Expression>> select_list;
29 if (columns.empty()) {
30 // Empty means ALL columns should be vacuumed/analyzed
31 auto &get = ref->get->Cast<LogicalGet>();
32 columns.insert(position: columns.end(), first: get.names.begin(), last: get.names.end());
33 }
34
35 case_insensitive_set_t column_name_set;
36 vector<string> non_generated_column_names;
37 for (auto &col_name : columns) {
38 if (column_name_set.count(x: col_name) > 0) {
39 throw BinderException("Vacuum the same column twice(same name in column name list)");
40 }
41 column_name_set.insert(x: col_name);
42 if (!table.ColumnExists(name: col_name)) {
43 throw BinderException("Column with name \"%s\" does not exist", col_name);
44 }
45 auto &col = table.GetColumn(name: col_name);
46 // ignore generated column
47 if (col.Generated()) {
48 continue;
49 }
50 non_generated_column_names.push_back(x: col_name);
51 ColumnRefExpression colref(col_name, table.name);
52 auto result = bind_context.BindColumn(colref, depth: 0);
53 if (result.HasError()) {
54 throw BinderException(result.error);
55 }
56 select_list.push_back(x: std::move(result.expression));
57 }
58 stmt.info->columns = std::move(non_generated_column_names);
59 if (!select_list.empty()) {
60 auto table_scan = CreatePlan(ref&: *ref);
61 D_ASSERT(table_scan->type == LogicalOperatorType::LOGICAL_GET);
62
63 auto &get = table_scan->Cast<LogicalGet>();
64
65 D_ASSERT(select_list.size() == get.column_ids.size());
66 D_ASSERT(stmt.info->columns.size() == get.column_ids.size());
67 for (idx_t i = 0; i < get.column_ids.size(); i++) {
68 stmt.info->column_id_map[i] =
69 table.GetColumns().LogicalToPhysical(index: LogicalIndex(get.column_ids[i])).index;
70 }
71
72 auto projection = make_uniq<LogicalProjection>(args: GenerateTableIndex(), args: std::move(select_list));
73 projection->children.push_back(x: std::move(table_scan));
74
75 root = std::move(projection);
76 } else {
77 // eg. CREATE TABLE test (x AS (1));
78 // ANALYZE test;
79 // Make it not a SINK so it doesn't have to do anything
80 stmt.info->has_table = false;
81 }
82 }
83 auto vacuum = make_uniq<LogicalSimple>(args: LogicalOperatorType::LOGICAL_VACUUM, args: std::move(stmt.info));
84 if (root) {
85 vacuum->children.push_back(x: std::move(root));
86 }
87
88 result.names = {"Success"};
89 result.types = {LogicalType::BOOLEAN};
90 result.plan = std::move(vacuum);
91 properties.return_type = StatementReturnType::NOTHING;
92 return result;
93}
94
95} // namespace duckdb
96