Skip to content

Commit 54702aa

Browse files
committed
Support for SNAPSHOT AT NUMBER and related improvements (#871, #836).
1 parent 5c016f1 commit 54702aa

File tree

16 files changed

+475
-22
lines changed

16 files changed

+475
-22
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* The contents of this file are subject to the Initial
3+
* Developer's Public License Version 1.0 (the "License");
4+
* you may not use this file except in compliance with the
5+
* License. You may obtain a copy of the License at
6+
* https://github.com/FirebirdSQL/NETProvider/raw/master/license.txt.
7+
*
8+
* Software distributed under the License is distributed on
9+
* an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
10+
* express or implied. See the License for the specific
11+
* language governing rights and limitations under the License.
12+
*
13+
* All Rights Reserved.
14+
*/
15+
16+
//$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net)
17+
18+
using System;
19+
using System.Linq;
20+
using System.Reflection;
21+
using System.Threading;
22+
using System.Threading.Tasks;
23+
using FirebirdSql.Data.TestsBase;
24+
using NUnit.Framework;
25+
26+
namespace FirebirdSql.Data.FirebirdClient.Tests;
27+
28+
[TestFixtureSource(typeof(FbServerTypeTestFixtureSource), nameof(FbServerTypeTestFixtureSource.Default))]
29+
[TestFixtureSource(typeof(FbServerTypeTestFixtureSource), nameof(FbServerTypeTestFixtureSource.Embedded))]
30+
public class FbTransactionInfoTests : FbTestsBase
31+
{
32+
public FbTransactionInfoTests(FbServerType serverType, bool compression, FbWireCrypt wireCrypt)
33+
: base(serverType, compression, wireCrypt)
34+
{ }
35+
36+
[Test]
37+
public async Task CompleteTransactionInfoTest()
38+
{
39+
await using (var transaction = await Connection.BeginTransactionAsync())
40+
{
41+
var txInfo = new FbTransactionInfo(transaction);
42+
foreach (var m in txInfo.GetType()
43+
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
44+
.Where(x => !x.IsSpecialName)
45+
.Where(x => x.Name.EndsWith("Async")))
46+
{
47+
if (ServerVersion < new Version(4, 0, 0, 0)
48+
&& new[] {
49+
nameof(FbTransactionInfo.GetTransactionSnapshotNumberAsync),
50+
}.Contains(m.Name))
51+
continue;
52+
53+
Assert.DoesNotThrowAsync(() => (Task)m.Invoke(txInfo, new object[] { CancellationToken.None }), m.Name);
54+
}
55+
}
56+
}
57+
}

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,28 @@ public async Task ReadCommittedReadConsistency()
115115
return;
116116

117117
await using (var transaction = await Connection.BeginTransactionAsync(new FbTransactionOptions() { TransactionBehavior = FbTransactionBehavior.ReadConsistency }))
118+
{ }
119+
}
120+
121+
[Test]
122+
public async Task SnapshotAtNumber()
123+
{
124+
if (!EnsureServerVersionAtLeast(new Version(4, 0, 0, 0)))
125+
return;
126+
127+
await using (var transaction1 = await Connection.BeginTransactionAsync(new FbTransactionOptions() { TransactionBehavior = FbTransactionBehavior.Concurrency }))
118128
{
119-
await transaction.DisposeAsync();
129+
var number1 = await new FbTransactionInfo(transaction1).GetTransactionSnapshotNumberAsync();
130+
Assert.NotZero(number1);
131+
await using (var conn = new FbConnection(BuildConnectionString(ServerType, Compression, WireCrypt)))
132+
{
133+
await conn.OpenAsync();
134+
await using (var transaction2 = await conn.BeginTransactionAsync(new FbTransactionOptions() { TransactionBehavior = FbTransactionBehavior.Concurrency, SnapshotAtNumber = number1 }))
135+
{
136+
var number2 = await new FbTransactionInfo(transaction2).GetTransactionSnapshotNumberAsync();
137+
Assert.AreEqual(number1, number2);
138+
}
139+
}
120140
}
121141
}
122142
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,10 @@ protected virtual async ValueTask<IResponse> ReadSingleResponseAsync(int operati
879879
return response;
880880
}
881881

882+
#endregion
883+
884+
#region Private Methods
885+
882886
private void DatabaseInfo(byte[] items, byte[] buffer, int bufferLength)
883887
{
884888
try

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

Lines changed: 84 additions & 4 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.Collections.Generic;
1920
using System.IO;
2021
using System.Threading;
2122
using System.Threading.Tasks;
@@ -314,10 +315,6 @@ public override async ValueTask RollbackRetainingAsync(CancellationToken cancell
314315
}
315316
}
316317

317-
#endregion
318-
319-
#region Two Phase Commit Methods
320-
321318
public override void Prepare()
322319
{
323320
EnsureActiveTransactionState();
@@ -406,5 +403,88 @@ public override async ValueTask PrepareAsync(byte[] buffer, CancellationToken ca
406403
}
407404
}
408405

