Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correction of the return values of the DatabaseInfo methods for determining the insert, read and delete operations #1183

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b492cce
Creation of backup and restore statistics can be switched off to supp…
DevM900 Jun 27, 2024
c39be07
Determination of the correct number of reads, inserts, deletes, etc. …
DevM900 Jun 28, 2024
1fd2464
Determination of the correct number of reads, inserts, deletes, etc. …
DevM900 Jun 28, 2024
9112e28
Added comments.
DevM900 Jun 28, 2024
c00d785
Merge branch 'DatabaseInfo' of https://github.com/DevM900/NETProvider…
DevM900 Jun 28, 2024
124e5f3
Type of the key of the result collection changed from "uint" to "short".
DevM900 Jul 4, 2024
c43a1bf
Type of the key of the result collection changed from "uint" to "short".
DevM900 Jul 4, 2024
72ed318
Added tests for determination of the number of reads, inserts, delete…
DevM900 Jul 4, 2024
aa0d9f7
using await
DevM900 Sep 13, 2024
2acda4a
changed to var
DevM900 Sep 13, 2024
7edc7ee
prefix on argument removed,
DevM900 Sep 13, 2024
367af06
extra spaces removed
DevM900 Sep 13, 2024
600fb37
'private' modifier removed
DevM900 Sep 13, 2024
3f1e72c
prefix 'a' on arguments removed
DevM900 Sep 13, 2024
a9311a0
Merge remote-tracking branch 'upstream/master' into DatabaseInfo
DevM900 Nov 4, 2024
cef7087
Typo statistic -> statistics
DevM900 Nov 4, 2024
e720df4
removed not needed "using"
DevM900 Nov 4, 2024
bf09d5a
changed to "var"
DevM900 Nov 4, 2024
fa0f53c
Using async/await in "GetTableNameList"
DevM900 Nov 4, 2024
9470f22
added “using” to ensure that resources are released
DevM900 Nov 4, 2024
187ab1c
moved "trim" to SQL
DevM900 Nov 4, 2024
fe4c525
added missing async/await
DevM900 Nov 6, 2024
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
121 changes: 121 additions & 0 deletions src/FirebirdSql.Data.FirebirdClient.Tests/FbDatabaseInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
Expand Down Expand Up @@ -61,4 +62,124 @@ public void CompleteDatabaseInfoTest()
Assert.DoesNotThrowAsync(() => (Task)m.Invoke(dbInfo, new object[] { CancellationToken.None }), m.Name);
}
}

[Test]
public async Task PerformanceAnalysis_SELECT_Test()
{
var tableNameList = await GetTableNameList();
var tableIdTest = tableNameList["TEST"];

var dbInfo = new FbDatabaseInfo(Connection);
var insertCount = await dbInfo.GetInsertCountAsync();
var updateCount = await dbInfo.GetUpdateCountAsync();
var readSeqCount = await dbInfo.GetReadSeqCountAsync();
var readIdxCount = await dbInfo.GetReadIdxCountAsync();

var fbCommand = new FbCommand("SELECT MAX(INT_FIELD) FROM TEST", Connection);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await using

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not look like it's here.

var maxIntField = await fbCommand.ExecuteScalarAsync() as int?;

insertCount = await GetAffectedTables(insertCount, await dbInfo.GetInsertCountAsync());
updateCount = await GetAffectedTables(updateCount, await dbInfo.GetUpdateCountAsync());
readSeqCount = await GetAffectedTables(readSeqCount, await dbInfo.GetReadSeqCountAsync());
readIdxCount = await GetAffectedTables(readIdxCount, await dbInfo.GetReadIdxCountAsync());

Assert.That(insertCount.ContainsKey(tableIdTest), Is.False);
Assert.That(updateCount.ContainsKey(tableIdTest), Is.False);
Assert.That(readSeqCount.ContainsKey(tableIdTest), Is.True);
Assert.That(readSeqCount[tableIdTest], Is.EqualTo(maxIntField + 1));
Assert.That(readIdxCount.ContainsKey(tableIdTest), Is.False);
}

