diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj index e561ec88c72..1ad5fa58728 100644 --- a/builds/win32/msvc15/engine_static.vcxproj +++ b/builds/win32/msvc15/engine_static.vcxproj @@ -303,6 +303,7 @@ + @@ -613,4 +614,4 @@ - + \ No newline at end of file diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters index f4ebd410f1d..fb4217fb99f 100644 --- a/builds/win32/msvc15/engine_static.vcxproj.filters +++ b/builds/win32/msvc15/engine_static.vcxproj.filters @@ -579,6 +579,7 @@ JRD files + @@ -1185,6 +1186,9 @@ Header files + + Header files + @@ -1239,4 +1243,4 @@ DSQL - + \ No newline at end of file diff --git a/src/burp/backup.epp b/src/burp/backup.epp index d14f3436ffb..abd3e5534ac 100644 --- a/src/burp/backup.epp +++ b/src/burp/backup.epp @@ -1752,6 +1752,9 @@ void put_index( burp_rel* relation) SORTED BY Y.RDB$FIELD_POSITION { PUT_TEXT (att_index_field_name, Y.RDB$FIELD_NAME); + + if (!Y.RDB$CHARACTER_LENGTH.NULL && Y.RDB$CHARACTER_LENGTH) + put_int32 (att_index_field_char_length, Y.RDB$CHARACTER_LENGTH); } END_FOR ON_ERROR diff --git a/src/burp/burp.h b/src/burp/burp.h index d8fc9110e1f..f20f77d48fc 100644 --- a/src/burp/burp.h +++ b/src/burp/burp.h @@ -225,6 +225,9 @@ Version 12: FB6.0. RDB$INDICES.RDB$PACKAGE_NAME and RDB$INDEX_SEGMENTS.RDB$PACKAGE_NAME. + Optional length for index segments: + RDB$INDEX_SEGMENTS.RDB$CHARACTER_LENGTH + Custom aggregate function. */ @@ -387,6 +390,7 @@ enum att_type { att_index_condition_blr, att_index_foreign_key_schema_name, att_index_tablespace_name, + att_index_field_char_length, // length of segment in characters, optional // Data record diff --git a/src/burp/restore.epp b/src/burp/restore.epp index 7c71d9022a2..3d0832a07b4 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -6831,6 +6831,19 @@ bool get_index(BurpGlobals* tdgbl, const burp_rel* relation) SSHORT count = 0, segments = 0; + struct IndexSegment + { + IndexSegment(MemoryPool& pool) : + name(pool) + { + } + + string name; + int length = 0; + }; + + ObjectsArray arrSegments(*getDefaultMemoryPool()); + STORE (REQUEST_HANDLE tdgbl->handles_get_index_req_handle1) X IN RDB$INDICES { @@ -6914,26 +6927,22 @@ bool get_index(BurpGlobals* tdgbl, const burp_rel* relation) break; case att_index_field_name: - STORE (REQUEST_HANDLE tdgbl->handles_get_index_req_handle2) - Y IN RDB$INDEX_SEGMENTS - { - GET_TEXT(Y.RDB$FIELD_NAME); - strcpy(Y.RDB$INDEX_NAME, X.RDB$INDEX_NAME); - Y.RDB$FIELD_POSITION = count++; + { + BASED ON RDB$INDEX_SEGMENTS.RDB$FIELD_NAME segName; + GET_TEXT(segName); + auto& seg = arrSegments.add(); + seg.name = segName; + break; + } - Y.RDB$SCHEMA_NAME.NULL = X.RDB$SCHEMA_NAME.NULL; - if (!X.RDB$SCHEMA_NAME.NULL) - strcpy(Y.RDB$SCHEMA_NAME, X.RDB$SCHEMA_NAME); + case att_index_field_char_length: + { + fb_assert(arrSegments.hasData()); - Y.RDB$PACKAGE_NAME.NULL = X.RDB$PACKAGE_NAME.NULL; - if (!X.RDB$PACKAGE_NAME.NULL) - strcpy(Y.RDB$PACKAGE_NAME, X.RDB$PACKAGE_NAME); - } - END_STORE - ON_ERROR - general_on_error (); - END_ERROR + auto& seg = arrSegments.back(); + seg.length = get_int32(tdgbl); break; + } case att_index_description: X.RDB$DESCRIPTION.NULL = FALSE; @@ -7019,43 +7028,59 @@ bool get_index(BurpGlobals* tdgbl, const burp_rel* relation) } count = 0; - FOR (REQUEST_HANDLE tdgbl->handles_get_index_req_handle3) - RFR IN RDB$RELATION_FIELDS - CROSS IDS IN RDB$INDEX_SEGMENTS - WITH RFR.RDB$SCHEMA_NAME EQUIV NULLIF(relation->rel_name.schema.c_str(), '') AND - RFR.RDB$PACKAGE_NAME EQUIV NULLIF(relation->rel_name.package.c_str(), '') AND - RFR.RDB$RELATION_NAME = relation->rel_name.object.c_str() AND - IDS.RDB$SCHEMA_NAME EQUIV RFR.RDB$SCHEMA_NAME AND - IDS.RDB$PACKAGE_NAME EQUIV RFR.RDB$PACKAGE_NAME AND - IDS.RDB$FIELD_NAME = RFR.RDB$FIELD_NAME AND - IDS.RDB$INDEX_NAME = index_name + for (const auto& seg : arrSegments) { - count++; - } - END_FOR - ON_ERROR - general_on_error (); - END_ERROR + FOR (REQUEST_HANDLE tdgbl->handles_get_index_req_handle3) + RFR IN RDB$RELATION_FIELDS + WITH RFR.RDB$SCHEMA_NAME EQUIV NULLIF(relation->rel_name.schema.c_str(), '') AND + RFR.RDB$PACKAGE_NAME EQUIV NULLIF(relation->rel_name.package.c_str(), '') AND + RFR.RDB$RELATION_NAME = relation->rel_name.object.c_str() AND + RFR.RDB$FIELD_NAME = seg.name.c_str() + { + count++; + } + END_FOR + ON_ERROR + general_on_error (); + END_ERROR + }; if (count != segments) + return false; + + count = 0; + for (const auto& seg : arrSegments) { - FOR (REQUEST_HANDLE tdgbl->handles_get_index_req_handle4) - IDS IN RDB$INDEX_SEGMENTS - WITH IDS.RDB$SCHEMA_NAME EQUIV NULLIF(relation->rel_name.schema.c_str(), '') AND - IDS.RDB$PACKAGE_NAME EQUIV NULLIF(relation->rel_name.package.c_str(), '') AND - IDS.RDB$INDEX_NAME = index_name + STORE (REQUEST_HANDLE tdgbl->handles_get_index_req_handle2) + Y IN RDB$INDEX_SEGMENTS { - ERASE IDS; - ON_ERROR - general_on_error (); - END_ERROR + strcpy(Y.RDB$FIELD_NAME, seg.name.c_str()); + + strcpy(Y.RDB$INDEX_NAME, X.RDB$INDEX_NAME); + Y.RDB$FIELD_POSITION = count++; + + Y.RDB$SCHEMA_NAME.NULL = X.RDB$SCHEMA_NAME.NULL; + if (!X.RDB$SCHEMA_NAME.NULL) + strcpy(Y.RDB$SCHEMA_NAME, X.RDB$SCHEMA_NAME); + + Y.RDB$PACKAGE_NAME.NULL = X.RDB$PACKAGE_NAME.NULL; + if (!X.RDB$PACKAGE_NAME.NULL) + strcpy(Y.RDB$PACKAGE_NAME, X.RDB$PACKAGE_NAME); + + if (seg.length == 0) + { + Y.RDB$CHARACTER_LENGTH.NULL = TRUE; + } + else + { + Y.RDB$CHARACTER_LENGTH.NULL = FALSE; + Y.RDB$CHARACTER_LENGTH = seg.length; + } } - END_FOR + END_STORE ON_ERROR general_on_error (); - END_ERROR - return false; } } END_STORE diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index b32ec5f42f3..1ab290ea697 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -7873,7 +7873,10 @@ void RelationNode::defineConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlSc if (constraint.index->descending) definition.descending = true; definition.inactive = false; - definition.columns = constraint.columns; + + for (const auto& column : constraint.columns) + definition.segments.add().name = column; + definition.refRelation = constraint.refRelation; definition.refColumns = constraint.refColumns; definition.tableSpace = constraint.tableSpace; @@ -9916,15 +9919,11 @@ void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScrat definition.descending = indexNode->descending; definition.inactive = !indexNode->active; - fb_assert(indexNode->columns); + fb_assert(indexNode->segments && indexNode->segments->hasData()); fb_assert(!indexNode->computed); fb_assert(!indexNode->partial); - for (const auto indexColumn : indexNode->columns->items) - { - MetaName& column = definition.columns.add(); - column = nodeAs(indexColumn)->dsqlName; - } + definition.segments = *indexNode->segments; indexList.push(CreateIndexNode::store(tdbb, indexList.getPool(), transaction, addInlineTableIndexClause->indexNode->name, definition)); @@ -13158,12 +13157,15 @@ void ModifyIndexNode::modify(thread_db* tdbb, jrd_tra* transaction) if (operation != OP_ACTIVE) step2(tdbb, transaction); + else if (deferredValidation) + validateUniqueIndex(tdbb, transaction, concurrently); } string ModifyIndexNode::print(Jrd::NodePrinter& printer) const { NODE_PRINT(printer, indexName); NODE_PRINT(printer, operation); + NODE_PRINT(printer, concurrently); NODE_PRINT(printer, tableSpace); return "ModifyIndexNode"; @@ -13226,7 +13228,7 @@ void ModifyIndexNode::validateUniqueIndex(thread_db* tdbb, jrd_tra* transaction, fb_assert(IDX.RDB$UNIQUE_FLAG & IDX_UNIQUE); - if (!(IDX.RDB$UNIQUE_FLAG & IDX_NOT_VALIDATED)) + if (!(IDX.RDB$UNIQUE_FLAG & IDX_NOT_VALIDATED) && !deferredValidation) status_exception::raise(Arg::Gds(isc_dyn_index_already_validated)); // msg 330: "Index uniqueness already validated" @@ -13235,12 +13237,44 @@ void ModifyIndexNode::validateUniqueIndex(thread_db* tdbb, jrd_tra* transaction, QualifiedName relName(IDX.RDB$RELATION_NAME, IDX.RDB$SCHEMA_NAME); jrd_rel* relation = MetadataCache::getVersioned(tdbb, relName, CacheFlag::AUTOCREATE); - if (relation && IDX_validate_unique(tdbb, relation, idxId)) + if (relation) { - MODIFY IDX - IDX.RDB$UNIQUE_FLAG.NULL = FALSE; - IDX.RDB$UNIQUE_FLAG &= ~IDX_NOT_VALIDATED; - END_MODIFY + FbLocalStatus status; + bool valid = true; + { + AutoSetRestoreFlag flagPageSpace(&tdbb->tdbb_flags, TDBB_use_db_page_space, !relation->isTemporary()); + try + { + IDX_validate_unique(tdbb, transaction, relation, idxId); + } + catch (const Exception& ex) + { + if (!silent) + { + if (operation == OP_ACTIVE) + IDX_mark_index(tdbb, relation, idxId); + throw; + } + + ex.stuffException(&status); + valid = false; + } + } + + const bool validated = !(IDX.RDB$UNIQUE_FLAG & IDX_NOT_VALIDATED); + if (valid != validated) + { + MODIFY IDX + IDX.RDB$UNIQUE_FLAG.NULL = FALSE; + if (valid) + IDX.RDB$UNIQUE_FLAG &= ~IDX_NOT_VALIDATED; + else + IDX.RDB$UNIQUE_FLAG |= IDX_NOT_VALIDATED; + END_MODIFY + } + + if (!valid) + status.raise(); } } END_FOR @@ -13325,20 +13359,20 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) // Msg308: can't create index %s } - index_desc idx; - idx.idx_count = 0; - int key_count = 0; - SET_TDBB(tdbb); Attachment* attachment = tdbb->getAttachment(); Database* dbb = tdbb->getDatabase(); MetaId idxId = dbb->dbb_max_idx; bool active = false; + bool havePartialSegments = false; const bool tempConn = !(tdbb->tdbb_flags & TDBB_use_db_page_space); auto* metaTransaction = tdbb->getAttachment()->getMetaTransaction(tdbb); - idx.idx_flags = 0; - idx.idx_pg_space_id = DB_PAGE_SPACE; + index_desc idx; + MOVE_CLEAR(&idx, sizeof(index_desc)); + idx.idx_pg_space_id = tempConn ? TEMP_PAGE_SPACE : DB_PAGE_SPACE; + + int key_count = 0; // Fetch the information necessary to create the index. request.reset(tdbb, irq_c_index, IRQ_REQUESTS); @@ -13506,11 +13540,14 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) collate = COLLATE_NONE; const TTypeId text_type(CSetId(FLD.RDB$CHARACTER_SET_ID), collate); - idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_itype = - DFW_assign_index_type(tdbb, indexName, gds_cvt_blr_dtype[FLD.RDB$FIELD_TYPE], text_type); + auto& tail = idx.idx_rpt[SEG.RDB$FIELD_POSITION]; + + tail.idx_itype = DFW_assign_index_type(tdbb, indexName, gds_cvt_blr_dtype[FLD.RDB$FIELD_TYPE], text_type); + tail.idx_length = SEG.RDB$CHARACTER_LENGTH; + havePartialSegments = (tail.idx_length != 0); // Initialize selectivity to zero. Otherwise random rubbish makes its way into database - idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_selectivity = 0; + tail.idx_selectivity = 0; } END_FOR } @@ -13568,7 +13605,7 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) if (!IDX_check_master_types(tdbb, idx, relVer, bad_segment)) { ERR_post(Arg::Gds(isc_no_meta_update) << - Arg::Gds(isc_partner_idx_incompat_type) << Arg::Num(bad_segment + 1)); + Arg::Gds(isc_partner_idx_incompat_type) << Arg::Num(bad_segment + 1)); } } } @@ -13582,17 +13619,21 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) fb_assert(idxId <= dbb->dbb_max_idx); idx.idx_id = idxId; + IndexCreation creation(concurrently ? IdxCreate::Concurrently : IdxCreate::ForRollback, + relation, &idx, indexName, transaction); + if ((idx.idx_flags & idx_unique) && (concurrently || havePartialSegments)) + { + creation.checkDuplicates = IdxCheckDups::AfterBuild; + deferredValidation = true; + } + SelectivityList selectivity(*tdbb->getDefaultPool()); - IDX_create_index(tdbb, - concurrently ? IdxCreate::Concurrently : IdxCreate::ForRollback, - relation, &idx, indexName, &idxId, transaction, selectivity); + IDX_create_index(tdbb, creation, &idxId, selectivity); fb_assert(idxId == idx.idx_id); + if (!tempConn) DFW_update_index(indexName, idxId, selectivity, transaction); - - if (concurrently && (idx.idx_flags & idx_unique)) - validateUniqueIndex(tdbb, transaction, true); } else fb_assert(idxId == dbb->dbb_max_idx); @@ -13635,12 +13676,6 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) // Msg308: can't create index %s } - index_desc idx; - MOVE_CLEAR(&idx, sizeof(index_desc)); - idx.idx_flags = 0; - idx.idx_count = 0; - idx.idx_pg_space_id = DB_PAGE_SPACE; - CompilerScratch* csb = nullptr; const auto dbb = tdbb->getDatabase(); @@ -13650,6 +13685,10 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) const bool tempConn = !(tdbb->tdbb_flags & TDBB_use_db_page_space); auto* metaTransaction = tdbb->getAttachment()->getMetaTransaction(tdbb); + index_desc idx; + MOVE_CLEAR(&idx, sizeof(index_desc)); + idx.idx_pg_space_id = tempConn ? TEMP_PAGE_SPACE : DB_PAGE_SPACE; + AutoCacheRequest request(tdbb, irq_c_exp_index, IRQ_REQUESTS); FOR(REQUEST_HANDLE request TRANSACTION_HANDLE metaTransaction) @@ -13713,6 +13752,7 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) DFW_assign_index_type(tdbb, indexName, idx.idx_expression_desc.dsc_dtype, idx.idx_expression_desc.getTextType()); + idx.idx_rpt[0].idx_length = 0; idx.idx_rpt[0].idx_selectivity = 0; } catch (const Exception&) @@ -13770,6 +13810,15 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) protectRelation.lock(); } + IndexCreation creation(concurrently ? IdxCreate::Concurrently : IdxCreate::ForRollback, + relation, & idx, indexName, transaction); + + if ((idx.idx_flags & idx_unique) && concurrently) + { + creation.checkDuplicates = IdxCheckDups::AfterBuild; + deferredValidation = true; + } + SelectivityList selectivity(*tdbb->getDefaultPool()); AutoSave2 currentTransaction(tdbb, &thread_db::getTransaction, &thread_db::setTransaction); @@ -13779,10 +13828,8 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) { fb_assert(idxId <= dbb->dbb_max_idx); idx.idx_id = idxId; - IDX_create_index(tdbb, - concurrently ? IdxCreate::Concurrently : IdxCreate::ForRollback, - relation, &idx, indexName, &idxId, transaction, selectivity); + IDX_create_index(tdbb, creation, &idxId, selectivity); fb_assert(idxId == idx.idx_id); } catch (const Exception&) @@ -13797,9 +13844,6 @@ MetaId StoreIndexNode::createExpression(thread_db* tdbb, jrd_tra* transaction) if (!tempConn) DFW_update_index(indexName, idx.idx_id, selectivity, transaction); - - if (concurrently && (idx.idx_flags & idx_unique)) - validateUniqueIndex(tdbb, transaction, true); } else fb_assert(idxId == dbb->dbb_max_idx); @@ -13892,11 +13936,6 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* { IDX.RDB$UNIQUE_FLAG.NULL = FALSE; IDX.RDB$UNIQUE_FLAG = definition.unique.asBool() ? IDX_UNIQUE : 0; - - if (definition.unique.asBool() && definition.concurrently.asBool()) - { - IDX.RDB$UNIQUE_FLAG |= IDX_NOT_VALIDATED; - } } if (definition.inactive.isAssigned()) @@ -13935,15 +13974,15 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* request2.reset(tdbb, drq_l_lfield, DYN_REQUESTS); - for (FB_SIZE_T i = 0; i < definition.columns.getCount(); ++i) + for (FB_SIZE_T i = 0; i < definition.segments.getCount(); ++i) { for (FB_SIZE_T j = 0; j < i; ++j) { - if (definition.columns[i] == definition.columns[j]) + if (definition.segments[i].name == definition.segments[j].name) { // msg 240 "Field %s cannot be used twice in index %s" status_exception::raise( - Arg::PrivateDyn(240) << definition.columns[i] << IDX.RDB$INDEX_NAME); + Arg::PrivateDyn(240) << definition.segments[i].name << IDX.RDB$INDEX_NAME); } } @@ -13955,11 +13994,12 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* WITH F.RDB$SCHEMA_NAME EQ IDX.RDB$SCHEMA_NAME AND F.RDB$PACKAGE_NAME EQUIV NULLIF(definition.relation.package.c_str(), '') AND F.RDB$RELATION_NAME EQ IDX.RDB$RELATION_NAME AND - F.RDB$FIELD_NAME EQ definition.columns[i].c_str() AND + F.RDB$FIELD_NAME EQ definition.segments[i].name.c_str() AND GF.RDB$SCHEMA_NAME EQ F.RDB$FIELD_SOURCE_SCHEMA_NAME AND GF.RDB$FIELD_NAME EQ F.RDB$FIELD_SOURCE { ULONG length = 0; + const SSHORT segmentLen = definition.segments[i].length; if (GF.RDB$FIELD_TYPE == blr_blob) { @@ -13981,25 +14021,42 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* // Compute the length of the key segment allowing for international // information. Note that we we must convert a // type to an index type in order to compute the length. + + if (!segmentLen) + length = GF.RDB$FIELD_LENGTH; + else if (segmentLen > GF.RDB$CHARACTER_LENGTH) + status_exception::raise(Arg::Gds(isc_random) << "segment length greater than field length "); + else + length = segmentLen * GF.RDB$FIELD_LENGTH / GF.RDB$CHARACTER_LENGTH; + if (!F.RDB$COLLATION_ID.NULL) { length = INTL_key_length(tdbb, INTL_TEXT_TO_INDEX(TTypeId(CSetId(GF.RDB$CHARACTER_SET_ID), CollId(F.RDB$COLLATION_ID))), - GF.RDB$FIELD_LENGTH); + length); } else if (!GF.RDB$COLLATION_ID.NULL) { length = INTL_key_length(tdbb, INTL_TEXT_TO_INDEX(TTypeId(CSetId(GF.RDB$CHARACTER_SET_ID), CollId(GF.RDB$COLLATION_ID))), - GF.RDB$FIELD_LENGTH); + length); } - else - length = GF.RDB$FIELD_LENGTH; } else + { + if (segmentLen) + { + string msg; + msg.printf("attempt to specify a length for non-text field %s in index %s", + definition.segments[i].name.c_str(), idxName.toQuotedString().c_str()); + + status_exception::raise(Arg::Gds(isc_random) << msg); + } + length = sizeof(double); + } if (keyLength) { @@ -14039,15 +14096,13 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* status_exception::raise(Arg::PrivateDyn(118) << idxName.toQuotedString()); } - if (definition.columns.hasData()) + if (definition.segments.hasData()) { request2.reset(tdbb, drq_s_idx_segs, DYN_REQUESTS); SSHORT position = 0; - for (ObjectsArray::const_iterator segment(definition.columns.begin()); - segment != definition.columns.end(); - ++segment) + for (const auto& segment : definition.segments) { STORE(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) X IN RDB$INDEX_SEGMENTS @@ -14059,7 +14114,11 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* strcpy(X.RDB$PACKAGE_NAME, IDX.RDB$PACKAGE_NAME); strcpy(X.RDB$INDEX_NAME, IDX.RDB$INDEX_NAME); - strcpy(X.RDB$FIELD_NAME, segment->c_str()); + strcpy(X.RDB$FIELD_NAME, segment.name.c_str()); + + X.RDB$CHARACTER_LENGTH.NULL = (segment.length > 0 ? FALSE : TRUE); + X.RDB$CHARACTER_LENGTH = segment.length; + X.RDB$FIELD_POSITION = position++; } END_STORE @@ -14080,7 +14139,7 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* // If referring columns count <> referred columns return error. - if (definition.columns.getCount() != definition.refColumns.getCount()) + if (definition.segments.getCount() != definition.refColumns.getCount()) { // msg 133: "Number of referencing columns do not equal number of // referenced columns @@ -14205,7 +14264,7 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* // of columns in referring index. fb_assert(IND.RDB$SEGMENT_COUNT >= 0); - if (definition.columns.getCount() != ULONG(IND.RDB$SEGMENT_COUNT)) + if (definition.segments.getCount() != ULONG(IND.RDB$SEGMENT_COUNT)) { // msg 133: "Number of referencing columns do not equal number of // referenced columns" @@ -14233,7 +14292,7 @@ ModifyIndexNode* CreateIndexNode::store(thread_db* tdbb, MemoryPool& p, jrd_tra* } } - IDX.RDB$SEGMENT_COUNT = SSHORT(definition.columns.getCount()); + IDX.RDB$SEGMENT_COUNT = SSHORT(definition.segments.getCount()); } END_STORE @@ -14279,7 +14338,7 @@ string CreateIndexNode::internalPrint(NodePrinter& printer) const NODE_PRINT(printer, active); NODE_PRINT(printer, concurrently); NODE_PRINT(printer, relation); - NODE_PRINT(printer, columns); + NODE_PRINT(printer, segments); NODE_PRINT(printer, computed); NODE_PRINT(printer, partial); NODE_PRINT(printer, createIfNotExistsOnly); @@ -14330,16 +14389,9 @@ void CreateIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, definition.inactive = !active; definition.concurrently = concurrently; - if (columns) + if (segments && segments->hasData()) { - const NestConst* ptr = columns->items.begin(); - const NestConst* const end = columns->items.end(); - - for (; ptr != end; ++ptr) - { - MetaName& column = definition.columns.add(); - column = nodeAs(*ptr)->dsqlName; - } + definition.segments = *segments; } else if (computed) { @@ -14430,28 +14482,29 @@ void CreateIndexNode::defineLocalTempIndex(thread_db* tdbb, DsqlCompilerScratch* status_exception::raise(Arg::Gds(isc_no_dup) << name.toQuotedString()); } - if (columns->items.getCount() > MAX_INDEX_SEGMENTS) - status_exception::raise(Arg::Gds(isc_idx_key_err) << name.toQuotedString()); - const auto& fields = ltt->hasPendingChanges ? ltt->pendingFields : ltt->fields; - for (const auto& col : columns->items) + if (segments) { - const auto& colName = nodeAs(col)->dsqlName; + if (segments->getCount() > MAX_INDEX_SEGMENTS) + status_exception::raise(Arg::Gds(isc_idx_key_err) << name.toQuotedString()); - auto fieldIt = std::find_if(fields.begin(), fields.end(), - [&colName](const auto& field) { return field.name == colName; }); - - if (fieldIt == fields.end()) + for (const auto& seg : *segments) { - // Column not found in LTT - status_exception::raise( - Arg::Gds(isc_dyn_column_does_not_exist) << colName.c_str() << ltt->name.toQuotedString()); - } + auto fieldIt = std::find_if(fields.begin(), fields.end(), + [&seg](const auto& field) { return field.name == seg.name; }); - // Check if field type is indexable (no blobs or arrays) - if (fieldIt->desc.dsc_dtype == dtype_blob) - status_exception::raise(Arg::Gds(isc_blob_idx_err) << colName.c_str()); + if (fieldIt == fields.end()) + { + // Column not found in LTT + status_exception::raise( + Arg::Gds(isc_dyn_column_does_not_exist) << seg.name.c_str() << ltt->name.toQuotedString()); + } + + // Check if field type is indexable (no blobs or arrays) + if (fieldIt->desc.dsc_dtype == dtype_blob) + status_exception::raise(Arg::Gds(isc_blob_idx_err) << seg.name.c_str()); + } } // Register undo action with the savepoint @@ -14483,8 +14536,11 @@ void CreateIndexNode::defineLocalTempIndex(thread_db* tdbb, DsqlCompilerScratch* newIndex.inactive = !active; newIndex.id = ltt->nextIndexId++; - for (const auto& col : columns->items) - newIndex.columns.add(nodeAs(col)->dsqlName); + if (segments) + { + for (const auto& seg : *segments) + newIndex.segments.add(seg); + } // Post deferred work to create the actual B-tree structure DFW_post_work(transaction, dfw_create_ltt_index, string(name.object.c_str()), name.schema, 0); @@ -14525,7 +14581,6 @@ string AlterIndexNode::internalPrint(NodePrinter& printer) const ModifyIndexNode::print(printer); NODE_PRINT(printer, idxId); - NODE_PRINT(printer, concurrently); return "AlterIndexNode"; } @@ -14675,6 +14730,14 @@ bool AlterIndexNode::step1(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jr fb_assert(ret); } + // When we going to activate unique index, there could be few cases: + // - re-activation is possible (see step2) + // - index was validated: no new validation necessary as index was maintained + // - index was not validated : validation is required + // - re-activation is not possible, index will be build: + // - validation is required and performed by rebuild procedure (storeNode.modify) + deferredValidation = (operation == OP_ACTIVE) && idxId.has_value() && (IDX.RDB$UNIQUE_FLAG & IDX_NOT_VALIDATED); + MODIFY IDX IDX.RDB$INDEX_INACTIVE.NULL = FALSE; IDX.RDB$INDEX_INACTIVE = desiredStatus; @@ -14683,6 +14746,12 @@ bool AlterIndexNode::step1(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jr { IDX.RDB$STATISTICS.NULL = FALSE; IDX.RDB$STATISTICS = -1.0; + + if (IDX.RDB$UNIQUE_FLAG & IDX_UNIQUE) + { + IDX.RDB$UNIQUE_FLAG.NULL = FALSE; + IDX.RDB$UNIQUE_FLAG &= ~IDX_NOT_VALIDATED; + } } END_MODIFY } @@ -14722,6 +14791,12 @@ void AlterIndexNode::step2(thread_db* tdbb, jrd_tra* transaction) IDX.RDB$STATISTICS.NULL = FALSE; IDX.RDB$STATISTICS = 0.0; IDX.RDB$INDEX_ID.NULL = TRUE; + + if (IDX.RDB$UNIQUE_FLAG & IDX_UNIQUE) + { + IDX.RDB$UNIQUE_FLAG.NULL = FALSE; + IDX.RDB$UNIQUE_FLAG &= ~IDX_NOT_VALIDATED; + } END_MODIFY } END_FOR @@ -14736,6 +14811,7 @@ void AlterIndexNode::step2(thread_db* tdbb, jrd_tra* transaction) StoreIndexNode storeNode(indexName, indexRelation, expressionIndex, concurrently); fb_assert(relation->getBasePages()->getIndexRootPage()); storeNode.step2(tdbb, transaction); + setDeferredValidationFrom(storeNode); return; } } diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index f77cb8ef80e..de489dae1b4 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -36,6 +36,7 @@ #include "../common/classes/array.h" #include "../common/classes/ByteChunk.h" #include "../common/classes/TriState.h" +#include "../jrd/IndexSegment.h" #include "../jrd/Relation.h" #include "../jrd/Savepoint.h" #include "../dsql/errd_proto.h" @@ -1984,20 +1985,23 @@ class ModifyIndexNode public: enum OP { OP_ACTIVE, OP_INACTIVE, OP_VALIDATE_UNIQUE, OP_SET_TABLESPACE }; - ModifyIndexNode(const QualifiedName& indexName, Cached::Relation* rel, OP op, bool expression) + ModifyIndexNode(const QualifiedName& indexName, Cached::Relation* rel, OP op, bool expression, bool concurrently) : indexName(indexName), indexRelation(rel), operation(op), - expressionIndex(expression) + expressionIndex(expression), + concurrently(concurrently) { fb_assert(indexRelation); } - ModifyIndexNode(const QualifiedName& indexName, OP op, bool expression) + ModifyIndexNode(const QualifiedName& indexName, OP op, bool expression, bool concurrently) : indexName(indexName), operation(op), - expressionIndex(expression) - { } + expressionIndex(expression), + concurrently(concurrently) + { + } virtual ~ModifyIndexNode() { @@ -2023,10 +2027,17 @@ class ModifyIndexNode static Cached::Relation* getRelByIndex(thread_db* tdbb, const QualifiedName& index, jrd_tra* transaction); virtual void step2(thread_db* tdbb, jrd_tra* transaction) = 0; + void setDeferredValidationFrom(const ModifyIndexNode& other) + { + deferredValidation = other.deferredValidation; + } + QualifiedName indexName; Cached::Relation* indexRelation = nullptr; const OP operation; bool expressionIndex; + bool deferredValidation = false; + bool concurrently; public: MetaName tableSpace; @@ -2036,6 +2047,27 @@ class ModifyIndexNode class CreateIndexNode final : public DdlNode { public: + struct Segment: Jrd::IndexSegment, Printable + { + Segment(MemoryPool& pool) : Jrd::IndexSegment(pool) + {} + + Segment(MemoryPool& pool, const IndexSegment& other) : + Jrd::IndexSegment(pool, other) + {} + + Firebird::string internalPrint(NodePrinter& printer) const override + { + NODE_PRINT(printer, name); + if (length) + NODE_PRINT(printer, length); + return "Segment"; + } + }; + + typedef Firebird::ObjectsArray Segments; + + struct Definition { Definition() @@ -2049,7 +2081,7 @@ class CreateIndexNode final : public DdlNode QualifiedName index; QualifiedName relation; - Firebird::ObjectsArray columns; + Segments segments; Firebird::TriState unique; Firebird::TriState descending; Firebird::TriState inactive; @@ -2102,7 +2134,7 @@ class CreateIndexNode final : public DdlNode bool active = true; bool concurrently = false; NestConst relation; - NestConst columns; + NestConst segments; NestConst computed; NestConst partial; bool createIfNotExistsOnly = false; @@ -2114,8 +2146,7 @@ class StoreIndexNode final : public ModifyIndexNode { public: StoreIndexNode(const QualifiedName& indexName, Cached::Relation* rel, bool expressionIndex, bool concurrently) - : ModifyIndexNode(indexName, rel, OP_ACTIVE, expressionIndex), - concurrently(concurrently) + : ModifyIndexNode(indexName, rel, OP_ACTIVE, expressionIndex, concurrently) { } public: @@ -2124,16 +2155,14 @@ class StoreIndexNode final : public ModifyIndexNode private: MetaId create(thread_db* tdbb, jrd_tra* transaction); MetaId createExpression(thread_db* tdbb, jrd_tra* transaction); - - bool concurrently; }; class AlterIndexNode final : public ModifyIndexNode, public DdlNode { public: - AlterIndexNode(MemoryPool& p, const QualifiedName& name, OP op) - : ModifyIndexNode(name, op, false), + AlterIndexNode(MemoryPool& p, const QualifiedName& name, OP op, bool concurrently) + : ModifyIndexNode(name, op, false, concurrently), DdlNode(p) { } @@ -2171,9 +2200,6 @@ class AlterIndexNode final : public ModifyIndexNode, public DdlNode } std::optional idxId; - -public: - bool concurrently = false; }; @@ -2224,7 +2250,7 @@ class DropIndexNode final : public ModifyIndexNode, public DdlNode { public: DropIndexNode(MemoryPool& p, const QualifiedName& name) - : ModifyIndexNode(name, OP_INACTIVE, false), + : ModifyIndexNode(name, OP_INACTIVE, false, false), DdlNode(p) { } diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index e09b463f64a..5c7d9e4fbd4 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -1827,7 +1827,19 @@ DmlNode* DeclareLocalTableNode::parse(thread_db* tdbb, MemoryPool& pool, Compile PAR_error(csb, Arg::Gds(isc_random) << "Invalid local table index segment count"); for (USHORT i = 0; i < segmentCount; ++i) - index.fieldIds.add(blrReader.getWord()); + { + const auto code = blrReader.getByte(); + if (code == blr_dcl_local_table_index_segment) + index.segments.add({ blrReader.getWord(), 0 }); + else + PAR_error(csb, Arg::Gds(isc_random) << "Invalid local table index segment subcode"); + + if (blrReader.peekByte() == blr_dcl_local_table_index_seg_length) + { + blrReader.getByte(); + index.segments.back().length = blrReader.getWord(); + } + } break; } @@ -1902,13 +1914,23 @@ DmlNode* DeclareLocalTableNode::parse(thread_db* tdbb, MemoryPool& pool, Compile indexNames.add(index.name); - for (const auto fieldId : index.fieldIds) + for (const auto& segment: index.segments) { - if (fieldId >= fieldCount) + if (segment.fieldId >= fieldCount) PAR_error(csb, Arg::Gds(isc_random) << "Invalid local table index field id"); - if (node->format->fmt_desc[fieldId].dsc_dtype == dtype_blob) + const auto fieldType = node->format->fmt_desc[segment.fieldId].dsc_dtype; + if (fieldType == dtype_blob) PAR_error(csb, Arg::Gds(isc_random) << "BLOB fields cannot be indexed in local tables"); + + if (segment.length && fieldType > dtype_any_text) + { + string msg; + msg.printf("Attempt to specify a length for non-text field %s in index %s", + node->fieldNames[segment.fieldId].c_str(), index.name.toQuotedString().c_str()); + + PAR_error(csb, Arg::Gds(isc_random) << msg); + } } } } @@ -2058,10 +2080,10 @@ DeclareLocalTableNode* DeclareLocalTableNode::dsqlPass(DsqlCompilerScratch* dsql ERRD_post(Arg::Gds(isc_no_dup) << indexNode->name.toQuotedString()); } - fb_assert(indexNode->columns); + fb_assert(indexNode->segments && indexNode->segments->hasData()); - if (indexNode->columns->items.getCount() == 0 || - indexNode->columns->items.getCount() > MAX_INDEX_SEGMENTS) + if (indexNode->segments->getCount() == 0 || + indexNode->segments->getCount() > MAX_INDEX_SEGMENTS) { status_exception::raise(Arg::Gds(isc_idx_key_err) << indexNode->name.toQuotedString()); } @@ -2071,14 +2093,13 @@ DeclareLocalTableNode* DeclareLocalTableNode::dsqlPass(DsqlCompilerScratch* dsql index.unique = indexNode->unique; index.descending = indexNode->descending; - for (const auto indexColumn : indexNode->columns->items) + for (const auto& segment : *indexNode->segments) { - const auto fieldName = nodeAs(indexColumn)->dsqlName; const dsql_fld* field = nullptr; for (auto existing = dsqlRelation->rel_fields; existing; existing = existing->fld_next) { - if (existing->fld_name == fieldName) + if (existing->fld_name == segment.name) { field = existing; break; @@ -2088,16 +2109,23 @@ DeclareLocalTableNode* DeclareLocalTableNode::dsqlPass(DsqlCompilerScratch* dsql if (!field) { status_exception::raise( - Arg::Gds(isc_dyn_column_does_not_exist) << fieldName.c_str() << dsqlName.toQuotedString()); + Arg::Gds(isc_dyn_column_does_not_exist) << segment.name.c_str() << dsqlName.toQuotedString()); } if (field->dtype == dtype_blob) - status_exception::raise(Arg::Gds(isc_blob_idx_err) << fieldName.c_str()); + status_exception::raise(Arg::Gds(isc_blob_idx_err) << index.name.c_str()); - if (std::find(index.fieldIds.begin(), index.fieldIds.end(), field->fld_id) != index.fieldIds.end()) - status_exception::raise(Arg::Gds(isc_key_field_err) << indexNode->name.toQuotedString()); + const auto foundSeg = std::find_if(index.segments.begin(), index.segments.end(), + [field](const auto& seg) { return seg.fieldId == field->fld_id; }); - index.fieldIds.add(field->fld_id); + if (foundSeg != index.segments.end()) + { + // msg 240 "Field %s cannot be used twice in index %s" + status_exception::raise( + Arg::PrivateDyn(240) << field->fld_name.c_str() << index.name.toQuotedString()); + } + + index.segments.add({field->fld_id, segment.length}); } break; @@ -2167,10 +2195,19 @@ void DeclareLocalTableNode::genBlr(DsqlCompilerScratch* dsqlScratch) flags |= blr_dcl_local_table_index_descending; dsqlScratch->appendUChar(flags); - dsqlScratch->appendUChar(index.fieldIds.getCount()); + dsqlScratch->appendUChar(index.segments.getCount()); - for (const auto fieldId : index.fieldIds) - dsqlScratch->appendUShort(fieldId); + for (const auto& segment : index.segments) + { + dsqlScratch->appendUChar(blr_dcl_local_table_index_segment); + dsqlScratch->appendUShort(segment.fieldId); + + if (segment.length) + { + dsqlScratch->appendUChar(blr_dcl_local_table_index_seg_length); + dsqlScratch->appendUShort(segment.length); + } + } } dsqlScratch->appendUChar(blr_end); @@ -2316,7 +2353,7 @@ jrd_rel* DeclareLocalTableNode::getRelation(thread_db* tdbb, Request* request) c const auto& index = indexes[i]; auto* idp = permanent->rel_indices.ensurePermanent(tdbb, i); AutoPtr idv(FB_NEW_POOL(pool) IndexVersion(pool, idp)); - idv->setLtt(tdbb, QualifiedName(index.name), index.unique, index.descending, index.fieldIds.getCount()); + idv->setLtt(tdbb, QualifiedName(index.name), index.unique, index.descending, index.segments.getCount()); idp->storeObject(tdbb, idv, 0); idv.release(); } @@ -2335,7 +2372,7 @@ void DeclareLocalTableNode::getIndexDescription(thread_db* tdbb, FB_SIZE_T index const auto& index = indexes[indexId]; idx->idx_id = indexId; - idx->idx_count = index.fieldIds.getCount(); + idx->idx_count = index.segments.getCount(); if (index.unique) idx->idx_flags |= idx_unique; @@ -2343,14 +2380,15 @@ void DeclareLocalTableNode::getIndexDescription(thread_db* tdbb, FB_SIZE_T index if (index.descending) idx->idx_flags |= idx_descending; - for (FB_SIZE_T i = 0; i < index.fieldIds.getCount(); ++i) + for (FB_SIZE_T i = 0; i < index.segments.getCount(); ++i) { - const auto fieldId = index.fieldIds[i]; - const auto& desc = format->fmt_desc[fieldId]; + const auto& segment = index.segments[i]; + const auto& desc = format->fmt_desc[segment.fieldId]; - idx->idx_rpt[i].idx_field = fieldId; + idx->idx_rpt[i].idx_field = segment.fieldId; idx->idx_rpt[i].idx_itype = DFW_assign_index_type(tdbb, QualifiedName(index.name), desc.dsc_dtype, desc.isText() ? desc.getTextType() : ttype_none); + idx->idx_rpt[i].idx_length = segment.length; } } @@ -2402,18 +2440,13 @@ void DeclareLocalTableNode::createFrameIndex(thread_db* tdbb, FB_SIZE_T indexId, PartitionedSort sort(dbb, &transaction->tra_sorts); - IndexCreation creation; - creation.index = &idx; - creation.index_name = QualifiedName(indexName); - creation.relation = relation; - creation.transaction = transaction; + IndexCreation creation(IdxCreate::AtOnce, relation, &idx, QualifiedName(indexName), transaction); creation.sort = &sort; creation.key_desc = keyDesc; creation.key_length = keyLength; creation.nullIndLen = nullIndLen; creation.dup_recno = -1; creation.duplicates.setValue(0); - creation.createMethod = IdxCreate::AtOnce; BTR_reserve_slot(tdbb, creation); diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index ab6bf03221d..4bc82c90e96 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -374,24 +374,30 @@ class DeclareCursorNode final : public TypedNode { public: + struct Segment + { + USHORT fieldId; + SSHORT length; + }; + struct Index { explicit Index(MemoryPool& pool) : name(pool), - fieldIds(pool) + segments(pool) { } Index(MemoryPool& pool, const Index& other) : name(pool, other.name), - fieldIds(pool, other.fieldIds), + segments(pool, other.segments), unique(other.unique), descending(other.descending) { } MetaName name; - Firebird::Array fieldIds; + Firebird::Array segments; bool unique = false; bool descending = false; }; diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index 9d335f9fbde..27b61973207 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -167 shift/reduce conflicts, 9 reduce/reduce conflicts. +167 shift/reduce conflicts, 10 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 667aa5b3989..0a521026ea7 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -905,6 +905,8 @@ using namespace Firebird; Jrd::CreatePackageConstantNode* createPackageConstantNode; Jrd::CreateAlterTablespaceNode* createAlterTablespaceNode; Jrd::DropTablespaceNode* dropTablespaceNode; + Jrd::CreateIndexNode::Segments* indexSegments; + Jrd::CreateIndexNode::Segment* indexSegment; } %include types.y @@ -1948,10 +1950,10 @@ index_definition($createIndexNode) %type index_column_expr() index_column_expr($createIndexNode) - : column_list - { $createIndexNode->columns = $1; } - | column_parens - { $createIndexNode->columns = $1; } + : segment_list + { $createIndexNode->segments = $1; } + | segment_parens + { $createIndexNode->segments = $1; } | computed_by '(' value ')' { $createIndexNode->computed = newNode(); @@ -1960,6 +1962,45 @@ index_column_expr($createIndexNode) } ; + +%type segment_parens +segment_parens + : '(' segment_list ')' { $$ = $2; } + ; + +%type segment_list +segment_list + : single_segment + { + $$ = newNode(); + $$->add(*$1); + } + | segment_list ',' single_segment + { + $1->add(*$3); + $$ = $1; + } + ; + + +%type single_segment +single_segment + : symbol_column_name + { + auto* segment = newNode(); + segment->name = *$1; + $$ = segment; + } + | symbol_column_name '(' pos_short_integer ')' + { + auto* segment = newNode(); + segment->name = *$1; + segment->length = $3; + $$ = segment; + } + ; + + %type index_condition_opt index_condition_opt : /* nothing */ @@ -2490,9 +2531,9 @@ table_clause $$ = $3; } | table_as_query_clause - { + { $$ = $1; - } + } table_attributes_opt($2) { $$ = $2; @@ -2632,12 +2673,12 @@ inline_table_indexes($createRelationNode) %type inline_table_index() inline_table_index($createRelationNode) - : unique_opt order_direction INDEX valid_symbol_name [YYVALID;] column_parens + : unique_opt order_direction INDEX valid_symbol_name [YYVALID;] segment_parens { const auto node = newNode(QualifiedName(*$4)); node->unique = $1; node->descending = $2; - node->columns = $6; + node->segments = $6; auto clause = newNode(node); $createRelationNode->clauses.add(clause); @@ -5251,16 +5292,15 @@ alter_index_clause : symbol_index_name index_active concurrently_opt { const auto operation = ($2 == true) ? AlterIndexNode::OP_ACTIVE : AlterIndexNode::OP_INACTIVE; - $$ = newNode(*$1, operation); - $$->concurrently = $3; + $$ = newNode(*$1, operation, $3); } | symbol_index_name VALIDATE UNIQUE { - $$ = newNode(*$1, AlterIndexNode::OP_VALIDATE_UNIQUE); + $$ = newNode(*$1, AlterIndexNode::OP_VALIDATE_UNIQUE, false); } | symbol_index_name SET TABLESPACE to_opt symbol_tablespace_name { - $$ = newNode(*$1, AlterIndexNode::OP_SET_TABLESPACE); + $$ = newNode(*$1, AlterIndexNode::OP_SET_TABLESPACE, false); $$->tableSpace = *$5; } ; diff --git a/src/include/firebird/impl/blr.h b/src/include/firebird/impl/blr.h index cc3b5d70a9e..4e9f75d4145 100644 --- a/src/include/firebird/impl/blr.h +++ b/src/include/firebird/impl/blr.h @@ -467,6 +467,8 @@ #define blr_dcl_local_table_index (unsigned char) 4 #define blr_dcl_local_table_index_unique (unsigned char) 1 #define blr_dcl_local_table_index_descending (unsigned char) 2 +#define blr_dcl_local_table_index_segment (unsigned char) 3 +#define blr_dcl_local_table_index_seg_length (unsigned char) 4 #define blr_local_table_truncate (unsigned char) 219 #define blr_local_table_id (unsigned char) 220 diff --git a/src/isql/isql.epp b/src/isql/isql.epp index d980c9236ae..d78bd44ec55 100644 --- a/src/isql/isql.epp +++ b/src/isql/isql.epp @@ -1779,6 +1779,13 @@ SLONG ISQL_get_index_segments(TEXT* segs, segs += fieldNameStrLen + 2; } } + + const auto ilen = SEG.RDB$CHARACTER_LENGTH; + if (ilen) + { + const auto l = snprintf(segs, segs_end - segs + 1, "(%d)", ilen); + segs += l; + } } END_FOR ON_ERROR diff --git a/src/jrd/IndexSegment.h b/src/jrd/IndexSegment.h new file mode 100644 index 00000000000..5ad9822584e --- /dev/null +++ b/src/jrd/IndexSegment.h @@ -0,0 +1,52 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Vladyslav Khorsun + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2026 Vladyslav Khorsun + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef JRD_INDEX_SEGMENT_H +#define JRD_INDEX_SEGMENT_H + +#include "firebird.h" +#include "../jrd/MetaName.h" +#include "../common/classes/alloc.h" + +namespace Jrd +{ + +struct IndexSegment +{ + IndexSegment(MemoryPool& pool) : + name(pool) + { + } + + IndexSegment(MemoryPool& pool, const IndexSegment& other) : + name(pool, other.name), + length(other.length) + { + } + + MetaName name; + SSHORT length = 0; // Length in characters, if zero - equal to the field's length +}; + +} // namespace Jrd + +#endif // JRD_INDEX_SEGMENT_H diff --git a/src/jrd/LocalTemporaryTable.h b/src/jrd/LocalTemporaryTable.h index f40ea57e7e2..104d95a62d5 100644 --- a/src/jrd/LocalTemporaryTable.h +++ b/src/jrd/LocalTemporaryTable.h @@ -25,6 +25,7 @@ #include "firebird.h" #include "../jrd/constants.h" +#include "../jrd/IndexSegment.h" #include "../jrd/MetaName.h" #include "../jrd/QualifiedName.h" #include "../common/dsc.h" @@ -82,19 +83,19 @@ namespace Jrd public: explicit Index(MemoryPool& pool) : name(pool), - columns(pool) + segments(pool) { } Index(MemoryPool& pool, const QualifiedName& aName) : name(pool, aName), - columns(pool) + segments(pool) { } Index(MemoryPool& pool, const Index& other) : name(pool, other.name), - columns(pool, other.columns), + segments(pool, other.segments), unique(other.unique), descending(other.descending), inactive(other.inactive), @@ -104,7 +105,7 @@ namespace Jrd public: QualifiedName name; - Firebird::ObjectsArray columns; + Firebird::ObjectsArray segments; bool unique = false; bool descending = false; bool inactive = false; diff --git a/src/jrd/Relation.cpp b/src/jrd/Relation.cpp index 777157ce003..01384b1bc0f 100644 --- a/src/jrd/Relation.cpp +++ b/src/jrd/Relation.cpp @@ -582,8 +582,11 @@ RelationPages* RelationPermanent::getTempPages(thread_db* tdbb, RelationPages::I } idx.idx_root = 0; + + IndexCreation creation(IdxCreate::AtOnce, relation, &idx, idx_name, idxTran); SelectivityList selectivity(*pool); - IDX_create_index(tdbb, IdxCreate::AtOnce, relation, &idx, idx_name, NULL, idxTran, selectivity); + + IDX_create_index(tdbb, creation, nullptr, selectivity); #ifdef VIO_DEBUG VIO_trace(DEBUG_WRITES, @@ -743,30 +746,30 @@ bool RelationPermanent::fillPagesSnapshot(PagesSnapshot& snapshot, const bool at for (const auto relPages : *rel_pages_inst) { - if (!attachmentOnly) - { - snapshot.add(relPages); - relPages->addRef(); - } - else if ((rel_flags & REL_temp_conn) && + if (!attachmentOnly) + { + snapshot.add(relPages); + relPages->addRef(); + } + else if ((rel_flags & REL_temp_conn) && PAG_attachment_id(snapshot.spt_tdbb) == relPages->getInstanceId()) - { - snapshot.add(relPages); - relPages->addRef(); - } - else if (rel_flags & REL_temp_tran) - { - const jrd_tra* tran = snapshot.spt_tdbb->getAttachment()->att_transactions; - for (; tran; tran = tran->tra_next) { - if (tran->tra_number == relPages->getInstanceId()) + snapshot.add(relPages); + relPages->addRef(); + } + else if (rel_flags & REL_temp_tran) + { + const jrd_tra* tran = snapshot.spt_tdbb->getAttachment()->att_transactions; + for (; tran; tran = tran->tra_next) { - snapshot.add(relPages); - relPages->addRef(); + if (tran->tra_number == relPages->getInstanceId()) + { + snapshot.add(relPages); + relPages->addRef(); + } } } } - } return true; } @@ -1146,7 +1149,7 @@ Triggers& TrigArray::operator[](int t) case TRIGGER_POST_ERASE: return postErase; - } + } fb_assert(false); fatal_exception::raise("Invalid trigger type"); diff --git a/src/jrd/btr.cpp b/src/jrd/btr.cpp index b00b1694af0..e6cf6dda355 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -83,8 +83,6 @@ namespace { constexpr unsigned MAX_LEVELS = 16; - constexpr MetaId SKIP_IN_PROGRESS = ~MetaId(0); - constexpr size_t OVERSIZE = (MAX_PAGE_SIZE + BTN_PAGE_SIZE + MAX_KEY + sizeof(SLONG) - 1) / sizeof(SLONG); // END_LEVEL (~0) is choosen here as a unknown/none value, because it's @@ -248,7 +246,7 @@ namespace static ULONG add_node(thread_db*, WIN*, index_insertion*, temporary_key*, RecordNumber*, ULONG*, ULONG*); static void compress(thread_db*, const dsc*, const SSHORT scale, temporary_key*, - USHORT, bool, USHORT, bool*); + USHORT, SSHORT, bool, USHORT, bool*); static USHORT compress_root(thread_db*, index_root_page*); static void copy_key(const temporary_mini_key*, temporary_mini_key*); static contents delete_node(thread_db*, WIN*, UCHAR*); @@ -353,7 +351,7 @@ void dumpIndexRoot(...) { } } static bool checkIrtRepeat(thread_db* tdbb, const index_root_page::irt_repeat* irt_desc, - RelationPermanent* relation, WIN* window, MetaId indexId); + RelationPermanent* relation, WIN* window, bool skipInProgress); static ModifyIrtRepeatValue modifyIrtRepeat(thread_db* tdbb, index_root_page::irt_repeat* irt_desc, RelationPermanent* relation, WIN* window, MetaId indexId, bool fromActivate = false); @@ -808,7 +806,7 @@ idx_e IndexKey::compose(Record* record, bool skipNewFormat) m_key.key_flags |= key_empty; - compress(m_tdbb, desc_ptr, 0, &m_key, tail->idx_itype, descending, m_keyType, nullptr); + compress(m_tdbb, desc_ptr, 0, &m_key, tail->idx_itype, tail->idx_length, descending, m_keyType, nullptr); } else { @@ -847,7 +845,7 @@ idx_e IndexKey::compose(Record* record, bool skipNewFormat) m_key.key_nulls |= 1 << n; } - compress(m_tdbb, desc_ptr, 0, &temp, tail->idx_itype, descending, m_keyType, nullptr); + compress(m_tdbb, desc_ptr, 0, &temp, tail->idx_itype, tail->idx_length, descending, m_keyType, nullptr); const UCHAR* q = temp.key_data; for (USHORT l = temp.key_length; l; --l, --stuff_count) @@ -1514,6 +1512,7 @@ bool BTR_delete_index(thread_db* tdbb, WIN* window, MetaId id, bool withCleanup) delete_tree(tdbb, relation_id, id, rootPage.value(), prior); // clear RDB$INDEX_ID and related stuff + // hvlad: should it be skipped if index instance is temporary? if (withCleanup) DropIndexNode::clearId(tdbb, relation_id, id); @@ -1802,6 +1801,7 @@ bool BTR_description(thread_db* tdbb, Cached::Relation* relation, const index_ro const irtd* key_descriptor = (irtd*) ptr; idx_desc->idx_field = key_descriptor->irtd_field; idx_desc->idx_itype = key_descriptor->irtd_itype; + idx_desc->idx_length = key_descriptor->irtd_length; idx_desc->idx_selectivity = key_descriptor->irtd_selectivity; ptr += sizeof(irtd); } @@ -2445,74 +2445,85 @@ USHORT BTR_key_length(thread_db* tdbb, jrd_rel* relation, index_desc* idx) // hvlad: in ODS11 key of descending index can be prefixed with // one byte value. See comments in compress const SLONG prefix = (idx->idx_flags & idx_descending) ? 1 : 0; - const Format* format = relation->currentFormat(tdbb); - index_desc::idx_repeat* tail = idx->idx_rpt; - SLONG length; - - // If there is only a single key, the computation is straightforward. - if (idx->idx_count == 1) + auto getSegmentLength = [&](const index_desc::idx_repeat* tail, const dsc* desc) -> SLONG { switch (tail->idx_itype) { case idx_numeric: - length = sizeof(double); - break; + return sizeof(double); case idx_sql_time: case idx_sql_time_tz: - length = sizeof(ULONG); - break; + return sizeof(ULONG); case idx_sql_date: - length = sizeof(SLONG); - break; + return sizeof(SLONG); case idx_timestamp: case idx_timestamp_tz: - length = sizeof(SINT64); - break; + return sizeof(SINT64); case idx_numeric2: - length = INT64_KEY_LENGTH; - break; + return INT64_KEY_LENGTH; case idx_boolean: - length = sizeof(UCHAR); - break; + return sizeof(UCHAR); case idx_decimal: - length = Decimal128::getIndexKeyLength(); - break; + return Decimal128::getIndexKeyLength(); case idx_bcd: - length = Int128::getIndexKeyLength(); - break; + return Int128::getIndexKeyLength(); default: - if (idx->idx_flags & idx_expression) { - fb_assert(idx->idx_expression_node); - length = idx->idx_expression_desc.dsc_length; - if (idx->idx_expression_desc.dsc_dtype == dtype_varying) + SLONG length; + + if (tail->idx_length == 0) { - length = length - sizeof(SSHORT); + length = desc->dsc_length; + if (desc->dsc_dtype == dtype_varying) + length -= sizeof(SSHORT); } - } - else - { - length = format->fmt_desc[tail->idx_field].dsc_length; - if (format->fmt_desc[tail->idx_field].dsc_dtype == dtype_varying) { - length = length - sizeof(SSHORT); + else if (tail->idx_itype == idx_metadata) + { + const auto cs = INTL_charset_lookup(tdbb, CS_METADATA); + length = tail->idx_length * cs->maxBytesPerChar(); } - } + else if (tail->idx_itype >= idx_first_intl_string) + { + const auto tt = INTL_texttype_lookup(tdbb, INTL_INDEX_TO_TEXT(tail->idx_itype)); + length = tail->idx_length * tt->getCharSet()->maxBytesPerChar(); + } + else + { + fb_assert(tail->idx_itype == idx_string || tail->idx_itype == idx_byte_array); + length = tail->idx_length; + } + + if (tail->idx_itype >= idx_first_intl_string) + length = INTL_key_length(tdbb, tail->idx_itype, length); - if (tail->idx_itype >= idx_first_intl_string) { - length = INTL_key_length(tdbb, tail->idx_itype, length); + return length; } - break; } + }; + + index_desc::idx_repeat* tail = idx->idx_rpt; + SLONG length; + + // If there is only a single key, the computation is straightforward. + if (idx->idx_count == 1) + { + if (idx->idx_flags & idx_expression) + { + fb_assert(idx->idx_expression_node); + length = getSegmentLength(tail, &idx->idx_expression_desc); + } + else + length = getSegmentLength(tail, &format->fmt_desc[tail->idx_field]); return length + prefix; } @@ -2522,42 +2533,7 @@ USHORT BTR_key_length(thread_db* tdbb, jrd_rel* relation, index_desc* idx) for (USHORT n = 0; n < idx->idx_count; n++, tail++) { - switch (tail->idx_itype) - { - case idx_numeric: - length = sizeof(double); - break; - case idx_sql_time: - case idx_sql_time_tz: - length = sizeof(ULONG); - break; - case idx_sql_date: - length = sizeof(ULONG); - break; - case idx_timestamp: - case idx_timestamp_tz: - length = sizeof(SINT64); - break; - case idx_numeric2: - length = INT64_KEY_LENGTH; - break; - case idx_boolean: - length = sizeof(UCHAR); - break; - case idx_decimal: - length = Decimal128::getIndexKeyLength(); - break; - case idx_bcd: - length = Int128::getIndexKeyLength(); - break; - default: - length = format->fmt_desc[tail->idx_field].dsc_length; - if (format->fmt_desc[tail->idx_field].dsc_dtype == dtype_varying) - length -= sizeof(SSHORT); - if (tail->idx_itype >= idx_first_intl_string) - length = INTL_key_length(tdbb, tail->idx_itype, length); - break; - } + length = getSegmentLength(tail, &format->fmt_desc[tail->idx_field]); key_length += ((length + prefix + STUFF_COUNT - 1) / STUFF_COUNT) * (STUFF_COUNT + 1); } @@ -2734,7 +2710,7 @@ idx_e BTR_make_key(thread_db* tdbb, key->key_flags |= key_empty; - compress(tdbb, desc, scale ? *scale : 0, key, tail->idx_itype, descending, keyType, forceInclude); + compress(tdbb, desc, scale ? *scale : 0, key, tail->idx_itype, tail->idx_length, descending, keyType, forceInclude); if (fuzzy && (key->key_flags & key_empty)) { @@ -2767,7 +2743,7 @@ idx_e BTR_make_key(thread_db* tdbb, temp.key_flags |= key_empty; - compress(tdbb, desc, scale ? *scale++ : 0, &temp, tail->idx_itype, descending, + compress(tdbb, desc, scale ? *scale++ : 0, &temp, tail->idx_itype, tail->idx_length, descending, (n == count - 1 ? keyType : ((idx->idx_flags & idx_unique) ? INTL_KEY_UNIQUE : INTL_KEY_SORT)), forceInclude); @@ -2893,7 +2869,7 @@ void BTR_make_null_key(thread_db* tdbb, const index_desc* idx, temporary_key* ke // If the index is a single segment index, don't sweat the compound stuff if ((idx->idx_count == 1) || (idx->idx_flags & idx_expression)) { - compress(tdbb, nullptr, 0, key, tail->idx_itype, descending, INTL_KEY_SORT, nullptr); + compress(tdbb, nullptr, 0, key, tail->idx_itype, tail->idx_length, descending, INTL_KEY_SORT, nullptr); } else { @@ -2907,7 +2883,7 @@ void BTR_make_null_key(thread_db* tdbb, const index_desc* idx, temporary_key* ke for (; stuff_count; --stuff_count) *p++ = 0; - compress(tdbb, nullptr, 0, &temp, tail->idx_itype, descending, INTL_KEY_SORT, nullptr); + compress(tdbb, nullptr, 0, &temp, tail->idx_itype, tail->idx_length, descending, INTL_KEY_SORT, nullptr); const UCHAR* q = temp.key_data; for (USHORT l = temp.key_length; l; --l, --stuff_count) @@ -3020,9 +2996,11 @@ void BTR_merge_index(thread_db* tdbb, const IndexCreation& creation, const PageN // checks is there a need to modify index descriptor // if yes - we release index root window +// window is released if function returns true and skipInProgress == false +// if skipInProgress == true, window is not released static bool checkIrtRepeat(thread_db* tdbb, const index_root_page::irt_repeat* irt_desc, - RelationPermanent* relation, WIN* window, MetaId indexId) + RelationPermanent* relation, WIN* window, bool skipInProgress) { const TraNumber irtTrans = irt_desc->getTransaction(); const TraNumber oldestActive = tdbb->getDatabase()->dbb_oldest_active; @@ -3036,7 +3014,7 @@ static bool checkIrtRepeat(thread_db* tdbb, const index_root_page::irt_repeat* i case irt_in_progress: case irt_concurrently: - if (indexId == SKIP_IN_PROGRESS) + if (skipInProgress) return false; // index creation - check transaction state to know what to do @@ -3063,7 +3041,7 @@ static bool checkIrtRepeat(thread_db* tdbb, const index_root_page::irt_repeat* i default: return false; } - if (indexId != SKIP_IN_PROGRESS) + if (!skipInProgress) CCH_RELEASE(tdbb, window); break; @@ -3071,7 +3049,7 @@ static bool checkIrtRepeat(thread_db* tdbb, const index_root_page::irt_repeat* i // drop index when OAT > irtTrans if (oldestActive <= irtTrans) return false; - if (indexId != SKIP_IN_PROGRESS) + if (!skipInProgress) CCH_RELEASE(tdbb, window); break; @@ -3108,7 +3086,8 @@ static ModifyIrtRepeatValue modifyIrtRepeat(thread_db* tdbb, index_root_page::ir CCH_RELEASE(tdbb, window); { auto transaction = tdbb->getTransaction(); - const auto state = transaction && (transaction->tra_number == irtTrans) ? tra_active : + const auto state = transaction && (transaction->tra_number == irtTrans) ? + (fromActivate ? tra_us : tra_active) : TRA_wait(tdbb, transaction, irtTrans, tra_probe); auto root = (index_root_page*) CCH_FETCH(tdbb, window, LCK_write, pag_root); @@ -3272,7 +3251,7 @@ bool BTR_next_index(thread_db* tdbb, jrd_rel* relation, jrd_tra* transaction, in { const auto irt_desc = getIndexSlot(tdbb, root, id); - if (checkIrtRepeat(tdbb, irt_desc, relation->getPermanent(), window, id)) + if (checkIrtRepeat(tdbb, irt_desc, relation->getPermanent(), window, false)) { const auto root_write = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, window); const auto irt_write = &root_write->irt_rpt[id]; @@ -3335,7 +3314,7 @@ bool BTR_cleanup_index(thread_db* tdbb, const QualifiedName& relName, jrd_tra* t const index_root_page::irt_repeat* irt_desc = root->irt_rpt + id; bool rc = false; - if (checkIrtRepeat(tdbb, irt_desc, relation->getPermanent(), &window, id)) + if (checkIrtRepeat(tdbb, irt_desc, relation->getPermanent(), &window, false)) { auto root_write = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); auto irt_write = &root_write->irt_rpt[id]; @@ -3531,16 +3510,25 @@ void BTR_reserve_slot(thread_db* tdbb, IndexCreation& creation) for (MetaId id = 0; id < root->irt_count; ++id) { - index_root_page::irt_repeat* root_idx = &root->irt_rpt[id]; + index_root_page::irt_repeat* root_idx = root->irt_rpt + id; if (root_idx->isUsed()) { - if (checkIrtRepeat(tdbb, root_idx, rel, &window, retry == 1 ? id : SKIP_IN_PROGRESS)) + const bool skipInProgress = (idx->idx_id == id && use_idx_id) ? false : (retry != 1); + + if (checkIrtRepeat(tdbb, root_idx, rel, &window, skipInProgress)) { + if (!skipInProgress) + { + root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); + root_idx = root->irt_rpt + id; + } fb_assert(window.win_bdb); switch(modifyIrtRepeat(tdbb, root_idx, rel, &window, id)) { case ModifyIrtRepeatValue::Skip: + CCH_MARK(tdbb, &window); + [[fallthrough]]; case ModifyIrtRepeatValue::Modified: fb_assert(window.win_bdb); // buffer could be released and refetched by modifyIrtRepeat @@ -3552,10 +3540,12 @@ void BTR_reserve_slot(thread_db* tdbb, IndexCreation& creation) case ModifyIrtRepeatValue::Deleted: root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); fb_assert(window.win_bdb); - root_idx = &root->irt_rpt[id]; + root_idx = root->irt_rpt + id; + CCH_MARK(tdbb, &window); break; } } + fb_assert(window.win_bdb); } if (root_idx->isUsed()) @@ -4042,16 +4032,9 @@ void BTR_copy_index(thread_db* tdbb, jrd_rel* relation, MetaId indexId, ULONG pa const USHORT nullIndLen = !(idx.idx_flags & idx_descending) && (idx.idx_count == 1) ? 1 : 0; const USHORT key_length = ROUNDUP(BTR_key_length(tdbb, relation, &idx) + nullIndLen, sizeof(SINT64)); - IndexCreation creation; - creation.index = &idx; - creation.relation = relation; - creation.transaction = transaction; - creation.sort = nullptr; + IndexCreation creation(IdxCreate::ForRollback, relation, &idx, {}, transaction); creation.key_length = key_length; creation.nullIndLen = nullIndLen; - creation.dup_recno = -1; - creation.duplicates.setValue(0); - creation.createMethod = IdxCreate::ForRollback; BTR_reserve_slot(tdbb, creation); const auto backIndexId = idx.idx_id; @@ -4233,6 +4216,7 @@ static void compress(thread_db* tdbb, const SSHORT matchScale, temporary_key* key, USHORT itype, + SSHORT ilength, bool descending, USHORT key_type, bool* forceInclude) { @@ -4330,6 +4314,35 @@ static void compress(thread_db* tdbb, } else if (itype >= idx_first_intl_string || itype == idx_metadata) { + MoveBuffer substr; + dsc subDesc; + const dsc* pDesc = desc; + + if (ilength) + { + // Get first ilength characters from a full string value + + const USHORT fromLen = desc->dsc_length - (desc->dsc_dtype == dtype_varying ? sizeof(USHORT) : 0); + const UCHAR* from = desc->dsc_address + (desc->dsc_dtype == dtype_varying ? sizeof(USHORT) : 0); + + CharSet* cs = INTL_charset_lookup(tdbb, desc->getCharSet()); + const auto fromChars = cs->length(fromLen, from, false); + if (fromChars > ilength) + { + const auto maxLen = ilength * cs->maxBytesPerChar(); + + subDesc.makeText(maxLen, desc->getTextType()); + subDesc.dsc_address = substr.getBuffer(maxLen); + + subDesc.dsc_length = cs->substring(fromLen, from, maxLen, subDesc.dsc_address, 0, ilength); + + pDesc = &subDesc; + } + + if (forceInclude && (ilength <= fromChars)) + *forceInclude = true; + } + DSC to; // convert to an international byte array @@ -4340,10 +4353,20 @@ static void compress(thread_db* tdbb, to.setTextType(ttype_sort_key); to.dsc_length = MIN(MAX_COLUMN_SIZE, MAX_KEY * 4); ptr = to.dsc_address = reinterpret_cast(buffer.vary_string); - multiKeyLength = length = INTL_string_to_key(tdbb, itype, desc, &to, key_type); + multiKeyLength = length = INTL_string_to_key(tdbb, itype, pDesc, &to, key_type); } else + { length = MOV_get_string(tdbb, desc, &ptr, &buffer, MAX_KEY); + + if (ilength && ilength <= length) + { + length = ilength; + + if (forceInclude) + *forceInclude = true; + } + } } if (key_type == INTL_KEY_MULTI_STARTING && multiKeyLength != 0) @@ -5541,7 +5564,8 @@ static ULONG fast_load(thread_db* tdbb, if (duplicate && (count > 1)) { ++duplicates; - if (unique && primarySeen && isPrimary && !(isr->isr_flags & ISR_null) && !creation.isConcurrently()) + if (unique && primarySeen && isPrimary && !(isr->isr_flags & ISR_null) && + (creation.checkDuplicates == IdxCheckDups::WhenBuild)) { ++creation.duplicates; creation.dup_recno = isr->isr_record_number; @@ -5925,7 +5949,17 @@ static ULONG fast_load(thread_db* tdbb, ERR_punt(); if (!relation->isTemporary()) + { CCH_flush(tdbb, FLUSH_ALL, 0); + } + else if (window->win_page.getPageSpaceID() == DB_PAGE_SPACE) + { + // Write the root page only to avoid possible circle in dependencies. + // Note, this is the only page in tree. + CCH_FETCH(tdbb, window, LCK_EX, pag_index); + CCH_MARK_MUST_WRITE(tdbb, window); + CCH_RELEASE(tdbb, window); + } // Calculate selectivity, also per segment when newer ODS selectivity.grow(segments); diff --git a/src/jrd/btr.h b/src/jrd/btr.h index 714be71838e..d687cd9e5ac 100644 --- a/src/jrd/btr.h +++ b/src/jrd/btr.h @@ -108,6 +108,7 @@ struct index_desc { USHORT idx_field; // field id USHORT idx_itype; // data of field in index + USHORT idx_length; // data length in characters, if set (non zero) float idx_selectivity; // segment selectivity } idx_rpt[MAX_INDEX_SEGMENTS]; }; @@ -340,20 +341,30 @@ class BtrPageGCLock : public Lock class IdxCreationHelper; enum class IdxCreate { AtOnce, ForRollback, Concurrently }; +enum class IdxCheckDups { WhenBuild, AfterBuild }; struct IndexCreation { + IndexCreation(IdxCreate aMethod, jrd_rel* aRelation, index_desc* aIdx, const QualifiedName& aNname, jrd_tra* aTran) : + createMethod(aMethod), + relation(aRelation), + index(aIdx), + index_name(aNname), + transaction(aTran) + {} + + IdxCreate createMethod; + IdxCheckDups checkDuplicates = IdxCheckDups::WhenBuild; jrd_rel* relation; index_desc* index; QualifiedName index_name; jrd_tra* transaction; - PartitionedSort* sort; - sort_key_def* key_desc; - USHORT key_length; - USHORT nullIndLen; - SINT64 dup_recno; + PartitionedSort* sort = nullptr; + sort_key_def* key_desc = nullptr; + USHORT key_length = 0; + USHORT nullIndLen = 0; + SINT64 dup_recno = -1; Firebird::AtomicCounter duplicates; - IdxCreate createMethod; bool isConcurrently() const { diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp index 9c68b5be9b1..30f042f3c3c 100644 --- a/src/jrd/dfw.epp +++ b/src/jrd/dfw.epp @@ -4942,7 +4942,7 @@ static bool create_ltt_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, memset(&idx, 0, sizeof(idx)); idx.idx_id = lttIndex->id; - idx.idx_count = lttIndex->columns.getCount(); + idx.idx_count = lttIndex->segments.getCount(); idx.idx_flags = 0; if (lttIndex->unique) @@ -4953,14 +4953,14 @@ static bool create_ltt_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, // Build the index key descriptors from LTT field info int keyPos = 0; - for (const auto& colName : lttIndex->columns) + for (const auto& segment : lttIndex->segments) { // Find the field in the LTT bool found = false; for (const auto& field : ltt->fields) { - if (field.name == colName) + if (field.name == segment.name) { idx.idx_rpt[keyPos].idx_field = field.id; @@ -4970,7 +4970,9 @@ static bool create_ltt_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, DTYPE_IS_TEXT(field.desc.dsc_dtype) ? TTypeId(field.charSetId.value_or(CS_NONE), field.collationId.value_or(COLLATE_NONE)) : ttype_none); + idx.idx_rpt[keyPos].idx_itype = idxType; + idx.idx_rpt[keyPos].idx_length = segment.length; found = true; break; @@ -4989,9 +4991,10 @@ static bool create_ltt_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, { // scope AutoSetRestoreFlag dbPageSpace(&tdbb->tdbb_flags, TDBB_use_db_page_space, true); + IndexCreation creation(IdxCreate::AtOnce, relation, &idx, work->getQualifiedName(), transaction); SelectivityList selectivity(*tdbb->getDefaultPool()); - IDX_create_index(tdbb, IdxCreate::AtOnce, relation, &idx, work->getQualifiedName(), - <tIndex->id, transaction, selectivity); + + IDX_create_index(tdbb, creation, <tIndex->id, selectivity); } // Update index on the current instance if it exists @@ -5001,10 +5004,11 @@ static bool create_ltt_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, if (relation->getPages(tdbb, 0, false)) { + IndexCreation creation(IdxCreate::AtOnce, relation, &idx, work->getQualifiedName(), transaction); SelectivityList selectivity(*tdbb->getDefaultPool()); + idx.idx_root = 0; - IDX_create_index(tdbb, IdxCreate::AtOnce, relation, &idx, work->getQualifiedName(), - <tIndex->id, transaction, selectivity); + IDX_create_index(tdbb, creation, <tIndex->id, selectivity); } } @@ -5015,7 +5019,7 @@ static bool create_ltt_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, auto& attPool = *attachment->att_pool; AutoPtr idv = FB_NEW_POOL(attPool) IndexVersion(attPool, idp); idv->setLtt(tdbb, lttIndex->name, lttIndex->unique, lttIndex->descending, - lttIndex->columns.getCount(), lttIndex->inactive); + lttIndex->segments.getCount(), lttIndex->inactive); idp->storeObject(tdbb, idv, 0); idv.release(); diff --git a/src/jrd/idx.cpp b/src/jrd/idx.cpp index afe5718a63c..cf1442cbf45 100644 --- a/src/jrd/idx.cpp +++ b/src/jrd/idx.cpp @@ -441,7 +441,7 @@ class IndexCreateTask : public Task m_condBlob.clear(); int workers = 1; - if (att->att_parallel_workers > 0) + if ((att->att_parallel_workers > 0) && !creation->relation->isTemporary()) workers = att->att_parallel_workers; // Classic in single-user shutdown mode can't create additional worker attachments @@ -602,7 +602,7 @@ class IndexCreateTask : public Task FPTR_REJECT_DUP_CALLBACK callback = NULL; void* callback_arg = NULL; - if ((m_idx.idx_flags & idx_unique) && !creation->isConcurrently()) + if ((m_idx.idx_flags & idx_unique) && (creation->checkDuplicates == IdxCheckDups::WhenBuild)) { callback = duplicate_key; callback_arg = creation; @@ -1058,12 +1058,8 @@ int IndexCreateTask::getMaxWorkers() void IDX_create_index(thread_db* tdbb, - IdxCreate createMethod, - jrd_rel* relation, - index_desc* idx, - const QualifiedName& index_name, + IndexCreation& creation, USHORT* index_id, - jrd_tra* transaction, SelectivityList& selectivity) { /************************************** @@ -1079,6 +1075,8 @@ void IDX_create_index(thread_db* tdbb, SET_TDBB(tdbb); Database* dbb = tdbb->getDatabase(); + auto relation = creation.relation; + if (relation->getExtFile()) { ERR_post(Arg::Gds(isc_no_meta_update) << @@ -1090,8 +1088,10 @@ void IDX_create_index(thread_db* tdbb, Arg::Gds(isc_wish_list)); } + auto transaction = creation.transaction; fb_assert(transaction); + auto idx = creation.index; const bool isDescending = (idx->idx_flags & idx_descending); const bool isForeign = (idx->idx_flags & idx_foreign); @@ -1110,27 +1110,21 @@ void IDX_create_index(thread_db* tdbb, if (key_length >= dbb->getMaxIndexKeyLength()) { ERR_post(Arg::Gds(isc_no_meta_update) << - Arg::Gds(isc_keytoobig) << index_name.toQuotedString()); + Arg::Gds(isc_keytoobig) << creation.index_name.toQuotedString()); } if (isForeign) { - if (!MET_lookup_partner(tdbb, getPermanent(relation), idx, index_name)) { + if (!MET_lookup_partner(tdbb, getPermanent(relation), idx, creation.index_name)) { BUGCHECK(173); // msg 173 referenced index description not found } } - IndexCreation creation; - creation.index = idx; - creation.index_name = index_name; - creation.relation = relation; - creation.transaction = transaction; - creation.sort = NULL; + creation.sort = nullptr; creation.key_length = key_length; creation.nullIndLen = nullIndLen; creation.dup_recno = -1; creation.duplicates.setValue(0); - creation.createMethod = createMethod; IdxCreationHelper helper(tdbb, creation); @@ -1205,7 +1199,7 @@ void IDX_create_index(thread_db* tdbb, } } - IndexErrorContext context(relation, idx, index_name); + IndexErrorContext context(relation, idx, creation.index_name); context.raise(tdbb, idx_e_duplicate, error_record); } @@ -1233,10 +1227,10 @@ bool IDX_mark_index(thread_db* tdbb, jrd_rel* relation, MetaId id) return false; WIN window(rootPage.value()); - index_root_page* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); + index_root_page* root = BTR_fetch_root_for_update(FB_FUNCTION, tdbb, &window); BTR_mark_index_for_delete(tdbb, relation->getPermanent(), id, &window, root, 0); - return true; + return true; } void IDX_mark_temp(thread_db* tdbb, RelationPermanent* relation, MetaId id, Attachment* current, TraNumber tran) @@ -1851,12 +1845,8 @@ void IDX_store(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) } -bool IDX_validate_unique(thread_db* tdbb, jrd_rel* relation, MetaId id) +bool IDX_validate_unique(thread_db* tdbb, jrd_tra* transaction, jrd_rel* relation, MetaId id) { - Database* dbb = tdbb->getDatabase(); - Attachment* attachment = tdbb->getAttachment(); - jrd_tra* const transaction = tdbb->getTransaction(); - // We don't need to check active versions because they are already checked by // corresponding INSERT or UPDATE. // Thus RC RV transaction is ideal here - it skips active records and returns @@ -2180,7 +2170,7 @@ static idx_e check_foreign_key(thread_db* tdbb, else { result = check_partner_index(tdbb, relation, record, transaction, idx, - partnerRelation, index_id); + partnerRelation, index_id); } if (result) diff --git a/src/jrd/idx_proto.h b/src/jrd/idx_proto.h index cc892b9f2af..930dddfd609 100644 --- a/src/jrd/idx_proto.h +++ b/src/jrd/idx_proto.h @@ -39,8 +39,7 @@ namespace Jrd } bool IDX_check_master_types(Jrd::thread_db*, Jrd::index_desc&, Jrd::jrd_rel*, int&); -void IDX_create_index(Jrd::thread_db*, Jrd::IdxCreate createMethod, Jrd::jrd_rel*, Jrd::index_desc*, - const Jrd::QualifiedName&, USHORT*, Jrd::jrd_tra*, Jrd::SelectivityList&); +void IDX_create_index(Jrd::thread_db*, Jrd::IndexCreation&, USHORT*, Jrd::SelectivityList&); bool IDX_mark_index(Jrd::thread_db*, Jrd::jrd_rel*, MetaId); void IDX_mark_temp(Jrd::thread_db* tdbb, Jrd::RelationPermanent* relation, MetaId id, Jrd::Attachment* current, TraNumber tran); @@ -51,7 +50,7 @@ void IDX_modify(Jrd::thread_db*, Jrd::record_param*, Jrd::record_param*, Jrd::jr void IDX_modify_check_constraints(Jrd::thread_db*, Jrd::record_param*, Jrd::record_param*, Jrd::jrd_tra*); void IDX_store(Jrd::thread_db*, Jrd::record_param*, Jrd::jrd_tra*); void IDX_modify_flag_uk_modified(Jrd::thread_db*, Jrd::record_param*, Jrd::record_param*, Jrd::jrd_tra*); -bool IDX_validate_unique(Jrd::thread_db* tdbb, Jrd::jrd_rel* relation, MetaId id); +bool IDX_validate_unique(Jrd::thread_db* tdbb, Jrd::jrd_tra*, Jrd::jrd_rel* relation, MetaId id); #endif // JRD_IDX_PROTO_H diff --git a/src/jrd/ini.epp b/src/jrd/ini.epp index b989c3c64d5..2af8bbd7094 100644 --- a/src/jrd/ini.epp +++ b/src/jrd/ini.epp @@ -595,7 +595,7 @@ namespace } } } - } + } private: AutoRequest handle; @@ -1763,6 +1763,7 @@ static void store_indices(thread_db* tdbb, USHORT odsVersion) PAD(field->fld_name, Y.RDB$FIELD_NAME); tail->idx_field = segment->ini_idx_rfld_id; tail->idx_itype = segment->ini_idx_type; + tail->idx_length = 0; tail->idx_selectivity = 0; } END_STORE @@ -1771,10 +1772,11 @@ static void store_indices(thread_db* tdbb, USHORT odsVersion) idx.idx_count = index->ini_idx_segment_count; idx.idx_flags = index->ini_idx_flags; idx.idx_pg_space_id = DB_PAGE_SPACE; + + IndexCreation creation(IdxCreate::AtOnce, relVers, &idx, indexName, transaction); SelectivityList selectivity(*tdbb->getDefaultPool()); - IDX_create_index(tdbb, IdxCreate::AtOnce, relVers, &idx, indexName, NULL, - transaction, selectivity); + IDX_create_index(tdbb, creation, nullptr, selectivity); X.RDB$INDEX_ID = idx.idx_id + 1; diff --git a/src/jrd/ods.h b/src/jrd/ods.h index 60308b21160..32316d54a58 100644 --- a/src/jrd/ods.h +++ b/src/jrd/ods.h @@ -471,13 +471,15 @@ struct irtd { USHORT irtd_field; USHORT irtd_itype; + USHORT irtd_length; // length in characters, if not zero float irtd_selectivity; }; -static_assert(sizeof(struct irtd) == 8, "struct irtd size mismatch"); +static_assert(sizeof(struct irtd) == 12, "struct irtd size mismatch"); static_assert(offsetof(struct irtd, irtd_field) == 0, "irtd_field offset mismatch"); static_assert(offsetof(struct irtd, irtd_itype) == 2, "irtd_itype offset mismatch"); -static_assert(offsetof(struct irtd, irtd_selectivity) == 4, "irtd_selectivity offset mismatch"); +static_assert(offsetof(struct irtd, irtd_length) == 4, "irtd_length offset mismatch"); +static_assert(offsetof(struct irtd, irtd_selectivity) == 8, "irtd_selectivity offset mismatch"); // possible index states inline constexpr UCHAR irt_unused = 0; // empty slot diff --git a/src/jrd/relations.h b/src/jrd/relations.h index 852f2e5845b..bdc54dc2798 100644 --- a/src/jrd/relations.h +++ b/src/jrd/relations.h @@ -87,6 +87,7 @@ RELATION(nam_i_segments, rel_segments, ODS_8_0, rel_persistent) FIELD(f_seg_statistics, nam_statistics, fld_statistics, 1, ODS_11_0) FIELD(f_seg_schema, nam_sch_name, fld_sch_name, 1, ODS_14_0) FIELD(f_seg_pkg_name, nam_pkg_name, fld_pkg_name, 1, ODS_14_0) + FIELD(f_seg_char_length, nam_char_length, fld_f_length, 1, ODS_14_0) END_RELATION // Relation 4 (RDB$INDICES)