406+
public override List<object> GetTransactionInfo(byte[] items)
407+
{
408+
return GetTransactionInfo(items, IscCodes.DEFAULT_MAX_BUFFER_SIZE);
409+
}
410+
public override ValueTask<List<object>> GetTransactionInfoAsync(byte[] items, CancellationToken cancellationToken = default)
411+
{
412+
return GetTransactionInfoAsync(items, IscCodes.DEFAULT_MAX_BUFFER_SIZE, cancellationToken);
413+
}
414+
415+
public override List<object> GetTransactionInfo(byte[] items, int bufferLength)
416+
{
417+
var buffer = new byte[bufferLength];
418+
DatabaseInfo(items, buffer, buffer.Length);
419+
return IscHelper.ParseTransactionInfo(buffer, _database.Charset);
420+
}
421+
public override async ValueTask<List<object>> GetTransactionInfoAsync(byte[] items, int bufferLength, CancellationToken cancellationToken = default)
422+
{
423+
var buffer = new byte[bufferLength];
424+
await DatabaseInfoAsync(items, buffer, buffer.Length, cancellationToken).ConfigureAwait(false);
425+
return IscHelper.ParseTransactionInfo(buffer, _database.Charset);
426+
}
427+
428+
#endregion
429+
430+
#region Private Methods
431+
432+
private void DatabaseInfo(byte[] items, byte[] buffer, int bufferLength)
433+
{
434+
try
435+
{
436+
_database.Xdr.Write(IscCodes.op_info_transaction);
437+
_database.Xdr.Write(_handle);
438+
_database.Xdr.Write(GdsDatabase.Incarnation);
439+
_database.Xdr.WriteBuffer(items, items.Length);
440+
_database.Xdr.Write(bufferLength);
441+
442+
_database.Xdr.Flush();
443+
444+
var response = (GenericResponse)_database.ReadResponse();
445+
446+
var responseLength = bufferLength;
447+
448+
if (response.Data.Length < bufferLength)
449+
{
450+
responseLength = response.Data.Length;
451+
}
452+
453+
Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength);
454+
}
455+
catch (IOException ex)
456+
{
457+
throw IscException.ForIOException(ex);
458+
}
459+
}
460+
private async ValueTask DatabaseInfoAsync(byte[] items, byte[] buffer, int bufferLength, CancellationToken cancellationToken = default)
461+
{
462+
try
463+
{
464+
await _database.Xdr.WriteAsync(IscCodes.op_info_transaction, cancellationToken).ConfigureAwait(false);
465+
await _database.Xdr.WriteAsync(_handle, cancellationToken).ConfigureAwait(false);
466+
await _database.Xdr.WriteAsync(GdsDatabase.Incarnation, cancellationToken).ConfigureAwait(false);
467+
await _database.Xdr.WriteBufferAsync(items, items.Length, cancellationToken).ConfigureAwait(false);
468+
await _database.Xdr.WriteAsync(bufferLength, cancellationToken).ConfigureAwait(false);
469+
470+
await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false);
471+
472+
var response = (GenericResponse)await _database.ReadResponseAsync(cancellationToken).ConfigureAwait(false);
473+
474+
var responseLength = bufferLength;
475+
476+
if (response.Data.Length < bufferLength)
477+
{
478+
responseLength = response.Data.Length;
479+
}
480+
481+
Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength);
482+
}
483+
catch (IOException ex)
484+
{
485+
throw IscException.ForIOException(ex);
486+
}
487+
}
488+
409489
#endregion
410490
}

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

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
using FirebirdSql.Data.Client.Native.Handles;
2525
using System.Threading.Tasks;
2626
using System.Threading;
27+
using System.Net;
28+
using System.Collections.Generic;
2729

2830
namespace FirebirdSql.Data.Client.Native;
2931

@@ -354,10 +356,6 @@ public override ValueTask RollbackRetainingAsync(CancellationToken cancellationT
354356
return ValueTask2.CompletedTask;
355357
}
356358

357-
#endregion
358-
359-
#region Two Phase Commit Methods
360-
361359
public override void Prepare()
362360
{ }
363361
public override ValueTask PrepareAsync(CancellationToken cancellationToken = default)
@@ -372,14 +370,65 @@ public override ValueTask PrepareAsync(byte[] buffer, CancellationToken cancella
372370
return ValueTask2.CompletedTask;
373371
}
374372

