duckdb 0.6.2-dev1376.0 → 0.6.2-dev1461.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/duckdb/src/common/types/conflict_info.cpp +18 -0
- package/src/duckdb/src/common/types/conflict_manager.cpp +257 -0
- package/src/duckdb/src/execution/column_binding_resolver.cpp +20 -0
- package/src/duckdb/src/execution/expression_executor/execute_operator.cpp +1 -1
- package/src/duckdb/src/execution/index/art/art.cpp +80 -47
- package/src/duckdb/src/execution/operator/persistent/buffered_csv_reader.cpp +2 -2
- package/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp +2 -0
- package/src/duckdb/src/execution/operator/persistent/physical_insert.cpp +247 -8
- package/src/duckdb/src/execution/physical_plan/plan_insert.cpp +17 -6
- package/src/duckdb/src/execution/physical_plan/plan_recursive_cte.cpp +1 -1
- package/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp +1 -1
- package/src/duckdb/src/function/table/version/pragma_version.cpp +2 -2
- package/src/duckdb/src/include/duckdb/common/types/conflict_manager.hpp +71 -0
- package/src/duckdb/src/include/duckdb/common/types/constraint_conflict_info.hpp +27 -0
- package/src/duckdb/src/include/duckdb/common/types/selection_vector.hpp +82 -2
- package/src/duckdb/src/include/duckdb/execution/index/art/art.hpp +13 -5
- package/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp +2 -0
- package/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp +37 -2
- package/src/duckdb/src/include/duckdb/parser/column_list.hpp +2 -0
- package/src/duckdb/src/include/duckdb/parser/statement/insert_statement.hpp +33 -0
- package/src/duckdb/src/include/duckdb/parser/statement/update_statement.hpp +20 -3
- package/src/duckdb/src/include/duckdb/parser/transformer.hpp +21 -0
- package/src/duckdb/src/include/duckdb/planner/binder.hpp +9 -0
- package/src/duckdb/src/include/duckdb/planner/column_binding.hpp +1 -0
- package/src/duckdb/src/include/duckdb/planner/operator/logical_insert.hpp +25 -1
- package/src/duckdb/src/include/duckdb/planner/table_binding.hpp +6 -0
- package/src/duckdb/src/include/duckdb/storage/data_table.hpp +8 -4
- package/src/duckdb/src/include/duckdb/storage/index.hpp +12 -2
- package/src/duckdb/src/include/duckdb/storage/table/row_group_collection.hpp +1 -1
- package/src/duckdb/src/include/duckdb/storage/table/table_index_list.hpp +8 -2
- package/src/duckdb/src/main/relation/update_relation.cpp +5 -3
- package/src/duckdb/src/parser/column_list.cpp +18 -0
- package/src/duckdb/src/parser/parser.cpp +2 -2
- package/src/duckdb/src/parser/statement/insert_statement.cpp +87 -1
- package/src/duckdb/src/parser/statement/update_statement.cpp +21 -6
- package/src/duckdb/src/parser/transform/statement/transform_create_index.cpp +23 -16
- package/src/duckdb/src/parser/transform/statement/transform_insert.cpp +18 -3
- package/src/duckdb/src/parser/transform/statement/transform_update.cpp +15 -7
- package/src/duckdb/src/parser/transform/statement/transform_upsert.cpp +95 -0
- package/src/duckdb/src/planner/binder/query_node/plan_setop.cpp +1 -0
- package/src/duckdb/src/planner/binder/statement/bind_insert.cpp +343 -9
- package/src/duckdb/src/planner/binder/statement/bind_update.cpp +52 -39
- package/src/duckdb/src/planner/binder.cpp +20 -0
- package/src/duckdb/src/planner/logical_operator_visitor.cpp +10 -0
- package/src/duckdb/src/planner/operator/logical_aggregate.cpp +1 -0
- package/src/duckdb/src/planner/operator/logical_insert.cpp +3 -0
- package/src/duckdb/src/planner/table_binding.cpp +38 -17
- package/src/duckdb/src/storage/data_table.cpp +201 -47
- package/src/duckdb/src/storage/table/row_group_collection.cpp +1 -1
- package/src/duckdb/src/storage/table_index_list.cpp +10 -8
- package/src/duckdb/third_party/libpg_query/include/nodes/nodes.hpp +12 -0
- package/src/duckdb/third_party/libpg_query/include/nodes/parsenodes.hpp +1 -0
- package/src/duckdb/third_party/libpg_query/include/parser/gram.hpp +2 -1
- package/src/duckdb/third_party/libpg_query/src_backend_parser_gram.cpp +8758 -8769
- package/src/duckdb/ub_src_common_types.cpp +4 -0
- package/src/duckdb/ub_src_parser_transform_statement.cpp +2 -0
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#include "duckdb/common/types/constraint_conflict_info.hpp"
|
|
2
|
+
#include "duckdb/storage/index.hpp"
|
|
3
|
+
|
|
4
|
+
namespace duckdb {
|
|
5
|
+
|
|
6
|
+
bool ConflictInfo::ConflictTargetMatches(Index &index) const {
|
|
7
|
+
if (only_check_unique && !index.IsUnique()) {
|
|
8
|
+
// We only support checking ON CONFLICT for Unique/Primary key constraints
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (column_ids.empty()) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
// Check whether the column ids match
|
|
15
|
+
return column_ids == index.column_id_set;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
} // namespace duckdb
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#include "duckdb/common/types/conflict_manager.hpp"
|
|
2
|
+
#include "duckdb/storage/index.hpp"
|
|
3
|
+
#include "duckdb/execution/index/art/art.hpp"
|
|
4
|
+
#include "duckdb/common/types/constraint_conflict_info.hpp"
|
|
5
|
+
|
|
6
|
+
namespace duckdb {
|
|
7
|
+
|
|
8
|
+
ConflictManager::ConflictManager(VerifyExistenceType lookup_type, idx_t input_size, ConflictInfo *conflict_info)
|
|
9
|
+
: lookup_type(lookup_type), input_size(input_size), conflict_info(conflict_info), conflicts(input_size, false),
|
|
10
|
+
mode(ConflictManagerMode::THROW) {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
ManagedSelection &ConflictManager::InternalSelection() {
|
|
14
|
+
if (!conflicts.Initialized()) {
|
|
15
|
+
conflicts.Initialize(input_size);
|
|
16
|
+
}
|
|
17
|
+
return conflicts;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const unordered_set<idx_t> &ConflictManager::InternalConflictSet() const {
|
|
21
|
+
D_ASSERT(conflict_set);
|
|
22
|
+
return *conflict_set;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Vector &ConflictManager::InternalRowIds() {
|
|
26
|
+
if (!row_ids) {
|
|
27
|
+
row_ids = make_unique<Vector>(LogicalType::ROW_TYPE, input_size);
|
|
28
|
+
}
|
|
29
|
+
return *row_ids;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Vector &ConflictManager::InternalIntermediate() {
|
|
33
|
+
if (!intermediate_vector) {
|
|
34
|
+
intermediate_vector = make_unique<Vector>(LogicalType::BOOLEAN, true, true, input_size);
|
|
35
|
+
}
|
|
36
|
+
return *intermediate_vector;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ConflictInfo &ConflictManager::GetConflictInfo() const {
|
|
40
|
+
D_ASSERT(conflict_info);
|
|
41
|
+
return *conflict_info;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void ConflictManager::FinishLookup() {
|
|
45
|
+
if (mode == ConflictManagerMode::THROW) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!SingleIndexTarget()) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (conflicts.Count() != 0) {
|
|
52
|
+
// We have recorded conflicts from the one index we're interested in
|
|
53
|
+
// We set this so we don't duplicate the conflicts when there are duplicate indexes
|
|
54
|
+
// that also match our conflict target
|
|
55
|
+
single_index_finished = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void ConflictManager::SetMode(ConflictManagerMode mode) {
|
|
60
|
+
// Only allow SCAN when we have conflict info
|
|
61
|
+
D_ASSERT(mode != ConflictManagerMode::SCAN || conflict_info != nullptr);
|
|
62
|
+
this->mode = mode;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
void ConflictManager::AddToConflictSet(idx_t chunk_index) {
|
|
66
|
+
if (!conflict_set) {
|
|
67
|
+
conflict_set = make_unique<unordered_set<idx_t>>();
|
|
68
|
+
}
|
|
69
|
+
auto &set = *conflict_set;
|
|
70
|
+
set.insert(chunk_index);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
void ConflictManager::AddConflictInternal(idx_t chunk_index, row_t row_id) {
|
|
74
|
+
D_ASSERT(mode == ConflictManagerMode::SCAN);
|
|
75
|
+
|
|
76
|
+
// Only when we should not throw on conflict should we get here
|
|
77
|
+
D_ASSERT(!ShouldThrow(chunk_index));
|
|
78
|
+
AddToConflictSet(chunk_index);
|
|
79
|
+
if (SingleIndexTarget()) {
|
|
80
|
+
// If we have identical indexes, only the conflicts of the first index should be recorded
|
|
81
|
+
// as the other index(es) would produce the exact same conflicts anyways
|
|
82
|
+
if (single_index_finished) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// We can be more efficient because we don't need to merge conflicts of multiple indexes
|
|
87
|
+
auto &selection = InternalSelection();
|
|
88
|
+
auto &row_ids = InternalRowIds();
|
|
89
|
+
auto data = FlatVector::GetData<row_t>(row_ids);
|
|
90
|
+
data[selection.Count()] = row_id;
|
|
91
|
+
selection.Append(chunk_index);
|
|
92
|
+
} else {
|
|
93
|
+
auto &intermediate = InternalIntermediate();
|
|
94
|
+
auto data = FlatVector::GetData<bool>(intermediate);
|
|
95
|
+
// Mark this index in the chunk as producing a conflict
|
|
96
|
+
data[chunk_index] = true;
|
|
97
|
+
if (row_id_map.empty()) {
|
|
98
|
+
row_id_map.resize(input_size);
|
|
99
|
+
}
|
|
100
|
+
row_id_map[chunk_index] = row_id;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
bool ConflictManager::IsConflict(LookupResultType type) {
|
|
105
|
+
switch (type) {
|
|
106
|
+
case LookupResultType::LOOKUP_NULL: {
|
|
107
|
+
if (ShouldIgnoreNulls()) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
// If nulls are not ignored, treat this as a hit instead
|
|
111
|
+
return IsConflict(LookupResultType::LOOKUP_HIT);
|
|
112
|
+
}
|
|
113
|
+
case LookupResultType::LOOKUP_HIT: {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
case LookupResultType::LOOKUP_MISS: {
|
|
117
|
+
// FIXME: If we record a miss as a conflict when the verify type is APPEND_FK, then we can simplify the checks
|
|
118
|
+
// in VerifyForeignKeyConstraint This also means we should not record a hit as a conflict when the verify type
|
|
119
|
+
// is APPEND_FK
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
default: {
|
|
123
|
+
throw NotImplementedException("Type not implemented for LookupResultType");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
bool ConflictManager::AddHit(idx_t chunk_index, row_t row_id) {
|
|
129
|
+
D_ASSERT(chunk_index < input_size);
|
|
130
|
+
// First check if this causes a conflict
|
|
131
|
+
if (!IsConflict(LookupResultType::LOOKUP_HIT)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Then check if we should throw on a conflict
|
|
136
|
+
if (ShouldThrow(chunk_index)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (mode == ConflictManagerMode::THROW) {
|
|
140
|
+
// When our mode is THROW, and the chunk index is part of the previously scanned conflicts
|
|
141
|
+
// then we ignore the conflict instead
|
|
142
|
+
D_ASSERT(!ShouldThrow(chunk_index));
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
D_ASSERT(conflict_info);
|
|
146
|
+
// Because we don't throw, we need to register the conflict
|
|
147
|
+
AddConflictInternal(chunk_index, row_id);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
bool ConflictManager::AddMiss(idx_t chunk_index) {
|
|
152
|
+
D_ASSERT(chunk_index < input_size);
|
|
153
|
+
return IsConflict(LookupResultType::LOOKUP_MISS);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
bool ConflictManager::AddNull(idx_t chunk_index) {
|
|
157
|
+
D_ASSERT(chunk_index < input_size);
|
|
158
|
+
if (!IsConflict(LookupResultType::LOOKUP_NULL)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return AddHit(chunk_index, DConstants::INVALID_INDEX);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
bool ConflictManager::SingleIndexTarget() const {
|
|
165
|
+
D_ASSERT(conflict_info);
|
|
166
|
+
// We are only interested in a specific index
|
|
167
|
+
return !conflict_info->column_ids.empty();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
bool ConflictManager::ShouldThrow(idx_t chunk_index) const {
|
|
171
|
+
if (mode == ConflictManagerMode::SCAN) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
D_ASSERT(mode == ConflictManagerMode::THROW);
|
|
175
|
+
if (conflict_set == nullptr) {
|
|
176
|
+
// No conflicts were scanned, so this conflict is not in the set
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
auto &set = InternalConflictSet();
|
|
180
|
+
if (set.count(chunk_index)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
// None of the scanned conflicts arose from this insert tuple
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
bool ConflictManager::ShouldIgnoreNulls() const {
|
|
188
|
+
switch (lookup_type) {
|
|
189
|
+
case VerifyExistenceType::APPEND:
|
|
190
|
+
return true;
|
|
191
|
+
case VerifyExistenceType::APPEND_FK:
|
|
192
|
+
return false;
|
|
193
|
+
case VerifyExistenceType::DELETE_FK:
|
|
194
|
+
return true;
|
|
195
|
+
default:
|
|
196
|
+
throw InternalException("Type not implemented for VerifyExistenceType");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
Vector &ConflictManager::RowIds() {
|
|
201
|
+
D_ASSERT(finalized);
|
|
202
|
+
return *row_ids;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const ManagedSelection &ConflictManager::Conflicts() const {
|
|
206
|
+
D_ASSERT(finalized);
|
|
207
|
+
return conflicts;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
idx_t ConflictManager::ConflictCount() const {
|
|
211
|
+
return conflicts.Count();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
void ConflictManager::Finalize() {
|
|
215
|
+
D_ASSERT(!finalized);
|
|
216
|
+
if (SingleIndexTarget()) {
|
|
217
|
+
// Selection vector has been directly populated already, no need to finalize
|
|
218
|
+
finalized = true;
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
finalized = true;
|
|
222
|
+
if (!intermediate_vector) {
|
|
223
|
+
// No conflicts were found, we're done
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
auto &intermediate = InternalIntermediate();
|
|
227
|
+
auto data = FlatVector::GetData<bool>(intermediate);
|
|
228
|
+
auto &selection = InternalSelection();
|
|
229
|
+
// Create the selection vector from the encountered conflicts
|
|
230
|
+
for (idx_t i = 0; i < input_size; i++) {
|
|
231
|
+
if (data[i]) {
|
|
232
|
+
selection.Append(i);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Now create the row_ids Vector, aligned with the selection vector
|
|
236
|
+
auto &row_ids = InternalRowIds();
|
|
237
|
+
auto row_id_data = FlatVector::GetData<row_t>(row_ids);
|
|
238
|
+
|
|
239
|
+
for (idx_t i = 0; i < selection.Count(); i++) {
|
|
240
|
+
D_ASSERT(!row_id_map.empty());
|
|
241
|
+
auto index = selection[i];
|
|
242
|
+
D_ASSERT(index < row_id_map.size());
|
|
243
|
+
auto row_id = row_id_map[index];
|
|
244
|
+
row_id_data[i] = row_id;
|
|
245
|
+
}
|
|
246
|
+
intermediate_vector.reset();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
VerifyExistenceType ConflictManager::LookupType() const {
|
|
250
|
+
return this->lookup_type;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
void ConflictManager::SetIndexCount(idx_t count) {
|
|
254
|
+
index_count = count;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
} // namespace duckdb
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "duckdb/planner/operator/logical_comparison_join.hpp"
|
|
4
4
|
#include "duckdb/planner/operator/logical_create_index.hpp"
|
|
5
5
|
#include "duckdb/planner/operator/logical_delim_join.hpp"
|
|
6
|
+
#include "duckdb/planner/operator/logical_insert.hpp"
|
|
6
7
|
|
|
7
8
|
#include "duckdb/planner/expression/bound_columnref_expression.hpp"
|
|
8
9
|
#include "duckdb/planner/expression/bound_reference_expression.hpp"
|
|
@@ -59,6 +60,25 @@ void ColumnBindingResolver::VisitOperator(LogicalOperator &op) {
|
|
|
59
60
|
bindings = op.GetColumnBindings();
|
|
60
61
|
VisitOperatorExpressions(op);
|
|
61
62
|
return;
|
|
63
|
+
} else if (op.type == LogicalOperatorType::LOGICAL_INSERT) {
|
|
64
|
+
//! We want to execute the normal path, but also add a dummy 'excluded' binding if there is a
|
|
65
|
+
// ON CONFLICT DO UPDATE clause
|
|
66
|
+
auto &insert_op = (LogicalInsert &)op;
|
|
67
|
+
if (insert_op.action_type != OnConflictAction::THROW) {
|
|
68
|
+
VisitOperatorChildren(op);
|
|
69
|
+
auto dummy_bindings = LogicalOperator::GenerateColumnBindings(
|
|
70
|
+
insert_op.excluded_table_index, insert_op.table->columns.PhysicalColumnCount());
|
|
71
|
+
bindings.insert(bindings.begin(), dummy_bindings.begin(), dummy_bindings.end());
|
|
72
|
+
if (insert_op.on_conflict_condition) {
|
|
73
|
+
VisitExpression(&insert_op.on_conflict_condition);
|
|
74
|
+
}
|
|
75
|
+
if (insert_op.do_update_condition) {
|
|
76
|
+
VisitExpression(&insert_op.do_update_condition);
|
|
77
|
+
}
|
|
78
|
+
VisitOperatorExpressions(op);
|
|
79
|
+
bindings = op.GetColumnBindings();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
62
82
|
}
|
|
63
83
|
// general case
|
|
64
84
|
// first visit the children of this operator
|
|
@@ -20,7 +20,7 @@ void ExpressionExecutor::Execute(const BoundOperatorExpression &expr, Expression
|
|
|
20
20
|
// IN has n children
|
|
21
21
|
if (expr.type == ExpressionType::COMPARE_IN || expr.type == ExpressionType::COMPARE_NOT_IN) {
|
|
22
22
|
if (expr.children.size() < 2) {
|
|
23
|
-
throw
|
|
23
|
+
throw InvalidInputException("IN needs at least two children");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
Vector left(expr.children[0]->return_type);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include "duckdb/execution/expression_executor.hpp"
|
|
6
6
|
#include "duckdb/storage/arena_allocator.hpp"
|
|
7
7
|
#include "duckdb/execution/index/art/art_key.hpp"
|
|
8
|
+
#include "duckdb/common/types/conflict_manager.hpp"
|
|
8
9
|
|
|
9
10
|
#include <algorithm>
|
|
10
11
|
#include <cstring>
|
|
@@ -375,15 +376,26 @@ bool ART::Append(IndexLock &lock, DataChunk &appended_data, Vector &row_identifi
|
|
|
375
376
|
}
|
|
376
377
|
|
|
377
378
|
void ART::VerifyAppend(DataChunk &chunk) {
|
|
378
|
-
|
|
379
|
+
ConflictManager conflict_manager(VerifyExistenceType::APPEND, chunk.size());
|
|
380
|
+
LookupValues(chunk, conflict_manager);
|
|
379
381
|
}
|
|
380
382
|
|
|
381
|
-
void ART::
|
|
382
|
-
|
|
383
|
+
void ART::VerifyAppend(DataChunk &chunk, ConflictManager &conflict_manager) {
|
|
384
|
+
D_ASSERT(conflict_manager.LookupType() == VerifyExistenceType::APPEND);
|
|
385
|
+
LookupValues(chunk, conflict_manager);
|
|
383
386
|
}
|
|
384
387
|
|
|
385
|
-
void ART::
|
|
386
|
-
|
|
388
|
+
void ART::VerifyAppendForeignKey(DataChunk &chunk) {
|
|
389
|
+
ConflictManager conflict_manager(VerifyExistenceType::APPEND_FK, chunk.size());
|
|
390
|
+
LookupValues(chunk, conflict_manager);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
void ART::VerifyDeleteForeignKey(DataChunk &chunk) {
|
|
394
|
+
if (!IsUnique()) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
ConflictManager conflict_manager(VerifyExistenceType::DELETE_FK, chunk.size());
|
|
398
|
+
LookupValues(chunk, conflict_manager);
|
|
387
399
|
}
|
|
388
400
|
|
|
389
401
|
bool ART::InsertToLeaf(Leaf &leaf, row_t row_id) {
|
|
@@ -789,69 +801,90 @@ bool ART::Scan(Transaction &transaction, DataTable &table, IndexScanState &table
|
|
|
789
801
|
return true;
|
|
790
802
|
}
|
|
791
803
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
804
|
+
string ART::GenerateErrorKeyName(DataChunk &input, idx_t row) {
|
|
805
|
+
// re-executing the expressions is not very fast, but we're going to throw anyways, so we don't care
|
|
806
|
+
DataChunk expression_chunk;
|
|
807
|
+
expression_chunk.Initialize(Allocator::DefaultAllocator(), logical_types);
|
|
808
|
+
ExecuteExpressions(input, expression_chunk);
|
|
809
|
+
|
|
810
|
+
string key_name;
|
|
811
|
+
for (idx_t k = 0; k < expression_chunk.ColumnCount(); k++) {
|
|
812
|
+
if (k > 0) {
|
|
813
|
+
key_name += ", ";
|
|
814
|
+
}
|
|
815
|
+
key_name += unbound_expressions[k]->GetName() + ": " + expression_chunk.data[k].GetValue(row).ToString();
|
|
816
|
+
}
|
|
817
|
+
return key_name;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
string ART::GenerateConstraintErrorMessage(VerifyExistenceType verify_type, const string &key_name) {
|
|
821
|
+
switch (verify_type) {
|
|
822
|
+
case VerifyExistenceType::APPEND: {
|
|
823
|
+
// This node already exists in the tree
|
|
824
|
+
string type = IsPrimary() ? "primary key" : "unique";
|
|
825
|
+
return StringUtil::Format("Duplicate key \"%s\" violates %s constraint", key_name, type);
|
|
826
|
+
}
|
|
827
|
+
case VerifyExistenceType::APPEND_FK: {
|
|
828
|
+
// The node we tried to insert does not exist in the foreign table
|
|
829
|
+
return StringUtil::Format(
|
|
830
|
+
"Violates foreign key constraint because key \"%s\" does not exist in referenced table", key_name);
|
|
831
|
+
}
|
|
832
|
+
case VerifyExistenceType::DELETE_FK: {
|
|
833
|
+
// The node we tried to delete still exists in the foreign table
|
|
834
|
+
return StringUtil::Format("Violates foreign key constraint because key \"%s\" exists in table has foreign key",
|
|
835
|
+
key_name);
|
|
836
|
+
}
|
|
837
|
+
default:
|
|
838
|
+
throw NotImplementedException("Type not implemented for VerifyExistenceType");
|
|
795
839
|
}
|
|
840
|
+
}
|
|
796
841
|
|
|
842
|
+
void ART::LookupValues(DataChunk &input, ConflictManager &conflict_manager) {
|
|
797
843
|
DataChunk expression_chunk;
|
|
798
844
|
expression_chunk.Initialize(Allocator::DefaultAllocator(), logical_types);
|
|
799
845
|
|
|
800
846
|
// unique index, check
|
|
801
847
|
lock_guard<mutex> l(lock);
|
|
848
|
+
|
|
802
849
|
// first resolve the expressions for the index
|
|
803
|
-
ExecuteExpressions(
|
|
850
|
+
ExecuteExpressions(input, expression_chunk);
|
|
804
851
|
|
|
805
852
|
// generate the keys for the given input
|
|
806
853
|
ArenaAllocator arena_allocator(BufferAllocator::Get(db));
|
|
807
854
|
vector<Key> keys(expression_chunk.size());
|
|
808
855
|
GenerateKeys(arena_allocator, expression_chunk, keys);
|
|
809
856
|
|
|
810
|
-
|
|
857
|
+
idx_t found_conflict = DConstants::INVALID_INDEX;
|
|
858
|
+
for (idx_t i = 0; found_conflict == DConstants::INVALID_INDEX && i < input.size(); i++) {
|
|
811
859
|
if (keys[i].Empty()) {
|
|
860
|
+
if (conflict_manager.AddNull(i)) {
|
|
861
|
+
found_conflict = i;
|
|
862
|
+
}
|
|
812
863
|
continue;
|
|
813
864
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
string key_name;
|
|
821
|
-
for (idx_t k = 0; k < expression_chunk.ColumnCount(); k++) {
|
|
822
|
-
if (k > 0) {
|
|
823
|
-
key_name += ", ";
|
|
865
|
+
Leaf *leaf_ptr = Lookup(tree, keys[i], 0);
|
|
866
|
+
if (leaf_ptr == nullptr) {
|
|
867
|
+
if (conflict_manager.AddMiss(i)) {
|
|
868
|
+
found_conflict = i;
|
|
824
869
|
}
|
|
825
|
-
|
|
826
|
-
}
|
|
827
|
-
string exception_msg;
|
|
828
|
-
switch (verify_type) {
|
|
829
|
-
case VerifyExistenceType::APPEND: {
|
|
830
|
-
// node already exists in tree
|
|
831
|
-
string type = IsPrimary() ? "primary key" : "unique";
|
|
832
|
-
exception_msg = "duplicate key \"" + key_name + "\" violates ";
|
|
833
|
-
exception_msg += type + " constraint";
|
|
834
|
-
break;
|
|
835
|
-
}
|
|
836
|
-
case VerifyExistenceType::APPEND_FK: {
|
|
837
|
-
// found node no exists in tree
|
|
838
|
-
exception_msg =
|
|
839
|
-
"violates foreign key constraint because key \"" + key_name + "\" does not exist in referenced table";
|
|
840
|
-
break;
|
|
841
|
-
}
|
|
842
|
-
case VerifyExistenceType::DELETE_FK: {
|
|
843
|
-
// found node exists in tree
|
|
844
|
-
exception_msg =
|
|
845
|
-
"violates foreign key constraint because key \"" + key_name + "\" exist in table has foreign key";
|
|
846
|
-
break;
|
|
847
|
-
}
|
|
870
|
+
continue;
|
|
848
871
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
872
|
+
// When we find a node, we need to update the 'matches' and 'row_ids'
|
|
873
|
+
// NOTE: Leafs can have more than one row_id, but for UNIQUE/PRIMARY KEY they will only have one
|
|
874
|
+
D_ASSERT(leaf_ptr->count == 1);
|
|
875
|
+
auto row_id = leaf_ptr->GetRowId(0);
|
|
876
|
+
if (conflict_manager.AddHit(i, row_id)) {
|
|
877
|
+
found_conflict = i;
|
|
853
878
|
}
|
|
854
879
|
}
|
|
880
|
+
conflict_manager.FinishLookup();
|
|
881
|
+
if (found_conflict == DConstants::INVALID_INDEX) {
|
|
882
|
+
// No conflicts detected
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
auto key_name = GenerateErrorKeyName(input, found_conflict);
|
|
886
|
+
auto exception_msg = GenerateConstraintErrorMessage(conflict_manager.LookupType(), key_name);
|
|
887
|
+
throw ConstraintException(exception_msg);
|
|
855
888
|
}
|
|
856
889
|
|
|
857
890
|
//===--------------------------------------------------------------------===//
|
|
@@ -128,7 +128,7 @@ TextSearchShiftArray::TextSearchShiftArray() {
|
|
|
128
128
|
|
|
129
129
|
TextSearchShiftArray::TextSearchShiftArray(string search_term) : length(search_term.size()) {
|
|
130
130
|
if (length > 255) {
|
|
131
|
-
throw
|
|
131
|
+
throw InvalidInputException("Size of delimiter/quote/escape in CSV reader is limited to 255 bytes");
|
|
132
132
|
}
|
|
133
133
|
// initialize the shifts array
|
|
134
134
|
shifts = unique_ptr<uint8_t[]>(new uint8_t[length * 255]);
|
|
@@ -248,7 +248,7 @@ void BufferedCSVReader::Initialize(const vector<LogicalType> &requested_types) {
|
|
|
248
248
|
if (options.auto_detect) {
|
|
249
249
|
return_types = SniffCSV(requested_types);
|
|
250
250
|
if (return_types.empty()) {
|
|
251
|
-
throw
|
|
251
|
+
throw InvalidInputException("Failed to detect column types from CSV: is the file a valid CSV file?");
|
|
252
252
|
}
|
|
253
253
|
if (cached_chunks.empty()) {
|
|
254
254
|
JumpToBeginning(options.skip_rows, options.header);
|
|
@@ -302,6 +302,8 @@ SinkResultType PhysicalBatchInsert::Sink(ExecutionContext &context, GlobalSinkSt
|
|
|
302
302
|
}
|
|
303
303
|
lstate.current_index = lstate.batch_index;
|
|
304
304
|
table->storage->VerifyAppendConstraints(*table, context.client, lstate.insert_chunk);
|
|
305
|
+
// TODO: call method that returns for which values the constraint check failed
|
|
306
|
+
// so we can support ON CONFLICT in here
|
|
305
307
|
auto new_row_group = lstate.current_collection->Append(lstate.insert_chunk, lstate.current_append_state);
|
|
306
308
|
if (new_row_group) {
|
|
307
309
|
lstate.writer->CheckFlushToDisk(*lstate.current_collection);
|