Skip to content

Commit 1172b33

Browse files
committed
Support higher maximum blob segment size (issue #1197)
- Add new 'BlobSegmentSize' connection string property (range 512-65535 bytes) replacing the blob-specific role of PacketSize - Deprecate PacketSize on FbConnectionStringBuilder and FbConnection (use BlobSegmentSize instead) - Raise wire protocol blob segment limit from 32767 to 65535 bytes in GdsBlob, XdrReaderWriter (WriteBlobBuffer) and the native IFbClient interface (isc_get_segment / isc_put_segment now take unsigned shorts) - Plumb BlobSegmentSize through the full stack: ConnectionString -> GdsConnection / FesDatabase -> DatabaseBase -> BlobBase._segmentSize - Add tests for: connection string parsing/synonyms/validation, FbConnectionStringBuilder getter/setter, and blob read/write with BlobSegmentSize=65535 Fixes #1197
1 parent 73e62c7 commit 1172b33

File tree

16 files changed

+211
-33
lines changed

16 files changed

+211
-33
lines changed

src/FirebirdSql.Data.FirebirdClient.Tests/ConnectionStringTests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
//$Authors = Jiri Cincura (jiri@cincura.net)
1717

18+
using System;
1819
using System.Globalization;
1920
using System.Threading;
2021
using FirebirdSql.Data.Common;
@@ -760,4 +761,61 @@ public void ParsingDatabaseHostnames(string hostname)
760761
Assert.AreEqual(hostname, cs.DataSource);
761762
Assert.AreEqual("test.fdb", cs.Database);
762763
}
764+
765+
[Test]
766+
public void BlobSegmentSizeDefault()
767+
{
768+
var cs = new ConnectionString();
769+
Assert.AreEqual(ConnectionString.DefaultValueBlobSegmentSize, cs.BlobSegmentSize);
770+
}
771+
772+
[Test]
773+
public void BlobSegmentSizeParsing()
774+
{
775+
const string connStr = "datasource=testserver;database=testdb.fdb;user=testuser;password=testpwd;blob segment size=65535";
776+
var cs = new ConnectionString(connStr);
777+
Assert.AreEqual(65535, cs.BlobSegmentSize);
778+
}
779+
780+
[Test]
781+
public void BlobSegmentSizeParsingSynonym()
782+
{
783+
const string connStr = "datasource=testserver;database=testdb.fdb;user=testuser;password=testpwd;blobsegmentsize=32000";
784+
var cs = new ConnectionString(connStr);
785+
Assert.AreEqual(32000, cs.BlobSegmentSize);
786+
}
787+
788+
[Test]
789+
public void BlobSegmentSizeValidationTooSmall()
790+
{
791+
const string connStr = "datasource=testserver;database=testdb.fdb;user=testuser;password=testpwd;blob segment size=100";
792+
var cs = new ConnectionString(connStr);
793+
Assert.Throws<ArgumentException>(() => cs.Validate());
794+
}
795+
796+
[Test]
797+
public void BlobSegmentSizeValidationTooLarge()
798+
{
799+
const string connStr = "datasource=testserver;database=testdb.fdb;user=testuser;password=testpwd;blob segment size=70000";
800+
var cs = new ConnectionString(connStr);
801+
Assert.Throws<ArgumentException>(() => cs.Validate());
802+
}
803+
804+
[Test]
805+
public void BlobSegmentSizeValidationMinBoundary()
806+
{
807+
const string connStr = "datasource=testserver;database=testdb.fdb;user=testuser;password=testpwd;blob segment size=512";
808+
var cs = new ConnectionString(connStr);
809+
Assert.DoesNotThrow(() => cs.Validate());
810+
Assert.AreEqual(512, cs.BlobSegmentSize);
811+
}
812+
813+
[Test]
814+
public void BlobSegmentSizeValidationMaxBoundary()
815+
{
816+
const string connStr = "datasource=testserver;database=testdb.fdb;user=testuser;password=testpwd;blob segment size=65535";
817+
var cs = new ConnectionString(connStr);
818+
Assert.DoesNotThrow(() => cs.Validate());
819+
Assert.AreEqual(65535, cs.BlobSegmentSize);
820+
}
763821
}

