duckdb 0.6.2-dev1372.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.
Files changed (57) hide show
  1. package/package.json +1 -1
  2. package/src/duckdb/src/common/types/conflict_info.cpp +18 -0
  3. package/src/duckdb/src/common/types/conflict_manager.cpp +257 -0
  4. package/src/duckdb/src/execution/column_binding_resolver.cpp +20 -0
  5. package/src/duckdb/src/execution/expression_executor/execute_operator.cpp +1 -1
  6. package/src/duckdb/src/execution/index/art/art.cpp +80 -47
  7. package/src/duckdb/src/execution/operator/persistent/buffered_csv_reader.cpp +2 -2
  8. package/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp +2 -0
  9. package/src/duckdb/src/execution/operator/persistent/physical_insert.cpp +247 -8
  10. package/src/duckdb/src/execution/physical_plan/plan_insert.cpp +17 -6
  11. package/src/duckdb/src/execution/physical_plan/plan_recursive_cte.cpp +1 -1
  12. package/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp +1 -1
  13. package/src/duckdb/src/function/table/version/pragma_version.cpp +2 -2
  14. package/src/duckdb/src/include/duckdb/common/types/conflict_manager.hpp +71 -0
  15. package/src/duckdb/src/include/duckdb/common/types/constraint_conflict_info.hpp +27 -0
  16. package/src/duckdb/src/include/duckdb/common/types/selection_vector.hpp +82 -2
  17. package/src/duckdb/src/include/duckdb/execution/index/art/art.hpp +13 -5
  18. package/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp +2 -0
  19. package/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp +37 -2
  20. package/src/duckdb/src/include/duckdb/parser/column_list.hpp +2 -0
  21. package/src/duckdb/src/include/duckdb/parser/statement/insert_statement.hpp +33 -0
  22. package/src/duckdb/src/include/duckdb/parser/statement/update_statement.hpp +20 -3
  23. package/src/duckdb/src/include/duckdb/parser/transformer.hpp +21 -0
  24. package/src/duckdb/src/include/duckdb/planner/binder.hpp +9 -0
  25. package/src/duckdb/src/include/duckdb/planner/column_binding.hpp +1 -0
  26. package/src/duckdb/src/include/duckdb/planner/operator/logical_insert.hpp +25 -1
  27. package/src/duckdb/src/include/duckdb/planner/table_binding.hpp +6 -0
  28. package/src/duckdb/src/include/duckdb/storage/data_table.hpp +8 -4
  29. package/src/duckdb/src/include/duckdb/storage/index.hpp +12 -2
  30. package/src/duckdb/src/include/duckdb/storage/table/row_group_collection.hpp +1 -1
  31. package/src/duckdb/src/include/duckdb/storage/table/table_index_list.hpp +8 -2
  32. package/src/duckdb/src/main/relation/update_relation.cpp +5 -3
  33. package/src/duckdb/src/parser/column_list.cpp +18 -0
  34. package/src/duckdb/src/parser/parser.cpp +2 -2
  35. package/src/duckdb/src/parser/statement/insert_statement.cpp +87 -1
  36. package/src/duckdb/src/parser/statement/update_statement.cpp +21 -6
  37. package/src/duckdb/src/parser/transform/statement/transform_create_index.cpp +23 -16
  38. package/src/duckdb/src/parser/transform/statement/transform_insert.cpp +18 -3
  39. package/src/duckdb/src/parser/transform/statement/transform_update.cpp +15 -7
  40. package/src/duckdb/src/parser/transform/statement/transform_upsert.cpp +95 -0
  41. package/src/duckdb/src/planner/binder/query_node/plan_setop.cpp +1 -0
  42. package/src/duckdb/src/planner/binder/statement/bind_insert.cpp +343 -9
  43. package/src/duckdb/src/planner/binder/statement/bind_update.cpp +52 -39
  44. package/src/duckdb/src/planner/binder.cpp +20 -0
  45. package/src/duckdb/src/planner/logical_operator_visitor.cpp +10 -0
  46. package/src/duckdb/src/planner/operator/logical_aggregate.cpp +1 -0
  47. package/src/duckdb/src/planner/operator/logical_insert.cpp +3 -0
  48. package/src/duckdb/src/planner/table_binding.cpp +38 -17
  49. package/src/duckdb/src/storage/data_table.cpp +201 -47
  50. package/src/duckdb/src/storage/table/row_group_collection.cpp +1 -1
  51. package/src/duckdb/src/storage/table_index_list.cpp +10 -8
  52. package/src/duckdb/third_party/libpg_query/include/nodes/nodes.hpp +12 -0
  53. package/src/duckdb/third_party/libpg_query/include/nodes/parsenodes.hpp +1 -0
  54. package/src/duckdb/third_party/libpg_query/include/parser/gram.hpp +2 -1
  55. package/src/duckdb/third_party/libpg_query/src_backend_parser_gram.cpp +8758 -8769
  56. package/src/duckdb/ub_src_common_types.cpp +4 -0
  57. package/src/duckdb/ub_src_parser_transform_statement.cpp +2 -0
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "duckdb",
3
3
  "main": "./lib/duckdb.js",
4
4
  "types": "./lib/duckdb.d.ts",
5
- "version": "0.6.2-dev1372.0",
5
+ "version": "0.6.2-dev1461.0",
6
6
  "description": "DuckDB node.js API",
7
7
  "gypfile": true,
8
8
  "dependencies": {
@@ -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 Exception("IN needs at least two children");
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
- VerifyExistence(chunk, VerifyExistenceType::APPEND);
379
+ ConflictManager conflict_manager(VerifyExistenceType::APPEND, chunk.size());
380
+ LookupValues(chunk, conflict_manager);
379
381
  }
380
382
 
381
- void ART::VerifyAppendForeignKey(DataChunk &chunk, string *err_msg_ptr) {
382
- VerifyExistence(chunk, VerifyExistenceType::APPEND_FK, err_msg_ptr);
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::VerifyDeleteForeignKey(DataChunk &chunk, string *err_msg_ptr) {
386
- VerifyExistence(chunk, VerifyExistenceType::DELETE_FK, err_msg_ptr);
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
- void ART::VerifyExistence(DataChunk &chunk, VerifyExistenceType verify_type, string *err_msg_ptr) {
793
- if (verify_type != VerifyExistenceType::DELETE_FK && !IsUnique()) {
794
- return;
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(chunk, expression_chunk);
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
- for (idx_t i = 0; i < chunk.size(); i++) {
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
- Node *node_ptr = Lookup(tree, keys[i], 0);
815
- bool throw_exception =
816
- verify_type == VerifyExistenceType::APPEND_FK ? node_ptr == nullptr : node_ptr != nullptr;
817
- if (!throw_exception) {
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
- key_name += unbound_expressions[k]->GetName() + ": " + expression_chunk.data[k].GetValue(i).ToString();
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
- if (err_msg_ptr) {
850
- err_msg_ptr[i] = exception_msg;
851
- } else {
852
- throw ConstraintException(exception_msg);
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 Exception("Size of delimiter/quote/escape in CSV reader is limited to 255 bytes");
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 Exception("Failed to detect column types from CSV: is the file a valid CSV file?");
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);