1#include "duckdb/parser/statement/update_statement.hpp"
2#include "duckdb/planner/binder.hpp"
3#include "duckdb/planner/expression/bound_columnref_expression.hpp"
4#include "duckdb/planner/expression/bound_default_expression.hpp"
5#include "duckdb/planner/expression_binder/update_binder.hpp"
6#include "duckdb/planner/expression_binder/where_binder.hpp"
7#include "duckdb/planner/operator/logical_filter.hpp"
8#include "duckdb/planner/operator/logical_get.hpp"
9#include "duckdb/planner/operator/logical_projection.hpp"
10#include "duckdb/planner/operator/logical_update.hpp"
11#include "duckdb/planner/constraints/bound_check_constraint.hpp"
12#include "duckdb/parser/expression/columnref_expression.hpp"
13#include "duckdb/storage/data_table.hpp"
14#include "duckdb/planner/bound_tableref.hpp"
15
16#include <algorithm>
17
18using namespace std;
19
20namespace duckdb {
21
22static void BindExtraColumns(TableCatalogEntry &table, LogicalGet &get, LogicalProjection &proj, LogicalUpdate &update,
23 unordered_set<column_t> &bound_columns) {
24 if (bound_columns.size() <= 1) {
25 return;
26 }
27 idx_t found_column_count = 0;
28 unordered_set<idx_t> found_columns;
29 for (idx_t i = 0; i < update.columns.size(); i++) {
30 if (bound_columns.find(update.columns[i]) != bound_columns.end()) {
31 // this column is referenced in the CHECK constraint
32 found_column_count++;
33 found_columns.insert(update.columns[i]);
34 }
35 }
36 if (found_column_count > 0 && found_column_count != bound_columns.size()) {
37 // columns in this CHECK constraint were referenced, but not all were part of the UPDATE
38 // add them to the scan and update set
39 for (auto &check_column_id : bound_columns) {
40 if (found_columns.find(check_column_id) != found_columns.end()) {
41 // column is already projected
42 continue;
43 }
44 // column is not projected yet: project it by adding the clause "i=i" to the set of updated columns
45 auto &column = table.columns[check_column_id];
46 auto col_type = GetInternalType(column.type);
47 // first add
48 update.expressions.push_back(make_unique<BoundColumnRefExpression>(
49 col_type, ColumnBinding(proj.table_index, proj.expressions.size())));
50 proj.expressions.push_back(
51 make_unique<BoundColumnRefExpression>(col_type, ColumnBinding(get.table_index, get.column_ids.size())));
52 get.column_ids.push_back(check_column_id);
53 update.columns.push_back(check_column_id);
54 }
55 }
56}
57
58static void BindUpdateConstraints(TableCatalogEntry &table, LogicalGet &get, LogicalProjection &proj,
59 LogicalUpdate &update) {
60 // check the constraints and indexes of the table to see if we need to project any additional columns
61 // we do this for indexes with multiple columns and CHECK constraints in the UPDATE clause
62 // suppose we have a constraint CHECK(i + j < 10); now we need both i and j to check the constraint
63 // if we are only updating one of the two columns we add the other one to the UPDATE set
64 // with a "useless" update (i.e. i=i) so we can verify that the CHECK constraint is not violated
65 for (auto &constraint : table.bound_constraints) {
66 if (constraint->type == ConstraintType::CHECK) {
67 auto &check = *reinterpret_cast<BoundCheckConstraint *>(constraint.get());
68 // check constraint! check if we need to add any extra columns to the UPDATE clause
69 BindExtraColumns(table, get, proj, update, check.bound_columns);
70 }
71 }
72 // for index updates, we do the same, however, for index updates we always turn any update into an insert and a
73 // delete for the insert, we thus need all the columns to be available, hence we check if the update touches any
74 // index columns
75 update.is_index_update = false;
76 for (auto &index : table.storage->info->indexes) {
77 if (index->IndexIsUpdated(update.columns)) {
78 update.is_index_update = true;
79 }
80 }
81 if (update.is_index_update) {
82 // the update updates a column required by an index, push projections for all columns
83 unordered_set<column_t> all_columns;
84 for (idx_t i = 0; i < table.storage->types.size(); i++) {
85 all_columns.insert(i);
86 }
87 BindExtraColumns(table, get, proj, update, all_columns);
88 }
89}
90
91BoundStatement Binder::Bind(UpdateStatement &stmt) {
92 BoundStatement result;
93 // visit the table reference
94 auto bound_table = Bind(*stmt.table);
95 if (bound_table->type != TableReferenceType::BASE_TABLE) {
96 throw BinderException("Can only update base table!");
97 }
98 auto root = CreatePlan(*bound_table);
99 auto &get = (LogicalGet &)*root;
100 assert(root->type == LogicalOperatorType::GET && get.table);
101
102 auto &table = get.table;
103 if (!table->temporary) {
104 // update of persistent table: not read only!
105 this->read_only = false;
106 }
107 auto update = make_unique<LogicalUpdate>(table);
108 // bind the default values
109 BindDefaultValues(table->columns, update->bound_defaults);
110
111 // project any additional columns required for the condition/expressions
112 if (stmt.condition) {
113 WhereBinder binder(*this, context);
114 auto condition = binder.Bind(stmt.condition);
115
116 PlanSubqueries(&condition, &root);
117 auto filter = make_unique<LogicalFilter>(move(condition));
118 filter->AddChild(move(root));
119 root = move(filter);
120 }
121
122 assert(stmt.columns.size() == stmt.expressions.size());
123
124 auto proj_index = GenerateTableIndex();
125 vector<unique_ptr<Expression>> projection_expressions;
126 for (idx_t i = 0; i < stmt.columns.size(); i++) {
127 auto &colname = stmt.columns[i];
128 auto &expr = stmt.expressions[i];
129 if (!table->ColumnExists(colname)) {
130 throw BinderException("Referenced update column %s not found in table!", colname.c_str());
131 }
132 auto &column = table->GetColumn(colname);
133 if (std::find(update->columns.begin(), update->columns.end(), column.oid) != update->columns.end()) {
134 throw BinderException("Multiple assignments to same column \"%s\"", colname.c_str());
135 }
136 update->columns.push_back(column.oid);
137
138 if (expr->type == ExpressionType::VALUE_DEFAULT) {
139 update->expressions.push_back(
140 make_unique<BoundDefaultExpression>(GetInternalType(column.type), column.type));
141 } else {
142 UpdateBinder binder(*this, context);
143 binder.target_type = column.type;
144 auto bound_expr = binder.Bind(expr);
145 PlanSubqueries(&bound_expr, &root);
146
147 update->expressions.push_back(make_unique<BoundColumnRefExpression>(
148 bound_expr->return_type, ColumnBinding(proj_index, projection_expressions.size())));
149 projection_expressions.push_back(move(bound_expr));
150 }
151 }
152 // now create the projection
153 auto proj = make_unique<LogicalProjection>(proj_index, move(projection_expressions));
154 proj->AddChild(move(root));
155
156 // bind any extra columns necessary for CHECK constraints or indexes
157 BindUpdateConstraints(*table, get, *proj, *update);
158
159 // finally add the row id column to the projection list
160 proj->expressions.push_back(
161 make_unique<BoundColumnRefExpression>(ROW_TYPE, ColumnBinding(get.table_index, get.column_ids.size())));
162 get.column_ids.push_back(COLUMN_IDENTIFIER_ROW_ID);
163
164 // set the projection as child of the update node and finalize the result
165 update->AddChild(move(proj));
166
167 result.names = {"Count"};
168 result.types = {SQLType::BIGINT};
169 result.plan = move(update);
170 return result;
171}
172
173} // namespace duckdb
174