Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit bd0db88

Browse files
Fix reading/writing characters with high/low surrogate pairs. Part 2. (#1213)
1 parent adcbe24 commit bd0db88

File tree

5 files changed

+42
-29
lines changed

5 files changed

+42
-29
lines changed

‎src/FirebirdSql.Data.FirebirdClient.Tests/FbCommandTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -699,8 +699,8 @@ public async Task HighLowSurrogatePassingTest()
699699
{
700700
await using (var cmd = Connection.CreateCommand())
701701
{
702-
const string Value = "😊";
703-
cmd.CommandText = "select cast(@value1 as varchar(1) character set utf8), cast(@value2 as char(1) character set utf8) from rdb$database";
702+
const string Value = "😊!";
703+
cmd.CommandText = "select cast(@value1 as varchar(2) character set utf8), cast(@value2 as char(2) character set utf8) from rdb$database";
704704
cmd.Parameters.Add("value1", Value);
705705
cmd.Parameters.Add("value2", Value);
706706
await using (var reader = await cmd.ExecuteReaderAsync())
@@ -717,8 +717,8 @@ public async Task HighLowSurrogateReadingTest()
717717
{
718718
await using (var cmd = Connection.CreateCommand())
719719
{
720-
const string Value = "😊";
721-
cmd.CommandText = "select cast(x'F09F988A' as varchar(1) character set utf8), cast(x'F09F988A' as char(1) character set utf8) from rdb$database";
720+
const string Value = "😊!";
721+
cmd.CommandText = "select cast(x'F09F988A21' as varchar(2) character set utf8), cast(x'F09F988A21' as char(2) character set utf8) from rdb$database";
722722
await using (var reader = await cmd.ExecuteReaderAsync())
723723
{
724724
await reader.ReadAsync();

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using System.Collections.Generic;
2020
using System.Diagnostics;
2121
using System.IO;
22+
using System.Linq;
2223
using System.Threading;
2324
using System.Threading.Tasks;
2425
using FirebirdSql.Data.Common;
@@ -1246,7 +1247,7 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field)
12461247
else
12471248
{
12481249
var svalue = field.DbValue.GetString();
1249-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > field.CharCount)
1250+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesEx().Count() > field.CharCount)
12501251
{
12511252
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
12521253
}
@@ -1271,7 +1272,7 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field)
12711272
else
12721273
{
12731274
var svalue = field.DbValue.GetString();
1274-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > field.CharCount)
1275+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesEx().Count() > field.CharCount)
12751276
{
12761277
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
12771278
}
@@ -1394,7 +1395,7 @@ protected async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field,
13941395
else
13951396
{
13961397
var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false);
1397-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > field.CharCount)
1398+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesEx().Count() > field.CharCount)
13981399
{
13991400
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
14001401
}
@@ -1419,7 +1420,7 @@ protected async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field,
14191420
else
14201421
{
14211422
var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false);
1422-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > field.CharCount)
1423+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesEx().Count() > field.CharCount)
14231424
{
14241425
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
14251426
}
@@ -1532,10 +1533,11 @@ protected object ReadRawValue(IXdrReader xdr, DbField field)
15321533
else
15331534
{
15341535
var s = xdr.ReadString(innerCharset, field.Length);
1536+
var runes = s.EnumerateRunesEx().ToList();
15351537
if ((field.Length % field.Charset.BytesPerCharacter) == 0 &&
1536-
s.RuneCount() > field.CharCount)
1538+
runes.Count > field.CharCount)
15371539
{
1538-
return s.Substring(0,field.CharCount);
1540+
return newstring([.. runes.Take(field.CharCount).SelectMany(x =>x)]);
15391541
}
15401542
else
15411543
{
@@ -1629,10 +1631,11 @@ protected async ValueTask<object> ReadRawValueAsync(IXdrReader xdr, DbField fiel
16291631
else
16301632
{
16311633
var s = await xdr.ReadStringAsync(innerCharset, field.Length, cancellationToken).ConfigureAwait(false);
1634+
var runes = s.EnumerateRunesEx().ToList();
16321635
if ((field.Length % field.Charset.BytesPerCharacter) == 0 &&
1633-
s.RuneCount() > field.CharCount)
1636+
runes.Count > field.CharCount)
16341637
{
1635-
return s.Substring(0,field.CharCount);
1638+
return newstring([.. runes.Take(field.CharCount).SelectMany(x =>x)]);
16361639
}
16371640
else
16381641
{

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
//$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net)
1717

1818
using System;
19+
using System.Linq;
1920
using System.Numerics;
2021
using FirebirdSql.Data.Types;
2122

@@ -325,10 +326,11 @@ public void SetValue(byte[] buffer)
325326
{
326327
var s = Charset.GetString(buffer, 0, buffer.Length);
327328

329+
var runes = s.EnumerateRunesEx().ToList();
328330
if ((Length % Charset.BytesPerCharacter) == 0 &&
329-
s.RuneCount() > CharCount)
331+
runes.Count > CharCount)
330332
{
331-
s = s.Substring(0,CharCount);
333+
s = newstring([.. runes.Take(CharCount).SelectMany(x =>x)]);
332334
}
333335

334336
DbValue.SetValue(s);

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
using System;
1919
using System.Globalization;
20+
using System.Linq;
2021
using System.Numerics;
2122
using System.Threading;
2223
using System.Threading.Tasks;
@@ -427,7 +428,7 @@ public byte[] GetBytes()
427428
else
428429
{
429430
var svalue = GetString();
430-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > Field.CharCount)
431+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesEx().Count() > Field.CharCount)
431432
{
432433
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
433434
}
@@ -463,7 +464,7 @@ public byte[] GetBytes()
463464
else
464465
{
465466
var svalue = GetString();
466-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > Field.CharCount)
467+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesEx().Count() > Field.CharCount)
467468
{
468469
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
469470
}
@@ -642,7 +643,7 @@ public async ValueTask<byte[]> GetBytesAsync(CancellationToken cancellationToken
642643
else
643644
{
644645
var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false);
645-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > Field.CharCount)
646+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesEx().Count() > Field.CharCount)
646647
{
647648
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
648649
}
@@ -678,7 +679,7 @@ public async ValueTask<byte[]> GetBytesAsync(CancellationToken cancellationToken
678679
else
679680
{
680681
var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false);
681-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > Field.CharCount)
682+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesEx().Count() > Field.CharCount)
682683
{
683684
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
684685
}

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

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,25 +67,32 @@ public static IEnumerable<IEnumerable<T>> Split<T>(this T[] array, int size)
6767
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source) => new HashSet<T>(source);
6868
#endif
6969