src/FirebirdSql.Data.FirebirdClient.Tests/FbBlobTests.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,70 @@ public async Task ReaderGetBytes()
9292
CollectionAssert.AreEqual(insert_values, select_values);
9393
}
9494
}
95+
96+
[Test]
97+
public async Task BinaryBlobTestWithLargeSegmentSize()
98+
{
99+
var id_value = RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue);
100+
var insert_values = RandomNumberGenerator.GetBytes(200000);
101+
102+
var csb = BuildConnectionStringBuilder(ServerType, Compression, WireCrypt);
103+
csb.BlobSegmentSize = 65535;
104+
105+
await using (var conn = new FbConnection(csb.ToString()))
106+
{
107+
await conn.OpenAsync();
108+
await using (var transaction = await conn.BeginTransactionAsync())
109+
{
110+
await using (var insert = new FbCommand("INSERT INTO TEST (int_field, blob_field) values(@int_field, @blob_field)", conn, transaction))
111+
{
112+
insert.Parameters.Add("@int_field", FbDbType.Integer).Value = id_value;
113+
insert.Parameters.Add("@blob_field", FbDbType.Binary).Value = insert_values;
114+
await insert.ExecuteNonQueryAsync();
115+
}
116+
117+
await transaction.CommitAsync();
118+
}
119+
120+
await using (var select = new FbCommand($"SELECT blob_field FROM TEST WHERE int_field = {id_value}", conn))
121+
{
122+
var select_values = (byte[])await select.ExecuteScalarAsync();
123+
CollectionAssert.AreEqual(insert_values, select_values);
124+
}
125+
}
126+
}
127+
128+
[Test]
129+
public async Task BinaryBlobTestWithMaxSegmentSize()
130+
{
131+
var id_value = RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue);
132+
var insert_values = RandomNumberGenerator.GetBytes(100000);
133+
134+
var csb = BuildConnectionStringBuilder(ServerType, Compression, WireCrypt);
135+
csb.BlobSegmentSize = 65535;
136+
137+
await using (var conn = new FbConnection(csb.ToString()))
138+
{
139+
await conn.OpenAsync();
140+
Assert.AreEqual(65535, conn.BlobSegmentSize);
141+
142+
await using (var transaction = await conn.BeginTransactionAsync())
143+
{
144+
await using (var insert = new FbCommand("INSERT INTO TEST (int_field, blob_field) values(@int_field, @blob_field)", conn, transaction))
145+
{
146+
insert.Parameters.Add("@int_field", FbDbType.Integer).Value = id_value;
147+
insert.Parameters.Add("@blob_field", FbDbType.Binary).Value = insert_values;
148+
await insert.ExecuteNonQueryAsync();
149+
}
150+
151+
await transaction.CommitAsync();
152+
}
153+
154+
await using (var select = new FbCommand($"SELECT blob_field FROM TEST WHERE int_field = {id_value}", conn))
155+
{
156+
var select_values = (byte[])await select.ExecuteScalarAsync();
157+
CollectionAssert.AreEqual(insert_values, select_values);
158+
}
159+
}
160+
}
95161
}

src/FirebirdSql.Data.FirebirdClient.Tests/FbConnectionStringBuilderTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,26 @@ public void WireCryptGetter()
6868
var b = new FbConnectionStringBuilder("wire crypt=required");
6969
Assert.AreEqual(FbWireCrypt.Required, b.WireCrypt);
7070
}
71+
72+
[Test]
73+
public void BlobSegmentSizeDefaultValue()
74+
{
75+
var b = new FbConnectionStringBuilder();
76+
Assert.AreEqual(ConnectionString.DefaultValueBlobSegmentSize, b.BlobSegmentSize);
77+
}
78+
79+
[Test]
80+
public void BlobSegmentSizeSetter()
81+
{
82+
var b = new FbConnectionStringBuilder();
83+
b.BlobSegmentSize = 65535;
84+
Assert.That(b.ToString(), Does.Contain("blob segment size=65535"));
85+
}
86+
87+
[Test]
88+
public void BlobSegmentSizeGetter()
89+
{
90+
var b = new FbConnectionStringBuilder("blob segment size=32000");
91+
Assert.AreEqual(32000, b.BlobSegmentSize);
92+
}
7193
}