[Test]
public async Task PerformanceAnalysis_INSERT_Test()
{
var tableNameList = await GetTableNameList();
var tableIdTest = tableNameList["TEST"];

var dbInfo = new FbDatabaseInfo(Connection);
var insertCount = await dbInfo.GetInsertCountAsync();
var updateCount = await dbInfo.GetUpdateCountAsync();
var readSeqCount = await dbInfo.GetReadSeqCountAsync();
var readIdxCount = await dbInfo.GetReadIdxCountAsync();

var fbCommand = new FbCommand("INSERT INTO TEST (INT_FIELD) VALUES (900)", Connection);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await using

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not look like it's here.

await fbCommand.ExecuteNonQueryAsync();

insertCount = await GetAffectedTables(insertCount, await dbInfo.GetInsertCountAsync());
updateCount = await GetAffectedTables(updateCount, await dbInfo.GetUpdateCountAsync());
readSeqCount = await GetAffectedTables(readSeqCount, await dbInfo.GetReadSeqCountAsync());
readIdxCount = await GetAffectedTables(readIdxCount, await dbInfo.GetReadIdxCountAsync());

Assert.That(insertCount.ContainsKey(tableIdTest), Is.True);
Assert.That(insertCount[tableIdTest], Is.EqualTo(1));
Assert.That(updateCount.ContainsKey(tableIdTest), Is.False);
Assert.That(readSeqCount.ContainsKey(tableIdTest), Is.False);
Assert.That(readIdxCount.ContainsKey(tableIdTest), Is.False);
}

[Test]
public async Task PerformanceAnalysis_UPDATE_Test()
{
var tableNameList = await GetTableNameList();
var tableIdTest = tableNameList["TEST"];

var fbCommand = new FbCommand("INSERT INTO TEST (INT_FIELD) VALUES (900)", Connection);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await using

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not look like it's here.

await fbCommand.ExecuteNonQueryAsync();

var dbInfo = new FbDatabaseInfo(Connection);
var insertCount = await dbInfo.GetInsertCountAsync();
var updateCount = await dbInfo.GetUpdateCountAsync();
var readSeqCount = await dbInfo.GetReadSeqCountAsync();
var readIdxCount = await dbInfo.GetReadIdxCountAsync();

fbCommand.CommandText = "UPDATE TEST SET SMALLINT_FIELD = 900 WHERE (INT_FIELD = 900)";
await fbCommand.ExecuteNonQueryAsync();

insertCount = await GetAffectedTables(insertCount, await dbInfo.GetInsertCountAsync());
updateCount = await GetAffectedTables(updateCount, await dbInfo.GetUpdateCountAsync());
readSeqCount = await GetAffectedTables(readSeqCount, await dbInfo.GetReadSeqCountAsync());
readIdxCount = await GetAffectedTables(readIdxCount, await dbInfo.GetReadIdxCountAsync());

Assert.That(insertCount.ContainsKey(tableIdTest), Is.False);
Assert.That(updateCount.ContainsKey(tableIdTest), Is.True);
Assert.That(updateCount[tableIdTest], Is.EqualTo(1));
Assert.That(readSeqCount.ContainsKey(tableIdTest), Is.False);
Assert.That(readIdxCount.ContainsKey(tableIdTest), Is.True);
Assert.That(readIdxCount[tableIdTest], Is.EqualTo(1));
}

async Task<IDictionary<short, ulong>> GetAffectedTables(IDictionary<short, ulong> statisticInfoBefore, IDictionary<short, ulong> statisticInfoAfter)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Task?

{
var result = new Dictionary<short, ulong>();
foreach (var keyValuePair in statisticInfoAfter)
{
if (statisticInfoBefore.TryGetValue(keyValuePair.Key, out var value))
{
var counter = keyValuePair.Value - value;
if (counter > 0)
{
result.Add(keyValuePair.Key, counter);
}
}
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add }/{ for consistency sake.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not look like it's here.

result.Add(keyValuePair.Key, keyValuePair.Value);
}
return await Task.FromResult(result);
}

async Task<IDictionary<string, short>> GetTableNameList()
{
IDictionary<string, short> result = new Dictionary<string, short>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use var.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not look like it's here.

await using (var command = new FbCommand("select R.RDB$RELATION_ID, TRIM(R.RDB$RELATION_NAME) from RDB$RELATIONS R WHERE RDB$SYSTEM_FLAG = 0", Connection))
{
await using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
result.Add(reader.GetString(1), reader.GetInt16(0));
}
}
}
return result;
}
}
14 changes: 14 additions & 0 deletions src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ public static string ToHexString(this byte[] b)
return BitConverter.ToString(b).Replace("-", string.Empty);
}

public static IDictionary<short, ulong> GetTableStatistics(this byte[] b, int length)
{
var capacity = length > 3 ? (length - 3) / 6 + 1 : 0;

var tableStatistics = new Dictionary<short, ulong>(capacity);
for (var i = 3; i < length; i += 6)
{
var tableId = (short)IscHelper.VaxInteger(b, i, 2);
var count = (ulong)IscHelper.VaxInteger(b, i + 2, 4);
tableStatistics.Add(tableId, count);
}
return tableStatistics;
}