70-
public static intRuneCount(this string s)
70+
public static IEnumerable<char[]>EnumerateRunesEx(this string s)
7171
{
7272
if (s == null)
7373
throw new ArgumentNullException(nameof(s));
7474

7575
#if NETSTANDARD2_0 || NETSTANDARD2_1 || NET48
76-
var cnt = 0;
7776
for (var i = 0; i < s.Length; i++)
78-
{
79-
if (char.IsHighSurrogate(s[i]) && i + 1 < s.Length && char.IsLowSurrogate(s[i + 1]))
80-
{
81-
i++;
82-
}
83-
cnt++;
84-
}
85-
return cnt;
77+
{
78+
if (char.IsHighSurrogate(s[i]) && i + 1 < s.Length && char.IsLowSurrogate(s[i + 1]))
79+
{
80+
yield return new[] { s[i], s[i + 1] };
81+
i++;
82+
}
83+
else
84+
{
85+
yield return new[] { s[i] };
86+
}
87+
}
8688

8789
#else
88-
return s.EnumerateRunes().Count();
90+
return s.EnumerateRunes().Select(r =>
91+
{
92+
var result = new char[r.Utf16SequenceLength];
93+
r.EncodeToUtf16(result);
94+
return result;
95+
});
8996
#endif
9097
}
9198
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /