diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj index 8c4feaad72f..4666e23f52b 100644 --- a/builds/win32/msvc15/engine_static.vcxproj +++ b/builds/win32/msvc15/engine_static.vcxproj @@ -297,6 +297,7 @@ + @@ -605,4 +606,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 9b99e9ebb8f..c83b38cec0f 100644 --- a/builds/win32/msvc15/engine_static.vcxproj.filters +++ b/builds/win32/msvc15/engine_static.vcxproj.filters @@ -564,6 +564,7 @@ JRD files + @@ -1164,6 +1165,9 @@ Header files + + Header files + @@ -1215,4 +1219,4 @@ DSQL - + \ No newline at end of file diff --git a/src/burp/backup.epp b/src/burp/backup.epp index 6fa5b06f812..61d65d30a76 100644 --- a/src/burp/backup.epp +++ b/src/burp/backup.epp @@ -1726,6 +1726,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 e21e2c69613..a1f6f0bb2af 100644 --- a/src/burp/burp.h +++ b/src/burp/burp.h @@ -223,6 +223,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. */ @@ -382,6 +385,7 @@ enum att_type { att_index_condition_source, att_index_condition_blr, att_index_foreign_key_schema_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 6b1e98e4bc7..cf92f936a25 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -6777,6 +6777,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 { @@ -6857,26 +6870,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; @@ -6948,43 +6957,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 92c59348124..9f859e3a56a 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -7861,7 +7861,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; @@ -9882,15 +9885,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, addPackagedTableIndexClause->indexNode->name, definition)); @@ -13355,6 +13354,8 @@ MetaId StoreIndexNode::create(thread_db* tdbb, jrd_tra* transaction) 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); + idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_length = SEG.RDB$CHARACTER_LENGTH; + // Initialize selectivity to zero. Otherwise random rubbish makes its way into database idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_selectivity = 0; } @@ -13572,6 +13573,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&) @@ -13761,15 +13763,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); } } @@ -13781,11 +13783,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) { @@ -13807,25 +13810,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) { @@ -13865,15 +13885,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 @@ -13885,7 +13903,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 @@ -13906,7 +13928,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 @@ -14031,7 +14053,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" @@ -14059,7 +14081,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 @@ -14105,7 +14127,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); @@ -14156,16 +14178,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) { @@ -14254,28 +14269,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; }); + + 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) << colName.c_str()); + // 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 @@ -14307,8 +14323,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); diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index fdcbf5ec827..f5918ea29c2 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" @@ -2022,6 +2023,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() @@ -2035,7 +2057,7 @@ class CreateIndexNode final : public DdlNode QualifiedName index; QualifiedName relation; - Firebird::ObjectsArray columns; + Segments segments; Firebird::TriState unique; Firebird::TriState descending; Firebird::TriState inactive; @@ -2087,7 +2109,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; diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index e9824176ae0..e5a69fe9345 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -153 shift/reduce conflicts, 8 reduce/reduce conflicts. +153 shift/reduce conflicts, 9 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index a8e8c16cb96..c2100607bc7 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -902,6 +902,8 @@ using namespace Firebird; Jrd::SessionResetNode* sessionResetNode; Jrd::ForRangeNode::Direction forRangeDirection; Jrd::CreatePackageConstantNode* createPackageConstantNode; + Jrd::CreateIndexNode::Segments* indexSegments; + Jrd::CreateIndexNode::Segment* indexSegment; } %include types.y @@ -1931,10 +1933,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(); @@ -1943,6 +1945,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 */ @@ -2609,12 +2650,12 @@ packaged_table_indexes($createRelationNode) %type packaged_table_index() packaged_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); 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/isql/show.epp b/src/isql/show.epp index ba192f8e9a1..78308a5e457 100644 --- a/src/isql/show.epp +++ b/src/isql/show.epp @@ -4733,11 +4733,12 @@ static void show_index(const QualifiedMetaString& relationName, **************************************/ isqlGlob.printf( - "%s%s%s INDEX ON %s", + "%s%s%s%s INDEX ON %s", IUTILS_name_to_string(indexName).c_str(), (unique_flag & IDX_UNIQUE ? " UNIQUE" : ""), (unique_flag & IDX_NOT_VALIDATED ? " NOT VALIDATED" : ""), - (index_type == 1 ? " DESCENDING" : ""), IUTILS_name_to_string(relationName).c_str()); + (index_type == 1 ? " DESCENDING" : ""), + IUTILS_name_to_string(relationName).c_str()); // Get column names 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/btr.cpp b/src/jrd/btr.cpp index d5968fbfb15..79fb33453ec 100644 --- a/src/jrd/btr.cpp +++ b/src/jrd/btr.cpp @@ -225,7 +225,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*); @@ -756,7 +756,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 { @@ -795,7 +795,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) @@ -1703,6 +1703,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); } @@ -2388,9 +2389,22 @@ USHORT BTR_key_length(thread_db* tdbb, jrd_rel* relation, index_desc* idx) } 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); + if (!tail->idx_length) + { + 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(); } } @@ -2437,9 +2451,23 @@ USHORT BTR_key_length(thread_db* tdbb, jrd_rel* relation, index_desc* idx) 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_length) + { + length = format->fmt_desc[tail->idx_field].dsc_length; + if (format->fmt_desc[tail->idx_field].dsc_dtype == dtype_varying) + 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(); + } + if (tail->idx_itype >= idx_first_intl_string) length = INTL_key_length(tdbb, tail->idx_itype, length); break; @@ -2620,7 +2648,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)) { @@ -2653,7 +2681,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); @@ -2779,7 +2807,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 { @@ -2793,7 +2821,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) @@ -3879,6 +3907,7 @@ static void compress(thread_db* tdbb, const SSHORT matchScale, temporary_key* key, USHORT itype, + SSHORT ilength, bool descending, USHORT key_type, bool* forceInclude) { @@ -3976,6 +4005,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 @@ -3986,10 +4044,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) diff --git a/src/jrd/btr.h b/src/jrd/btr.h index 41f3092471a..9effdb5aac6 100644 --- a/src/jrd/btr.h +++ b/src/jrd/btr.h @@ -107,6 +107,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]; }; diff --git a/src/jrd/dfw.epp b/src/jrd/dfw.epp index 5bef5ff1ab7..41237eb32e2 100644 --- a/src/jrd/dfw.epp +++ b/src/jrd/dfw.epp @@ -4914,7 +4914,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) @@ -4925,14 +4925,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; @@ -4942,7 +4942,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; diff --git a/src/jrd/ini.epp b/src/jrd/ini.epp index 1db45ba4ca5..46599f55e98 100644 --- a/src/jrd/ini.epp +++ b/src/jrd/ini.epp @@ -1743,6 +1743,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 diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 2efe74ad0a3..dd6e4c5edfd 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -5176,7 +5176,7 @@ void IndexVersion::setLtt(thread_db* tdbb, LocalTemporaryTable::Index* lttIndex) idv_name = lttIndex->name; idv_uniqFlag = lttIndex->unique; - idv_segmentCount = lttIndex->columns.getCount(); + idv_segmentCount = lttIndex->segments.getCount(); idv_type = lttIndex->descending; idv_active = lttIndex->inactive ? MET_index_inactive : MET_index_active; } diff --git a/src/jrd/ods.h b/src/jrd/ods.h index 08a026ee234..307a6cb3568 100644 --- a/src/jrd/ods.h +++ b/src/jrd/ods.h @@ -458,13 +458,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 fd7baa1aa1f..2b4e57b0553 100644 --- a/src/jrd/relations.h +++ b/src/jrd/relations.h @@ -86,6 +86,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)