diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp index 3c3fdb90eb..e575abf400 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -3364,6 +3364,7 @@ static ULONG fast_load(thread_db* tdbb, bool error = false; FB_UINT64 count = 0; FB_UINT64 duplicates = 0; + const bool unique = (idx->idx_flags & idx_unique); const bool descending = (idx->idx_flags & idx_descending); const ULONG segments = idx->idx_count; @@ -3437,6 +3438,13 @@ static ULONG fast_load(thread_db* tdbb, IndexNode tempNode; + // Detect the case when set of duplicate keys contains more then one key + // from primary record version. It breaks the unique constraint and must + // be rejected. Note, it is not always could be detected while sorting. + // Set to true when primary record version is found in current set of + // duplicate keys. + bool primarySeen = false; + while (!error) { // Get the next record in sorted order. @@ -3444,7 +3452,7 @@ static ULONG fast_load(thread_db* tdbb, UCHAR* record; scb->get(tdbb, reinterpret_cast(&record)); - if (!record) + if (!record || creation.duplicates) break; index_sort_record* isr = (index_sort_record*) (record + key_length); @@ -3656,8 +3664,21 @@ static ULONG fast_load(thread_db* tdbb, // check if this is a duplicate node duplicate = (!newNode.length && prefix == leafKey->key_length); + const bool isPrimary = !(isr->isr_flags & ISR_secondary); if (duplicate && (count > 1)) + { ++duplicates; + if (unique && primarySeen && isPrimary && !(isr->isr_flags & ISR_null)) + { + creation.duplicates++; + creation.dup_recno = isr->isr_record_number; + } + + if (isPrimary) + primarySeen = true; + } + else + primarySeen = isPrimary; // Update the length of the page. bucket->btr_length = pointer - (UCHAR*) bucket; diff --git a/src/jrd/btr.h b/src/jrd/btr.h index 7ea5630b20..baaeb37a71 100644 --- a/src/jrd/btr.h +++ b/src/jrd/btr.h @@ -254,6 +254,8 @@ struct IndexCreation jrd_tra* transaction; USHORT key_length; Firebird::AutoPtr sort; + SINT64 dup_recno; + SLONG duplicates; }; // Class used to report any index related errors diff --git a/src/jrd/idx.cpp b/src/jrd/idx.cpp index 1ee040fc48..37b46e7328 100644 --- a/src/jrd/idx.cpp +++ b/src/jrd/idx.cpp @@ -70,14 +70,6 @@ using namespace Jrd; using namespace Ods; using namespace Firebird; -// Data to be passed to index fast load duplicates routine - -struct index_fast_load -{ - SINT64 ifl_dup_recno; - SLONG ifl_duplicates; - USHORT ifl_key_length; -}; static idx_e check_duplicates(thread_db*, Record*, index_desc*, index_insertion*, jrd_rel*); static idx_e check_foreign_key(thread_db*, Record*, jrd_rel*, jrd_tra*, index_desc*, IndexErrorContext&); @@ -301,6 +293,8 @@ void IDX_create_index(thread_db* tdbb, creation.relation = relation; creation.transaction = transaction; creation.key_length = key_length; + creation.dup_recno = -1; + creation.duplicates = 0; BTR_reserve_slot(tdbb, creation); @@ -310,11 +304,6 @@ void IDX_create_index(thread_db* tdbb, RecordStack stack; const UCHAR pad = isDescending ? -1 : 0; - index_fast_load ifl_data; - ifl_data.ifl_dup_recno = -1; - ifl_data.ifl_duplicates = 0; - ifl_data.ifl_key_length = key_length; - sort_key_def key_desc[2]; // Key sort description key_desc[0].setSkdLength(SKD_bytes, key_length); @@ -328,7 +317,7 @@ void IDX_create_index(thread_db* tdbb, key_desc[1].skd_vary_offset = 0; FPTR_REJECT_DUP_CALLBACK callback = (idx->idx_flags & idx_unique) ? duplicate_key : NULL; - void* callback_arg = (idx->idx_flags & idx_unique) ? &ifl_data : NULL; + void* callback_arg = (idx->idx_flags & idx_unique) ? &creation : NULL; Sort* const scb = FB_NEW_POOL(transaction->tra_sorts.getPool()) Sort(dbb, &transaction->tra_sorts, key_length + sizeof(index_sort_record), @@ -465,7 +454,7 @@ void IDX_create_index(thread_db* tdbb, // try to catch duplicates early - if (ifl_data.ifl_duplicates > 0) + if (creation.duplicates > 0) { do { if (record != gc_record) @@ -502,7 +491,7 @@ void IDX_create_index(thread_db* tdbb, delete record; } - if (ifl_data.ifl_duplicates > 0) + if (creation.duplicates > 0) break; if (--tdbb->tdbb_quantum < 0) @@ -514,20 +503,20 @@ void IDX_create_index(thread_db* tdbb, if (primary.getWindow(tdbb).win_flags & WIN_large_scan) --relation->rel_scan_count; - if (!ifl_data.ifl_duplicates) + if (!creation.duplicates) scb->sort(tdbb); - // ASF: We have a callback accessing ifl_data, so don't join above and below if's. + // ASF: We have a callback accessing "creation", so don't join above and below if's. - if (!ifl_data.ifl_duplicates) + if (!creation.duplicates) BTR_create(tdbb, creation, selectivity); - if (ifl_data.ifl_duplicates > 0) + if (creation.duplicates > 0) { AutoPtr error_record; primary.rpb_record = NULL; - fb_assert(ifl_data.ifl_dup_recno >= 0); - primary.rpb_number.setValue(ifl_data.ifl_dup_recno); + fb_assert(creation.dup_recno >= 0); + primary.rpb_number.setValue(creation.dup_recno); if (DPM_get(tdbb, &primary, LCK_read)) { @@ -1440,15 +1429,15 @@ static bool duplicate_key(const UCHAR* record1, const UCHAR* record2, void* ifl_ * bump a counter. * **************************************/ - index_fast_load* ifl_data = static_cast(ifl_void); - const index_sort_record* rec1 = (index_sort_record*) (record1 + ifl_data->ifl_key_length); - const index_sort_record* rec2 = (index_sort_record*) (record2 + ifl_data->ifl_key_length); + IndexCreation* ifl_data = static_cast(ifl_void); + const index_sort_record* rec1 = (index_sort_record*) (record1 + ifl_data->key_length); + const index_sort_record* rec2 = (index_sort_record*) (record2 + ifl_data->key_length); if (!(rec1->isr_flags & (ISR_secondary | ISR_null)) && !(rec2->isr_flags & (ISR_secondary | ISR_null))) { - if (!ifl_data->ifl_duplicates++) - ifl_data->ifl_dup_recno = rec2->isr_record_number; + if (!ifl_data->duplicates++) + ifl_data->dup_recno = rec2->isr_record_number; } return false;