1 | #include "duckdb/parser/expression/columnref_expression.hpp" |
2 | #include "duckdb/parser/statement/update_statement.hpp" |
3 | #include "duckdb/planner/binder.hpp" |
4 | #include "duckdb/planner/tableref/bound_joinref.hpp" |
5 | #include "duckdb/planner/bound_tableref.hpp" |
6 | #include "duckdb/planner/constraints/bound_check_constraint.hpp" |
7 | #include "duckdb/planner/expression/bound_columnref_expression.hpp" |
8 | #include "duckdb/planner/expression/bound_default_expression.hpp" |
9 | #include "duckdb/planner/expression_binder/update_binder.hpp" |
10 | #include "duckdb/planner/expression_binder/where_binder.hpp" |
11 | #include "duckdb/planner/operator/logical_filter.hpp" |
12 | #include "duckdb/planner/operator/logical_get.hpp" |
13 | #include "duckdb/planner/operator/logical_projection.hpp" |
14 | #include "duckdb/planner/operator/logical_update.hpp" |
15 | #include "duckdb/planner/tableref/bound_basetableref.hpp" |
16 | #include "duckdb/catalog/catalog_entry/duck_table_entry.hpp" |
17 | #include "duckdb/storage/data_table.hpp" |
18 | |
19 | #include <algorithm> |
20 | |
21 | namespace duckdb { |
22 | |
23 | // This creates a LogicalProjection and moves 'root' into it as a child |
24 | // unless there are no expressions to project, in which case it just returns 'root' |
25 | unique_ptr<LogicalOperator> Binder::BindUpdateSet(LogicalOperator &op, unique_ptr<LogicalOperator> root, |
26 | UpdateSetInfo &set_info, TableCatalogEntry &table, |
27 | vector<PhysicalIndex> &columns) { |
28 | auto proj_index = GenerateTableIndex(); |
29 | |
30 | vector<unique_ptr<Expression>> projection_expressions; |
31 | D_ASSERT(set_info.columns.size() == set_info.expressions.size()); |
32 | for (idx_t i = 0; i < set_info.columns.size(); i++) { |
33 | auto &colname = set_info.columns[i]; |
34 | auto &expr = set_info.expressions[i]; |
35 | if (!table.ColumnExists(name: colname)) { |
36 | throw BinderException("Referenced update column %s not found in table!" , colname); |
37 | } |
38 | auto &column = table.GetColumn(name: colname); |
39 | if (column.Generated()) { |
40 | throw BinderException("Cant update column \"%s\" because it is a generated column!" , column.Name()); |
41 | } |
42 | if (std::find(first: columns.begin(), last: columns.end(), val: column.Physical()) != columns.end()) { |
43 | throw BinderException("Multiple assignments to same column \"%s\"" , colname); |
44 | } |
45 | columns.push_back(x: column.Physical()); |
46 | if (expr->type == ExpressionType::VALUE_DEFAULT) { |
47 | op.expressions.push_back(x: make_uniq<BoundDefaultExpression>(args: column.Type())); |
48 | } else { |
49 | UpdateBinder binder(*this, context); |
50 | binder.target_type = column.Type(); |
51 | auto bound_expr = binder.Bind(expr); |
52 | PlanSubqueries(expr&: bound_expr, root); |
53 | |
54 | op.expressions.push_back(x: make_uniq<BoundColumnRefExpression>( |
55 | args&: bound_expr->return_type, args: ColumnBinding(proj_index, projection_expressions.size()))); |
56 | projection_expressions.push_back(x: std::move(bound_expr)); |
57 | } |
58 | } |
59 | if (op.type != LogicalOperatorType::LOGICAL_UPDATE && projection_expressions.empty()) { |
60 | return root; |
61 | } |
62 | // now create the projection |
63 | auto proj = make_uniq<LogicalProjection>(args&: proj_index, args: std::move(projection_expressions)); |
64 | proj->AddChild(child: std::move(root)); |
65 | return unique_ptr_cast<LogicalProjection, LogicalOperator>(src: std::move(proj)); |
66 | } |
67 | |
68 | BoundStatement Binder::Bind(UpdateStatement &stmt) { |
69 | BoundStatement result; |
70 | unique_ptr<LogicalOperator> root; |
71 | |
72 | // visit the table reference |
73 | auto bound_table = Bind(ref&: *stmt.table); |
74 | if (bound_table->type != TableReferenceType::BASE_TABLE) { |
75 | throw BinderException("Can only update base table!" ); |
76 | } |
77 | auto &table_binding = bound_table->Cast<BoundBaseTableRef>(); |
78 | auto &table = table_binding.table; |
79 | |
80 | // Add CTEs as bindable |
81 | AddCTEMap(cte_map&: stmt.cte_map); |
82 | |
83 | optional_ptr<LogicalGet> get; |
84 | if (stmt.from_table) { |
85 | auto from_binder = Binder::CreateBinder(context, parent: this); |
86 | BoundJoinRef bound_crossproduct(JoinRefType::CROSS); |
87 | bound_crossproduct.left = std::move(bound_table); |
88 | bound_crossproduct.right = from_binder->Bind(ref&: *stmt.from_table); |
89 | root = CreatePlan(ref&: bound_crossproduct); |
90 | get = &root->children[0]->Cast<LogicalGet>(); |
91 | bind_context.AddContext(other: std::move(from_binder->bind_context)); |
92 | } else { |
93 | root = CreatePlan(ref&: *bound_table); |
94 | get = &root->Cast<LogicalGet>(); |
95 | } |
96 | |
97 | if (!table.temporary) { |
98 | // update of persistent table: not read only! |
99 | properties.modified_databases.insert(x: table.catalog.GetName()); |
100 | } |
101 | auto update = make_uniq<LogicalUpdate>(args&: table); |
102 | |
103 | // set return_chunk boolean early because it needs uses update_is_del_and_insert logic |
104 | if (!stmt.returning_list.empty()) { |
105 | update->return_chunk = true; |
106 | } |
107 | // bind the default values |
108 | BindDefaultValues(columns: table.GetColumns(), bound_defaults&: update->bound_defaults); |
109 | |
110 | // project any additional columns required for the condition/expressions |
111 | if (stmt.set_info->condition) { |
112 | WhereBinder binder(*this, context); |
113 | auto condition = binder.Bind(expr&: stmt.set_info->condition); |
114 | |
115 | PlanSubqueries(expr&: condition, root); |
116 | auto filter = make_uniq<LogicalFilter>(args: std::move(condition)); |
117 | filter->AddChild(child: std::move(root)); |
118 | root = std::move(filter); |
119 | } |
120 | |
121 | D_ASSERT(stmt.set_info); |
122 | D_ASSERT(stmt.set_info->columns.size() == stmt.set_info->expressions.size()); |
123 | |
124 | auto proj_tmp = BindUpdateSet(op&: *update, root: std::move(root), set_info&: *stmt.set_info, table, columns&: update->columns); |
125 | D_ASSERT(proj_tmp->type == LogicalOperatorType::LOGICAL_PROJECTION); |
126 | auto proj = unique_ptr_cast<LogicalOperator, LogicalProjection>(src: std::move(proj_tmp)); |
127 | |
128 | // bind any extra columns necessary for CHECK constraints or indexes |
129 | table.BindUpdateConstraints(get&: *get, proj&: *proj, update&: *update, context); |
130 | |
131 | // finally add the row id column to the projection list |
132 | proj->expressions.push_back(x: make_uniq<BoundColumnRefExpression>( |
133 | args: LogicalType::ROW_TYPE, args: ColumnBinding(get->table_index, get->column_ids.size()))); |
134 | get->column_ids.push_back(x: COLUMN_IDENTIFIER_ROW_ID); |
135 | |
136 | // set the projection as child of the update node and finalize the result |
137 | update->AddChild(child: std::move(proj)); |
138 | |
139 | auto update_table_index = GenerateTableIndex(); |
140 | update->table_index = update_table_index; |
141 | if (!stmt.returning_list.empty()) { |
142 | unique_ptr<LogicalOperator> update_as_logicaloperator = std::move(update); |
143 | |
144 | return BindReturning(returning_list: std::move(stmt.returning_list), table, alias: stmt.table->alias, update_table_index, |
145 | child_operator: std::move(update_as_logicaloperator), result: std::move(result)); |
146 | } |
147 | |
148 | result.names = {"Count" }; |
149 | result.types = {LogicalType::BIGINT}; |
150 | result.plan = std::move(update); |
151 | properties.allow_stream_result = false; |
152 | properties.return_type = StatementReturnType::CHANGED_ROWS; |
153 | return result; |
154 | } |
155 | |
156 | } // namespace duckdb |
157 | |