Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1533,16 +1533,7 @@ protected object ReadRawValue(IXdrReader xdr, DbField field)
else
{
var s = xdr.ReadString(innerCharset, field.Length);
var runes = s.EnumerateRunesToChars().ToList();
if ((field.Length % field.Charset.BytesPerCharacter) == 0 &&
runes.Count > field.CharCount)
{
return new string([.. runes.Take(field.CharCount).SelectMany(x => x)]);
}
else
{
return s;
}
return TruncateStringByRuneCount(s, field);
}

case DbDataType.VarChar:
Expand Down Expand Up @@ -1631,16 +1622,7 @@ protected async ValueTask<object> ReadRawValueAsync(IXdrReader xdr, DbField fiel
else
{
var s = await xdr.ReadStringAsync(innerCharset, field.Length, cancellationToken).ConfigureAwait(false);
var runes = s.EnumerateRunesToChars().ToList();
if ((field.Length % field.Charset.BytesPerCharacter) == 0 &&
runes.Count > field.CharCount)
{
return new string([.. runes.Take(field.CharCount).SelectMany(x => x)]);
}
else
{
return s;
}
return TruncateStringByRuneCount(s, field);
}

case DbDataType.VarChar:
Expand Down Expand Up @@ -1797,6 +1779,22 @@ protected virtual async ValueTask<DbValue[]> ReadRowAsync(CancellationToken canc
return row;
}

private static string TruncateStringByRuneCount(string s, DbField field)
{
if ((field.Length % field.Charset.BytesPerCharacter) != 0)
{
return s;
}

var runeCount = s.CountRunes();
if (runeCount <= field.CharCount)
{
return s;
}

return new string(s.TruncateStringToRuneCount(field.CharCount));
}

#endregion

#region Protected Internal Methods
Expand Down
12 changes: 6 additions & 6 deletions src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,12 @@ public void SetValue(byte[] buffer)
else
{
var s = Charset.GetString(buffer, 0, buffer.Length);

var runes = s.EnumerateRunesToChars().ToList();
if ((Length % Charset.BytesPerCharacter) == 0 &&
runes.Count > CharCount)
{
s = new string([.. runes.Take(CharCount).SelectMany(x => x)]);
if((Length % Charset.BytesPerCharacter) == 0)
{
var runes = s.CountRunes();
if(runes > CharCount) {
s = new string(s.TruncateStringToRuneCount(CharCount));
}
}

DbValue.SetValue(s);
Expand Down
8 changes: 4 additions & 4 deletions src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ public byte[] GetBytes()
else
{
var svalue = GetString();
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount)
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount)
{
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
}
Expand Down Expand Up @@ -460,7 +460,7 @@ public byte[] GetBytes()
else
{
var svalue = GetString();
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount)
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount)
{
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
}
Expand Down Expand Up @@ -639,7 +639,7 @@ public async ValueTask<byte[]> GetBytesAsync(CancellationToken cancellationToken
else
{
var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false);
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount)
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount)
{
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
}
Expand Down Expand Up @@ -675,7 +675,7 @@ public async ValueTask<byte[]> GetBytesAsync(CancellationToken cancellationToken
else
{
var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false);
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount)
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount)
{
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
}
Expand Down
56 changes: 56 additions & 0 deletions src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,60 @@ public static Encoding GetANSIEncoding()
}
}
}

public static int CountRunes(this ReadOnlySpan<char> text)
{
var length = text.Length;
if(length == 0)
return 0;

var i = text.IndexOfAnyInRange('\uD800', '\uDBFF');
if(i < 0)
return length;

var count = i;
while(i < length)
{
if(char.IsHighSurrogate(text[i]) && i + 1 < length && char.IsLowSurrogate(text[i + 1]))
{
i += 2;
}
else
{
i++;
}
count++;
}
return count;
}

public static ReadOnlySpan<char> TruncateStringToRuneCount(this ReadOnlySpan<char> text, int maxRuneCount)
{
if(maxRuneCount <= 0 || text.IsEmpty)
return ReadOnlySpan<char>.Empty;

var length = text.Length;
if(maxRuneCount >= length)
return text;

var prefix = text[..maxRuneCount];
var i = prefix.IndexOfAnyInRange('\uD800', '\uDBFF');
if(i < 0)
return prefix;

var remaining = maxRuneCount - i;
while(i < length && remaining > 0)
{
if(char.IsHighSurrogate(text[i]) && i + 1 < length && char.IsLowSurrogate(text[i + 1]))
{
i += 2;
}
else
{
i++;
}
remaining--;
}
return text[..i];
}
}