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)