Skip to content

Commit ddaf012

Browse files
committed
Reworked the rune enumerator to not spam byte[1...4] alloc
ations and optimized rune operations
1 parent 5556482 commit ddaf012

File tree

4 files changed

+66
-34
lines changed

4 files changed

+66
-34
lines changed

src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,7 +1405,7 @@ protected static void WriteRawParameter(IXdrWriter xdr, DbField field)
14051405
else
14061406
{
14071407
var svalue = field.DbValue.GetString();
1408-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount)
1408+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > field.CharCount)
14091409
{
14101410
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
14111411
}
@@ -1445,7 +1445,7 @@ protected static void WriteRawParameter(IXdrWriter xdr, DbField field)
14451445
else
14461446
{
14471447
var svalue = field.DbValue.GetString();
1448-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount)
1448+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > field.CharCount)
14491449
{
14501450
throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]);
14511451
}
@@ -1579,7 +1579,7 @@ protected static async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField
15791579
else
15801580
{
15811581
var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false);
1582-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount)
1582+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > field.CharCount)
15831583
{
15841584
throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]);
15851585
}
@@ -1612,7 +1612,7 @@ protected static async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField
16121612
else
16131613
{
16141614
var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false);
1615-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount)
1615+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > field.CharCount)
16161616
{
16171617
throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]);
16181618
}
@@ -1738,16 +1738,7 @@ protected object ReadRawValue(IXdrReader xdr, DbField field)
17381738
else
17391739
{
17401740
var s = xdr.ReadString(innerCharset, field.Length);
1741-
var runes = s.EnumerateRunesToChars().ToList();
1742-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 &&
1743-
runes.Count > field.CharCount)
1744-
{
1745-
return new string([.. runes.Take(field.CharCount).SelectMany(x => x)]);
1746-
}
1747-
else
1748-
{
1749-
return s;
1750-
}
1741+
return TruncateStringByRuneCount(s, field);
17511742
}
17521743

17531744
case DbDataType.VarChar:
@@ -1836,16 +1827,7 @@ protected async ValueTask<object> ReadRawValueAsync(IXdrReader xdr, DbField fiel
18361827
else
18371828
{
18381829
var s = await xdr.ReadStringAsync(innerCharset, field.Length, cancellationToken).ConfigureAwait(false);
1839-
var runes = s.EnumerateRunesToChars().ToList();
1840-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 &&
1841-
runes.Count > field.CharCount)
1842-
{
1843-
return new string([.. runes.Take(field.CharCount).SelectMany(x => x)]);
1844-
}
1845-
else
1846-
{
1847-
return s;
1848-
}
1830+
return TruncateStringByRuneCount(s, field);
18491831
}
18501832

18511833
case DbDataType.VarChar:
@@ -2002,6 +1984,22 @@ protected virtual async ValueTask<DbValue[]> ReadRowAsync(CancellationToken canc
20021984
return row;
20031985
}
20041986

1987+
private static string TruncateStringByRuneCount(string s, DbField field)
1988+
{
1989+
if ((field.Length % field.Charset.BytesPerCharacter) != 0)
1990+
{
1991+
return s;
1992+
}
1993+
1994+
var runeCount = s.CountRunes();
1995+
if (runeCount <= field.CharCount)
1996+
{
1997+
return s;
1998+
}
1999+
2000+
return new string(s.TruncateStringToRuneCount(field.CharCount));
2001+
}
2002+
20052003
#endregion
20062004

20072005
#region Protected Internal Methods

src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,12 @@ public void SetValue(byte[] buffer)
325325
else
326326
{
327327
var s = Charset.GetString(buffer, 0, buffer.Length);
328-
329-
var runes = s.EnumerateRunesToChars().ToList();
330-
if ((Length % Charset.BytesPerCharacter) == 0 &&
331-
runes.Count > CharCount)
332-
{
333-
s = new string([.. runes.Take(CharCount).SelectMany(x => x)]);
328+
if((Length % Charset.BytesPerCharacter) == 0)
329+
{
330+
var runes = s.CountRunes();
331+
if(runes > CharCount) {
332+
s = new string(s.TruncateStringToRuneCount(CharCount));
333+
}
334334
}
335335

336336
DbValue.SetValue(s);

src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ public byte[] GetBytes()
424424
else
425425
{
426426
var svalue = GetString();
427-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount)
427+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount)
428428
{
429429
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
430430
}
@@ -460,7 +460,7 @@ public byte[] GetBytes()
460460
else
461461
{
462462
var svalue = GetString();
463-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount)
463+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount)
464464
{
465465
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
466466
}
@@ -639,7 +639,7 @@ public async ValueTask<byte[]> GetBytesAsync(CancellationToken cancellationToken
639639
else
640640
{
641641
var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false);
642-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount)
642+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount)
643643
{
644644
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
645645
}
@@ -675,7 +675,7 @@ public async ValueTask<byte[]> GetBytesAsync(CancellationToken cancellationToken
675675
else
676676
{
677677
var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false);
678-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount)
678+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount)
679679
{
680680
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
681681
}

src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,38 @@ public static Encoding GetANSIEncoding()
102102
}
103103
}
104104
}
105+
106+
public static int CountRunes(this ReadOnlySpan<char> text) {
107+
var count = 0;
108+
var i = 0;
109+
while(i < text.Length) {
110+
if(char.IsHighSurrogate(text[i]) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1])) {
111+
i += 2;
112+
}
113+
else {
114+
i++;
115+
}
116+
count++;
117+
}
118+
return count;
119+
}
120+
121+
public static ReadOnlySpan<char> TruncateStringToRuneCount(this ReadOnlySpan<char> text, int maxRuneCount) {
122+
var count = 0;
123+
var i = 0;
124+
while(i < text.Length && count < maxRuneCount) {
125+
var nextI = i;
126+
if(char.IsHighSurrogate(text[i]) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1])) {
127+
nextI += 2;
128+
}
129+
else {
130+
nextI++;
131+
}
132+
count++;
133+
if(count <= maxRuneCount) {
134+
i = nextI;
135+
}
136+
}
137+
return text[..i];
138+
}
105139
}

0 commit comments

Comments
 (0)