373+
public override List<object> GetTransactionInfo(byte[] items)
374+
{
375+
return GetTransactionInfo(items, IscCodes.DEFAULT_MAX_BUFFER_SIZE);
376+
}
377+
public override ValueTask<List<object>> GetTransactionInfoAsync(byte[] items, CancellationToken cancellationToken = default)
378+
{
379+
return GetTransactionInfoAsync(items, IscCodes.DEFAULT_MAX_BUFFER_SIZE, cancellationToken);
380+
}
381+
382+
public override List<object> GetTransactionInfo(byte[] items, int bufferLength)
383+
{
384+
var buffer = new byte[bufferLength];
385+
386+
TransactionInfo(items, buffer, buffer.Length);
387+
388+
return IscHelper.ParseTransactionInfo(buffer, _database.Charset);
389+
}
390+
public override ValueTask<List<object>> GetTransactionInfoAsync(byte[] items, int bufferLength, CancellationToken cancellationToken = default)
391+
{
392+
var buffer = new byte[bufferLength];
393+
394+
TransactionInfo(items, buffer, buffer.Length);
395+
396+
return ValueTask2.FromResult(IscHelper.ParseTransactionInfo(buffer, _database.Charset));
397+
}
398+
375399
#endregion
376400

377401
#region Private Methods
378402

403+
private void TransactionInfo(byte[] items, byte[] buffer, int bufferLength)
404+
{
405+
StatusVectorHelper.ClearStatusVector(_statusVector);
406+
407+
_database.FbClient.isc_transaction_info(
408+
_statusVector,
409+
ref _handle,
410+
(short)items.Length,
411+
items,
412+
(short)bufferLength,
413+
buffer);
414+
415+
ProcessStatusVector();
416+
}
417+
379418
private void ClearStatusVector()
380419
{
381420
Array.Clear(_statusVector, 0, _statusVector.Length);
382421
}
383422

423+
private void ProcessStatusVector()
424+
{
425+
StatusVectorHelper.ProcessStatusVector(_statusVector, _database.Charset, _database.WarningMessage);
426+
}
427+
428+
private void ProcessStatusVector(Charset charset)
429+
{
430+
StatusVectorHelper.ProcessStatusVector(_statusVector, charset, _database.WarningMessage);
431+
}
432+
384433
#endregion
385434
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,5 +251,13 @@ IntPtr fb_dsql_set_timeout(
251251
void isc_get_client_version(
252252
[MarshalAs(UnmanagedType.LPStr)] StringBuilder version);
253253

254+
IntPtr isc_transaction_info(
255+
[In, Out] IntPtr[] statusVector,
256+
[MarshalAs(UnmanagedType.I4)] ref TransactionHandle trHandle,
257+
short itemListBufferLength,
258+
byte[] itemListBuffer,
259+
short resultBufferLength,
260+
byte[] resultBuffer);
261+
254262
#pragma warning restore IDE1006
255263
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* The contents of this file are subject to the Initial
3+
* Developer's Public License Version 1.0 (the "License");
4+
* you may not use this file except in compliance with the
5+
* License. You may obtain a copy of the License at
6+
* https://github.com/FirebirdSQL/NETProvider/raw/master/license.txt.
7+
*
8+
* Software distributed under the License is distributed on
9+
* an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
10+
* express or implied. See the License for the specific
11+
* language governing rights and limitations under the License.
12+
*
13+
* All Rights Reserved.
14+
*/
15+
16+
//$Authors = Jiri Cincura (jiri@cincura.net)
17+
18+
using System;
19+
20+
namespace FirebirdSql.Data.Common;
21+
22+
internal static class InfoValuesHelper
23+
{
24+
public static T ConvertValue<T>(object value) => value is IConvertible ? (T)Convert.ChangeType(value, typeof(T)) : (T)value;
25+
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,34 @@ internal static class IscCodes
10981098

10991099
#endregion
11001100

1101+
#region Transaction information items
1102+
1103+
public const int isc_info_tra_id = 4;
1104+
public const int isc_info_tra_oldest_interesting = 5;
1105+
public const int isc_info_tra_oldest_snapshot = 6;
1106+
public const int isc_info_tra_oldest_active = 7;
1107+
public const int isc_info_tra_isolation = 8;
1108+
public const int isc_info_tra_access = 9;
1109+
public const int isc_info_tra_lock_timeout = 10;
1110+
public const int fb_info_tra_dbpath = 11;
1111+
public const int fb_info_tra_snapshot_number = 12;
1112+
1113+
// isc_info_tra_isolation responses
1114+
public const int isc_info_tra_consistency = 1;
1115+
public const int isc_info_tra_concurrency = 2;
1116+
public const int isc_info_tra_read_committed = 3;
1117+
1118+
// isc_info_tra_read_committed options
1119+
public const int isc_info_tra_no_rec_version = 0;
1120+
public const int isc_info_tra_rec_version = 1;
1121+
public const int isc_info_tra_read_consistency = 2;
1122+
1123+
// isc_info_tra_access responses
1124+
public const int isc_info_tra_readonly = 0;
1125+
public const int isc_info_tra_readwrite = 1;
1126+
1127+
#endregion
1128+
11011129
public static class Batch
11021130
{
11031131
public const int VERSION1 = 1;

0 commit comments

Comments
 (0)