public static IEnumerable<IEnumerable<T>> Split<T>(this T[] array, int size)
{
for (var i = 0; i < (float)array.Length / size; i++)
Expand Down
19 changes: 11 additions & 8 deletions src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,6 @@ public static List<object> ParseDatabaseInfo(byte[] buffer, Charset charset)
case IscCodes.isc_info_marks:
case IscCodes.isc_info_reads:
case IscCodes.isc_info_writes:
case IscCodes.isc_info_backout_count:
case IscCodes.isc_info_delete_count:
case IscCodes.isc_info_expunge_count:
case IscCodes.isc_info_insert_count:
case IscCodes.isc_info_purge_count:
case IscCodes.isc_info_read_idx_count:
case IscCodes.isc_info_read_seq_count:
case IscCodes.isc_info_update_count:
case IscCodes.isc_info_db_size_in_pages:
case IscCodes.isc_info_oldest_transaction:
case IscCodes.isc_info_oldest_active:
Expand All @@ -77,6 +69,17 @@ public static List<object> ParseDatabaseInfo(byte[] buffer, Charset charset)
info.Add(VaxInteger(buffer, pos, length));
break;

case IscCodes.isc_info_backout_count:
case IscCodes.isc_info_delete_count:
case IscCodes.isc_info_expunge_count:
case IscCodes.isc_info_insert_count:
case IscCodes.isc_info_purge_count:
case IscCodes.isc_info_update_count:
case IscCodes.isc_info_read_seq_count:
case IscCodes.isc_info_read_idx_count:
info.Add(buffer.GetTableStatistics(length));
break;

case IscCodes.isc_info_no_reserve:
case IscCodes.isc_info_forced_writes:
case IscCodes.isc_info_db_read_only:
Expand Down
130 changes: 97 additions & 33 deletions src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbDatabaseInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -225,76 +224,141 @@ public Task<int> GetWritesAsync(CancellationToken cancellationToken = default)
return GetValueAsync<int>(IscCodes.isc_info_writes, cancellationToken);
}

public int GetBackoutCount()
/// <summary>
/// Returns the number of removals of a version of a record of the affected tables.
/// </summary>
/// <returns>Dictionary with the number of removals, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public IDictionary<short, ulong> GetBackoutCount()
{
return GetValue<int>(IscCodes.isc_info_backout_count);
return GetValue<IDictionary<short, ulong>>(IscCodes.isc_info_backout_count);
}
public Task<int> GetBackoutCountAsync(CancellationToken cancellationToken = default)
/// <summary>
/// Returns the number of removals of a version of a record of the affected tables.
/// </summary>
/// <returns>Dictionary with the number of removals, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public Task<IDictionary<short, ulong>> GetBackoutCountAsync(CancellationToken cancellationToken = default)
{
return GetValueAsync<int>(IscCodes.isc_info_backout_count, cancellationToken);
return GetValueAsync<IDictionary<short, ulong>>(IscCodes.isc_info_backout_count, cancellationToken);
}

public int GetDeleteCount()
/// <summary>
/// Returns the number of database deletes of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of deletes, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public IDictionary<short, ulong> GetDeleteCount()
{
return GetValue<int>(IscCodes.isc_info_delete_count);
return GetValue<IDictionary<short, ulong>>(IscCodes.isc_info_delete_count);
}
public Task<int> GetDeleteCountAsync(CancellationToken cancellationToken = default)
/// <summary>
/// Returns the number of database deletes of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of deletes, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public Task<IDictionary<short, ulong>> GetDeleteCountAsync(CancellationToken cancellationToken = default)
{
return GetValueAsync<int>(IscCodes.isc_info_delete_count, cancellationToken);
return GetValueAsync<IDictionary<short, ulong>>(IscCodes.isc_info_delete_count, cancellationToken);
}

public int GetExpungeCount()
/// <summary>
/// Returns the number of removals of a record and all of its ancestors, for records whose deletions have been committed of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of removals, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public IDictionary<short, ulong> GetExpungeCount()
{
return GetValue<int>(IscCodes.isc_info_expunge_count);
return GetValue<IDictionary<short, ulong>>(IscCodes.isc_info_expunge_count);
}
public Task<int> GetExpungeCountAsync(CancellationToken cancellationToken = default)

/// <summary>
/// Returns the number of removals of a record and all of its ancestors, for records whose deletions have been committed of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of removals, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public Task<IDictionary<short, ulong>> GetExpungeCountAsync(CancellationToken cancellationToken = default)
{
return GetValueAsync<int>(IscCodes.isc_info_expunge_count, cancellationToken);
return GetValueAsync<IDictionary<short, ulong>>(IscCodes.isc_info_expunge_count, cancellationToken);
}

