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)