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 adcbe24

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

File tree

5 files changed

+67
-11
lines changed

5 files changed

+67
-11
lines changed

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,40 @@ public async Task PassesDateTimeWithProperPrecisionTest()
694694
}
695695
}
696696

697+
[Test]
698+
public async Task HighLowSurrogatePassingTest()
699+
{
700+
await using (var cmd = Connection.CreateCommand())
701+
{
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";
704+
cmd.Parameters.Add("value1", Value);
705+
cmd.Parameters.Add("value2", Value);
706+
await using (var reader = await cmd.ExecuteReaderAsync())
707+
{
708+
await reader.ReadAsync();
709+
Assert.AreEqual(Value, reader[0]);
710+
Assert.AreEqual(Value, reader[1]);
711+
}
712+
}
713+
}
714+
715+
[Test]
716+
public async Task HighLowSurrogateReadingTest()
717+
{
718+
await using (var cmd = Connection.CreateCommand())
719+
{
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";
722+
await using (var reader = await cmd.ExecuteReaderAsync())
723+
{
724+
await reader.ReadAsync();
725+
Assert.AreEqual(Value, reader[0]);
726+
Assert.AreEqual(Value, reader[1]);
727+
}
728+
}
729+
}
730+
697731
[Test]
698732
public async Task ExecuteNonQueryReturnsMinusOneOnNonInsertUpdateDeleteTest()
699733
{

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,7 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field)
12461246
else
12471247
{
12481248
var svalue = field.DbValue.GetString();
1249-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.Length > field.CharCount)
1249+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > field.CharCount)
12501250
{
12511251
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
12521252
}
@@ -1271,7 +1271,7 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field)
12711271
else
12721272
{
12731273
var svalue = field.DbValue.GetString();
1274-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.Length > field.CharCount)
1274+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > field.CharCount)
12751275
{
12761276
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
12771277
}
@@ -1394,7 +1394,7 @@ protected async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field,
13941394
else
13951395
{
13961396
var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false);
1397-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.Length > field.CharCount)
1397+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > field.CharCount)
13981398
{
13991399
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
14001400
}
@@ -1419,7 +1419,7 @@ protected async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field,
14191419
else
14201420
{
14211421
var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false);
1422-
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.Length > field.CharCount)
1422+
if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > field.CharCount)
14231423
{
14241424
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
14251425
}
@@ -1533,7 +1533,7 @@ protected object ReadRawValue(IXdrReader xdr, DbField field)
15331533
{
15341534
var s = xdr.ReadString(innerCharset, field.Length);
15351535
if ((field.Length % field.Charset.BytesPerCharacter) == 0 &&
1536-
s.Length > field.CharCount)
1536+
s.RuneCount() > field.CharCount)
15371537
{
15381538
return s.Substring(0, field.CharCount);
15391539
}
@@ -1630,7 +1630,7 @@ protected async ValueTask<object> ReadRawValueAsync(IXdrReader xdr, DbField fiel
16301630
{
16311631
var s = await xdr.ReadStringAsync(innerCharset, field.Length, cancellationToken).ConfigureAwait(false);
16321632
if ((field.Length % field.Charset.BytesPerCharacter) == 0 &&
1633-
s.Length > field.CharCount)
1633+
s.RuneCount() > field.CharCount)
16341634
{
16351635
return s.Substring(0, field.CharCount);
16361636
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ public void SetValue(byte[] buffer)
326326
var s = Charset.GetString(buffer, 0, buffer.Length);
327327

328328
if ((Length % Charset.BytesPerCharacter) == 0 &&
329-
s.Length > CharCount)
329+
s.RuneCount() > CharCount)
330330
{
331331
s = s.Substring(0, CharCount);
332332
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ public byte[] GetBytes()
427427
else
428428
{
429429
var svalue = GetString();
430-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.Length > Field.CharCount)
430+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > Field.CharCount)
431431
{
432432
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
433433
}
@@ -463,7 +463,7 @@ public byte[] GetBytes()
463463
else
464464
{
465465
var svalue = GetString();
466-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.Length > Field.CharCount)
466+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > Field.CharCount)
467467
{
468468
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
469469
}
@@ -642,7 +642,7 @@ public async ValueTask<byte[]> GetBytesAsync(CancellationToken cancellationToken
642642
else
643643
{
644644
var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false);
645-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.Length > Field.CharCount)
645+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > Field.CharCount)
646646
{
647647
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
648648
}
@@ -678,7 +678,7 @@ public async ValueTask<byte[]> GetBytesAsync(CancellationToken cancellationToken
678678
else
679679
{
680680
var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false);
681-
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.Length > Field.CharCount)
681+
if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.RuneCount() > Field.CharCount)
682682
{
683683
throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation });
684684
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,26 @@ public static IEnumerable<IEnumerable<T>> Split<T>(this T[] array, int size)
6666
#if NETSTANDARD2_0
6767
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source) => new HashSet<T>(source);
6868
#endif
69+
70+
public static int RuneCount(this string s)
71+
{
72+
if (s == null)
73+
throw new ArgumentNullException(nameof(s));
74+
75+
#if NETSTANDARD2_0 || NETSTANDARD2_1 || NET48
76+
var cnt = 0;
77+
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;
86+
87+
#else
88+
return s.EnumerateRunes().Count();
89+
#endif
90+
}
6991
}

0 commit comments

Comments
(0)

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