public int GetInsertCount()
/// <summary>
/// Returns the number of inserts into the database of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of inserts, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public IDictionary<short, ulong> GetInsertCount()
{
return GetValue<int>(IscCodes.isc_info_insert_count);
return GetValue<IDictionary<short, ulong>>(IscCodes.isc_info_insert_count);
}
public Task<int> GetInsertCountAsync(CancellationToken cancellationToken = default)
/// <summary>
/// Returns the number of inserts into the database of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of inserts, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public Task<IDictionary<short, ulong>> GetInsertCountAsync(CancellationToken cancellationToken = default)
{
return GetValueAsync<int>(IscCodes.isc_info_insert_count, cancellationToken);
return GetValueAsync<IDictionary<short, ulong>>(IscCodes.isc_info_insert_count, cancellationToken);
}

public int GetPurgeCount()
/// <summary>
/// Returns the number of removals of old versions of fully mature records (records that are committed, so that older ancestor versions are no longer needed) of the affected tables.
/// </summary>
/// <returns>Dictionary with the number of removals, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public IDictionary<short, ulong> GetPurgeCount()
{
return GetValue<int>(IscCodes.isc_info_purge_count);
return GetValue<IDictionary<short, ulong>>(IscCodes.isc_info_purge_count);
}
public Task<int> GetPurgeCountAsync(CancellationToken cancellationToken = default)
/// <summary>
/// Returns the number of removals of old versions of fully mature records (records that are committed, so that older ancestor versions are no longer needed) of the affected tables.
/// </summary>
/// <returns>Dictionary with the number of removals, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public Task<IDictionary<short, ulong>> GetPurgeCountAsync(CancellationToken cancellationToken = default)
{
return GetValueAsync<int>(IscCodes.isc_info_purge_count, cancellationToken);
return GetValueAsync<IDictionary<short, ulong>>(IscCodes.isc_info_purge_count, cancellationToken);
}

public long GetReadIdxCount()
/// <summary>
/// Returns the number of reads done via an index of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of reads, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public IDictionary<short, ulong> GetReadIdxCount()
{
return GetValue<long>(IscCodes.isc_info_read_idx_count);
return GetValue<IDictionary<short, ulong>>(IscCodes.isc_info_read_idx_count);
}
public Task<long> GetReadIdxCountAsync(CancellationToken cancellationToken = default)
/// <summary>
/// Returns the number of reads done via an index of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of reads, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public Task<IDictionary<short, ulong>> GetReadIdxCountAsync(CancellationToken cancellationToken = default)
{
return GetValueAsync<long>(IscCodes.isc_info_read_idx_count, cancellationToken);
return GetValueAsync<IDictionary<short, ulong>>(IscCodes.isc_info_read_idx_count, cancellationToken);
}

public long GetReadSeqCount()
/// <summary>
/// Returns the number of sequential table scans (row reads) of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of reads, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public IDictionary<short, ulong> GetReadSeqCount()
{
return GetValue<long>(IscCodes.isc_info_read_seq_count);
return GetValue<IDictionary<short, ulong>>(IscCodes.isc_info_read_seq_count);
}
public Task<long> GetReadSeqCountAsync(CancellationToken cancellationToken = default)
/// <summary>
/// Returns the number of sequential table scans (row reads) of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of reads, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public Task<IDictionary<short, ulong>> GetReadSeqCountAsync(CancellationToken cancellationToken = default)
{
return GetValueAsync<long>(IscCodes.isc_info_read_seq_count, cancellationToken);
return GetValueAsync<IDictionary<short, ulong>>(IscCodes.isc_info_read_seq_count, cancellationToken);
}

public long GetUpdateCount()
/// <summary>
/// Returns the number of database updates of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of updates, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public IDictionary<short, ulong> GetUpdateCount()
{
return GetValue<long>(IscCodes.isc_info_update_count);
return GetValue<IDictionary<short, ulong>>(IscCodes.isc_info_update_count);
}
public Task<long> GetUpdateCountAsync(CancellationToken cancellationToken = default)
/// <summary>
/// Returns the number of database updates of the affected tables since the database was last attached.
/// </summary>
/// <returns>Dictionary with the number of updates, with table id (field RDB$RELATION_ID of the system table RDB$RELATIONS) as key.</returns>
public Task<IDictionary<short, ulong>> GetUpdateCountAsync(CancellationToken cancellationToken = default)
{
return GetValueAsync<long>(IscCodes.isc_info_update_count, cancellationToken);
return GetValueAsync<IDictionary<short, ulong>>(IscCodes.isc_info_update_count, cancellationToken);
}

public int GetDatabaseSizeInPages()
Expand Down