src/FirebirdSql.Data.FirebirdClient/Client/ClientFactory.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private static DatabaseBase CreateManagedDatabase(ConnectionString options)
6969
{
7070
var charset = GetCharset(options);
7171

72-
var connection = new GdsConnection(options.UserID, options.Password, options.DataSource, options.Port, options.ConnectionTimeout, options.PacketSize, charset, options.Dialect, options.Compression, FbWireCryptToWireCryptOption(options.WireCrypt), options.CryptKey);
72+
var connection = new GdsConnection(options.UserID, options.Password, options.DataSource, options.Port, options.ConnectionTimeout, options.PacketSize, options.BlobSegmentSize, charset, options.Dialect, options.Compression, FbWireCryptToWireCryptOption(options.WireCrypt), options.CryptKey);
7373
connection.Connect();
7474
try
7575
{
@@ -95,7 +95,7 @@ private static async ValueTask<DatabaseBase> CreateManagedDatabaseAsync(Connecti
9595
{
9696
var charset = GetCharset(options);
9797

98-
var connection = new GdsConnection(options.UserID, options.Password, options.DataSource, options.Port, options.ConnectionTimeout, options.PacketSize, charset, options.Dialect, options.Compression, FbWireCryptToWireCryptOption(options.WireCrypt), options.CryptKey);
98+
var connection = new GdsConnection(options.UserID, options.Password, options.DataSource, options.Port, options.ConnectionTimeout, options.PacketSize, options.BlobSegmentSize, charset, options.Dialect, options.Compression, FbWireCryptToWireCryptOption(options.WireCrypt), options.CryptKey);
9999
await connection.ConnectAsync(cancellationToken).ConfigureAwait(false);
100100
try
101101
{
@@ -122,20 +122,20 @@ private static DatabaseBase CreateNativeDatabase(ConnectionString options)
122122
{
123123
var charset = GetCharset(options);
124124

125-
return new Native.FesDatabase(options.ClientLibrary, charset, options.PacketSize, options.Dialect);
125+
return new Native.FesDatabase(options.ClientLibrary, charset, options.PacketSize, options.BlobSegmentSize, options.Dialect);
126126
}
127127
private static ValueTask<DatabaseBase> CreateNativeDatabaseAsync(ConnectionString options)
128128
{
129129
var charset = GetCharset(options);
130130

131-
return ValueTask.FromResult<DatabaseBase>(new Native.FesDatabase(options.ClientLibrary, charset, options.PacketSize, options.Dialect));
131+
return ValueTask.FromResult<DatabaseBase>(new Native.FesDatabase(options.ClientLibrary, charset, options.PacketSize, options.BlobSegmentSize, options.Dialect));
132132
}
133133

134134
private static ServiceManagerBase CreateManagedServiceManager(ConnectionString options)
135135
{
136136
var charset = GetCharset(options);
137137

138-
var connection = new GdsConnection(options.UserID, options.Password, options.DataSource, options.Port, options.ConnectionTimeout, options.PacketSize, charset, options.Dialect, options.Compression, FbWireCryptToWireCryptOption(options.WireCrypt), options.CryptKey);
138+
var connection = new GdsConnection(options.UserID, options.Password, options.DataSource, options.Port, options.ConnectionTimeout, options.PacketSize, options.BlobSegmentSize, charset, options.Dialect, options.Compression, FbWireCryptToWireCryptOption(options.WireCrypt), options.CryptKey);
139139
connection.Connect();
140140
try
141141
{
@@ -161,7 +161,7 @@ private static async ValueTask<ServiceManagerBase> CreateManagedServiceManagerAs
161161
{
162162
var charset = GetCharset(options);
163163

164-
var connection = new GdsConnection(options.UserID, options.Password, options.DataSource, options.Port, options.ConnectionTimeout, options.PacketSize, charset, options.Dialect, options.Compression, FbWireCryptToWireCryptOption(options.WireCrypt), options.CryptKey);
164+
var connection = new GdsConnection(options.UserID, options.Password, options.DataSource, options.Port, options.ConnectionTimeout, options.PacketSize, options.BlobSegmentSize, charset, options.Dialect, options.Compression, FbWireCryptToWireCryptOption(options.WireCrypt), options.CryptKey);
165165
await connection.ConnectAsync(cancellationToken).ConfigureAwait(false);
166166
try
167167
{

src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ internal sealed class GdsConnection
3838
public int PortNumber { get; private set; }
3939
public int Timeout { get; private set; }
4040
public int PacketSize { get; private set; }
41+
public int BlobSegmentSize { get; private set; }
4142
public Charset Charset { get; private set; }
4243
public short Dialect { get; private set; }
4344
public bool Compression { get; private set; }
@@ -55,17 +56,18 @@ internal sealed class GdsConnection
5556
internal AuthBlock AuthBlock { get; private set; }
5657

5758
public GdsConnection(string dataSource, int port, int timeout)
58-
: this(null, null, dataSource, port, timeout, 8192, Charset.DefaultCharset, 3, false, Version13.WireCryptOption.Enabled, null)
59+
: this(null, null, dataSource, port, timeout, 8192, 8192, Charset.DefaultCharset, 3, false, Version13.WireCryptOption.Enabled, null)
5960
{ }
6061

61-
public GdsConnection(string user, string password, string dataSource, int portNumber, int timeout, int packetSize, Charset charset, short dialect, bool compression, Version13.WireCryptOption wireCrypt, byte[] cryptKey)
62+
public GdsConnection(string user, string password, string dataSource, int portNumber, int timeout, int packetSize, int blobSegmentSize, Charset charset, short dialect, bool compression, Version13.WireCryptOption wireCrypt, byte[] cryptKey)
6263
{
6364
User = user;
6465
Password = password;
6566
DataSource = dataSource;
6667
PortNumber = portNumber;
6768
Timeout = timeout;
6869
PacketSize = packetSize;
70+
BlobSegmentSize = blobSegmentSize;
6971
Charset = charset;
7072
Dialect = dialect;
7173
Compression = compression;

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public override void GetSegment(Stream stream)
205205
{
206206
_database.Xdr.Write(IscCodes.op_get_segment);
207207
_database.Xdr.Write(_blobHandle);
208-
_database.Xdr.Write(requested < short.MaxValue - 12 ? requested : short.MaxValue - 12);
208+
_database.Xdr.Write(requested < ushort.MaxValue ? requested : ushort.MaxValue);
209209
_database.Xdr.Write(DataSegment);
210210
_database.Xdr.Flush();
211211

@@ -254,7 +254,7 @@ public override async ValueTask GetSegmentAsync(Stream stream, CancellationToken
254254
{
255255
await _database.Xdr.WriteAsync(IscCodes.op_get_segment, cancellationToken).ConfigureAwait(false);
256256
await _database.Xdr.WriteAsync(_blobHandle, cancellationToken).ConfigureAwait(false);
257-
await _database.Xdr.WriteAsync(requested < short.MaxValue - 12 ? requested : short.MaxValue - 12, cancellationToken).ConfigureAwait(false);
257+
await _database.Xdr.WriteAsync(requested < ushort.MaxValue ? requested : ushort.MaxValue, cancellationToken).ConfigureAwait(false);
258258
await _database.Xdr.WriteAsync(DataSegment, cancellationToken).ConfigureAwait(false);
259259
await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false);
260260

@@ -304,7 +304,7 @@ public override byte[] GetSegment()
304304
{
305305
_database.Xdr.Write(IscCodes.op_get_segment);
306306
_database.Xdr.Write(_blobHandle);
307-
_database.Xdr.Write(requested < short.MaxValue - 12 ? requested : short.MaxValue - 12);
307+
_database.Xdr.Write(requested < ushort.MaxValue ? requested : ushort.MaxValue);
308308
_database.Xdr.Write(DataSegment);
309309
_database.Xdr.Flush();
310310

@@ -360,7 +360,7 @@ public override async ValueTask<byte[]> GetSegmentAsync(CancellationToken cancel
360360
{
361361
await _database.Xdr.WriteAsync(IscCodes.op_get_segment, cancellationToken).ConfigureAwait(false);
362362
await _database.Xdr.WriteAsync(_blobHandle, cancellationToken).ConfigureAwait(false);
363-
await _database.Xdr.WriteAsync(requested < short.MaxValue - 12 ? requested : short.MaxValue - 12, cancellationToken).ConfigureAwait(false);
363+
await _database.Xdr.WriteAsync(requested < ushort.MaxValue ? requested : ushort.MaxValue, cancellationToken).ConfigureAwait(false);
364364
await _database.Xdr.WriteAsync(DataSegment, cancellationToken).ConfigureAwait(false);
365365
await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false);
366366

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public AuthBlock AuthBlock
7676
#region Constructors
7777

7878
public GdsDatabase(GdsConnection connection)
79-
: base(connection.Charset, connection.PacketSize, connection.Dialect)
79+
: base(connection.Charset, connection.PacketSize, connection.BlobSegmentSize, connection.Dialect)
8080
{
8181
_connection = connection;
8282
_handle = -1;

src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ public async ValueTask WriteBufferAsync(byte[] buffer, int length, CancellationT
548548
public void WriteBlobBuffer(byte[] buffer)
549549
{
550550
var length = buffer.Length; // 2 for short for buffer length
551-
if (length > short.MaxValue)
551+
if (length > ushort.MaxValue)
552552
throw new IOException("Blob buffer too big.");
553553
Write(length + 2);
554554
Write(length + 2); //bizarre but true! three copies of the length
@@ -559,7 +559,7 @@ public void WriteBlobBuffer(byte[] buffer)
559559
public async ValueTask WriteBlobBufferAsync(byte[] buffer, CancellationToken cancellationToken = default)
560560
{
561561
var length = buffer.Length; // 2 for short for buffer length
562-
if (length > short.MaxValue)
562+
if (length > ushort.MaxValue)
563563
throw new IOException("Blob buffer too big.");
564564
await WriteAsync(length + 2, cancellationToken).ConfigureAwait(false);
565565
await WriteAsync(length + 2, cancellationToken).ConfigureAwait(false); //bizarre but true! three copies of the length

src/FirebirdSql.Data.FirebirdClient/Client/Native/FesBlob.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ public override ValueTask<int> GetLengthAsync(CancellationToken cancellationToke
206206

207207
public override void GetSegment(Stream stream)
208208
{
209-
var requested = (short)SegmentSize;
210-
short segmentLength = 0;
209+
var requested = (ushort)SegmentSize;
210+
ushort segmentLength = 0;
211211

212212
ClearStatusVector();
213213

@@ -243,8 +243,8 @@ public override void GetSegment(Stream stream)
243243
}
244244
public override ValueTask GetSegmentAsync(Stream stream, CancellationToken cancellationToken = default)
245245
{
246-
var requested = (short)SegmentSize;
247-
short segmentLength = 0;
246+
var requested = (ushort)SegmentSize;
247+
ushort segmentLength = 0;
248248

249249
ClearStatusVector();
250250

@@ -284,8 +284,8 @@ public override ValueTask GetSegmentAsync(Stream stream, CancellationToken cance
284284

285285
public override byte[] GetSegment()
286286
{
287-
var requested = (short)(SegmentSize - 2);
288-
short segmentLength = 0;
287+
var requested = (ushort)(SegmentSize - 2);
288+
ushort segmentLength = 0;
289289

290290
ClearStatusVector();
291291

@@ -328,8 +328,8 @@ public override byte[] GetSegment()
328328
}
329329
public override ValueTask<byte[]> GetSegmentAsync(CancellationToken cancellationToken = default)
330330
{
331-
var requested = (short)SegmentSize;
332-
short segmentLength = 0;
331+
var requested = (ushort)SegmentSize;
332+
ushort segmentLength = 0;
333333

334334
ClearStatusVector();
335335

@@ -380,7 +380,7 @@ public override void PutSegment(byte[] buffer)
380380
_database.FbClient.isc_put_segment(
381381
_statusVector,
382382
ref _blobHandle,
383-
(short)buffer.Length,
383+
(ushort)buffer.Length,
384384
buffer);
385385

386386
_database.ProcessStatusVector(_statusVector);
@@ -392,7 +392,7 @@ public override ValueTask PutSegmentAsync(byte[] buffer, CancellationToken cance
392392
_database.FbClient.isc_put_segment(
393393
_statusVector,
394394
ref _blobHandle,
395-
(short)buffer.Length,
395+
(ushort)buffer.Length,
396396
buffer);
397397

398398
_database.ProcessStatusVector(_statusVector);

src/FirebirdSql.Data.FirebirdClient/Client/Native/FesDatabase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ internal sealed class FesDatabase : DatabaseBase
5151

5252
#region Constructors
5353

54-
public FesDatabase(string dllName, Charset charset, int packetSize, short dialect)
55-
: base(charset, packetSize, dialect)
54+
public FesDatabase(string dllName, Charset charset, int packetSize, int blobSegmentSize, short dialect)
55+
: base(charset, packetSize, blobSegmentSize, dialect)
5656
{
5757
_fbClient = FbClientFactory.Create(dllName);
5858
_fbClientVersion = FesConnection.GetClientVersion(_fbClient);

0 commit comments

Comments
 (0)