Upgrade ODBC support

This commit is contained in:
Andy Howard
2026-09-15 17:46:16 +01:00
parent 81a933eaa3
commit 0491c9f4b0
7 changed files with 129 additions and 7 deletions
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.Odbc;
using System.Linq;
namespace Sacs.UnitTests.Net.Standard.Base.Dbas
{
@@ -51,6 +52,39 @@ namespace Sacs.UnitTests.Net.Standard.Base.Dbas
Assert.That(odbcConnection.ConnectionString, Is.EqualTo(config.ConnectionString));
}
[Test]
public void OpenOdbcDatabase_CreatesOdbcConnectionForNonSqlDatabase()
{
SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig
{
Dbms = DbmsType.Postgres,
DatabaseName = "TestDb",
ConnectionString = "Driver={PostgreSQL Unicode};Server=localhost;Database=TestDb;"
};
SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config);
using DbConnection connection = database.CreateConnection();
Assert.That(connection, Is.TypeOf<OdbcConnection>());
Assert.That(connection.ConnectionString, Is.EqualTo(config.ConnectionString));
}
[Test]
public void CreateDataAdapter_ReturnsOdbcAdapter()
{
SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig
{
Dbms = DbmsType.General,
DatabaseName = "TestDb",
ConnectionString = "Driver={Test Driver};Database=TestDb;"
};
SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config);
using DbDataAdapter adapter = database.CreateDataAdapter();
Assert.That(adapter, Is.TypeOf<OdbcDataAdapter>());
}
// --- Command Parameters ---
[Test]
@@ -107,6 +141,59 @@ namespace Sacs.UnitTests.Net.Standard.Base.Dbas
Assert.That(command.Parameters[2].DbType, Is.EqualTo(DbType.DateTime2));
}
[Test]
public void AddCommandParameter_CreatesOdbcValuesWithExpectedTypesAndOrder()
{
ParameterTestItem item = new ParameterTestItem();
Guid identifier = Guid.Parse("024f2943-12a6-46be-88cf-49980f81e603");
byte[] data = new byte[] { 1, 2, 3 };
using OdbcCommand command = new OdbcCommand();
item.AddParameter(command, "@Text", "value");
item.AddParameter(command, "@Short", (short)12);
item.AddParameter(command, "@Integer", 34);
item.AddParameter(command, "@Long", 56L);
item.AddParameter(command, "@Byte", (byte)78);
item.AddParameter(command, "@Single", 90.1f);
item.AddParameter(command, "@Double", 23.4d);
item.AddParameter(command, "@Decimal", 78.9m);
item.AddParameter(command, "@Boolean", true);
item.AddParameter(command, "@Identifier", identifier);
item.AddParameter(command, "@Data", data);
Assert.That(command.Parameters.Cast<OdbcParameter>().Select(parameter => parameter.ParameterName),
Is.EqualTo(new[] { "@Text", "@Short", "@Integer", "@Long", "@Byte", "@Single", "@Double", "@Decimal", "@Boolean", "@Identifier", "@Data" }));
Assert.That(command.Parameters.Cast<OdbcParameter>().Select(parameter => parameter.DbType),
Is.EqualTo(new[] { DbType.String, DbType.Int16, DbType.Int32, DbType.Int64, DbType.Byte, DbType.Single, DbType.Double, DbType.Decimal, DbType.Boolean, DbType.Guid, DbType.Binary }));
Assert.That(command.Parameters[0].Value, Is.EqualTo("value"));
Assert.That(command.Parameters[9].Value, Is.EqualTo(identifier));
Assert.That(command.Parameters[10].Value, Is.SameAs(data));
}
[Test]
public void AddCommandParameter_CreatesTypedOdbcNulls()
{
ParameterTestItem item = new ParameterTestItem();
using OdbcCommand command = new OdbcCommand();
item.AddParameter<short?>(command, "@Short", null);
item.AddParameter<int?>(command, "@Integer", null);
item.AddParameter<long?>(command, "@Long", null);
item.AddParameter<byte?>(command, "@Byte", null);
item.AddParameter<float?>(command, "@Single", null);
item.AddParameter<double?>(command, "@Double", null);
item.AddParameter<decimal?>(command, "@Decimal", null);
item.AddParameter<bool?>(command, "@Boolean", null);
item.AddParameter<Guid?>(command, "@Identifier", null);
item.AddParameter<DateTime?>(command, "@Date", null);
item.AddParameter<byte[]>(command, "@Data", null);
Assert.That(command.Parameters.Cast<OdbcParameter>().Select(parameter => parameter.Value),
Is.All.EqualTo(DBNull.Value));
Assert.That(command.Parameters.Cast<OdbcParameter>().Select(parameter => parameter.DbType),
Is.EqualTo(new[] { DbType.Int16, DbType.Int32, DbType.Int64, DbType.Byte, DbType.Single, DbType.Double, DbType.Decimal, DbType.Boolean, DbType.Guid, DbType.DateTime, DbType.Binary }));
}
// --- The End ---
}
}
+34 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common;
@@ -581,7 +581,11 @@ namespace Sarand.Net.Standard.Dbas.Data
DbParameter dbParameter = dbCommand.CreateParameter();
dbParameter.ParameterName = parameterName;
dbParameter.Value = (Object)value ?? DBNull.Value;
if (typeof(ValueType) == typeof(Int16) || typeof(ValueType) == typeof(Int16?))
if (typeof(ValueType) == typeof(String))
{
dbParameter.DbType = System.Data.DbType.String;
}
else if (typeof(ValueType) == typeof(Int16) || typeof(ValueType) == typeof(Int16?))
{
dbParameter.DbType = System.Data.DbType.Int16;
}
@@ -589,6 +593,34 @@ namespace Sarand.Net.Standard.Dbas.Data
{
dbParameter.DbType = System.Data.DbType.Int32;
}
else if (typeof(ValueType) == typeof(Int64) || typeof(ValueType) == typeof(Int64?))
{
dbParameter.DbType = System.Data.DbType.Int64;
}
else if (typeof(ValueType) == typeof(Byte) || typeof(ValueType) == typeof(Byte?))
{
dbParameter.DbType = System.Data.DbType.Byte;
}
else if (typeof(ValueType) == typeof(Single) || typeof(ValueType) == typeof(Single?))
{
dbParameter.DbType = System.Data.DbType.Single;
}
else if (typeof(ValueType) == typeof(Double) || typeof(ValueType) == typeof(Double?))
{
dbParameter.DbType = System.Data.DbType.Double;
}
else if (typeof(ValueType) == typeof(Decimal) || typeof(ValueType) == typeof(Decimal?))
{
dbParameter.DbType = System.Data.DbType.Decimal;
}
else if (typeof(ValueType) == typeof(Boolean) || typeof(ValueType) == typeof(Boolean?))
{
dbParameter.DbType = System.Data.DbType.Boolean;
}
else if (typeof(ValueType) == typeof(Guid) || typeof(ValueType) == typeof(Guid?))
{
dbParameter.DbType = System.Data.DbType.Guid;
}
else if (typeof(ValueType) == typeof(DateTime) || typeof(ValueType) == typeof(DateTime?))
{
if (dbCommand is SqlCommand)
+1 -1
View File
@@ -12,7 +12,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.3" />
<PackageReference Include="System.Data.Odbc" Version="6.0.2" />
<PackageReference Include="System.Data.Odbc" Version="10.0.12" />
</ItemGroup>
<ItemGroup>
+3 -3
View File
@@ -11,10 +11,10 @@ Last updated: 2026-09-15
| Component | Scope | Status | Date reviewed | Next steps | Notes |
|---|---|---|---|---|---|
| `SaCommonVS.sln` | .NET 10 migration build and tests | Migration complete; passing with warnings | 2026-09-15 | Continue the staged package review with the remaining framework-aligned and separately tested major updates. | All supported projects target .NET 10 only. MailKit 4.18.0, DocumentFormat.OpenXml 3.5.1, ZstdNet 1.5.7, and replacement of the obsolete ASP.NET Core 2.3 packages with the .NET 10 shared framework pass the complete validation. The solution builds with zero errors and all 137 tests pass; BusinessMgr builds with 11 Control and 13 Portal tests passing. Xamarin remains isolated and unchanged. |
| `SaCommonVS.sln` | .NET 10 migration build and tests | Migration complete; passing with warnings | 2026-09-15 | Continue the staged package review with the remaining framework-aligned and separately tested major updates. | All supported projects target .NET 10 only. The completed low-risk batch and System.Data.Odbc 10.0.12 pass the complete validation. The solution builds with zero errors and all 141 tests pass; BusinessMgr builds with 11 Control and 13 Portal tests passing. Xamarin remains isolated and unchanged. |
| `SacsStandardBase/Base/Utils/ImageUtils.cs` | ImageSharp wrapper | Covered for package upgrades | 2026-09-14 | Extend the suite when adding formats or changing resize policy. | Five in-memory tests cover PNG and JPEG metadata, invalid and truncated input, SVG recognition, format-preserving encoding, proportional landscape and portrait resizing, no-upscale behavior, and invalid constraints. |
| `SacsStandardBase` JSON utilities and `SysId` converter | Newtonsoft.Json integration | Covered for package upgrades | 2026-09-14 | Retain the suite for future Newtonsoft.Json updates. | Six tests cover objects, arrays, nulls, serializer settings, valid and invalid parsing paths, dynamic access, `SysId` conversion, independent deep copies, nested collections, and constructor-value replacement. |
| `SacsStandardDbas` provider and parameter creation | Microsoft.Data.SqlClient and registry integration | Initial package-upgrade coverage complete | 2026-09-14 | Expand into configured database integration tests before the planned SqlClient major upgrade. | Four database-free tests cover SQL versus forced ODBC connection selection, connection strings, values and nulls, explicit integer and binary types, SQL `DateTime2`, ODBC `DateTime`, precision normalization, and sentinel dates. The redundant Registry 5.0.0 package was removed; .NET 10 supplies the APIs and `NU1510` is gone. |
| `SacsStandardDbas` provider and parameter creation | Microsoft.Data.SqlClient, System.Data.Odbc, and registry integration | ODBC aligned with .NET 10; broader coverage complete | 2026-09-15 | Add optional configured database integration tests before the planned SqlClient major upgrade. | System.Data.Odbc 10.0.12 is covered by eight database-free tests for provider selection, connection strings, adapter creation, parameter order, scalar and null types, SQL `DateTime2`, ODBC `DateTime`, precision normalization, and sentinel dates. Explicit string, `Int64`, `Decimal`, `Boolean`, and `Guid` mappings correct a defect exposed by the expanded suite. |
| `SacsCoreUnitTests` | Test framework, SDK, and coverage tooling | Migrated to NUnit; coverage verified | 2026-09-15 | Use NUnit constraint assertions with `Assert.That` wherever possible. | Uses Microsoft.NET.Test.Sdk 18.10.0, NUnit 4.6.1, NUnit3TestAdapter 6.3.0, NUnit.Analyzers 4.15.0, and coverlet collector 10.0.1. All 126 tests use NUnit's constraint assertion model, pass under NUnit, and produce a non-empty Cobertura report in the optional validation coverage mode. |
| `SacsMobileXamarin` | Migration disposition | Isolated; retained for reference | 2026-09-10 | Keep outside the supported build unless a separately scoped resurrection or migration is approved. | Source is unchanged and available through `SaCommonVS.LegacyXamarin.sln`; see `docs/legacy-xamarin.md`. |
| [`SacsMobileMaui`](../SacsMobileMaui/docs/component-reviews.md) | Project | .NET 10 migration complete; tracked in project docs | 2026-09-11 | Validate Apple targets on a Mac and continue API cleanup in the project tracker. | The shared project and both active mobile consumers target .NET 10. Windows runtime smoke tests pass, and Windows and Android Debug builds complete with zero errors. |
@@ -25,4 +25,4 @@ Last updated: 2026-09-15
| `SacsStandardProc/Proc/Excel/SaMsSpreadsheet_OpenXml.cs` | Class | Dependency upgrade complete; implementation remains incomplete | 2026-09-15 | Scope functional completion and dedicated wrapper tests separately if this implementation is to be revived. | DocumentFormat.OpenXml 3.5.1 builds and passes the existing spreadsheet and downstream validation. The Open XML implementation contains numerous pre-existing unsupported or placeholder operations, so this update does not present it as production-complete. |
| `SacsStandardProc/Proc/Excel/SaMsSpreadsheet_POI.cs` | Class | Dependency upgrade complete | 2026-09-12 | Retain the focused spreadsheet suite for future NPOI updates; do not adopt NPOI 2.8.0 without an explicit licensing decision. | NPOI 2.7.6 is paired with an explicit `System.Security.Cryptography.Xml` 10.0.12 reference and NuGet reports no vulnerable packages for `SacsStandardProc`. Nullable date and indexed-colour API changes were adapted. Ten focused tests and all 111 Common tests pass; BusinessMgr builds and its 21 tests pass. |
| `SacsStandardBase/Base/Helpers/SacsEncryptionHelper.cs` and `SacsStandardBase/Base/Data/SacsAesIvCrypter.cs` | Classes | Reviewed; action required | 2026-09-08 | Design a versioned authenticated-encryption format with legacy migration and tamper tests. | Current CBC encryption is unauthenticated; password-based key derivation uses a fixed salt and low iteration count. |
| `eng/validate.ps1` | Repository validation | Complete; passing | 2026-09-15 | Use as the standard validation entry point and extend it when repository-wide checks change. | Builds serially, runs the 10-test focused NPOI suite and all 137 Common tests, audits the NPOI-owning project, verifies changed-file line endings and diffs, then invokes BusinessMgr validation. |
| `eng/validate.ps1` | Repository validation | Complete; passing | 2026-09-15 | Use as the standard validation entry point and extend it when repository-wide checks change. | Builds serially, runs the 8-test focused ODBC-provider suite, the 10-test focused NPOI suite, and all 141 Common tests; audits both package-owning projects; verifies changed-file line endings and diffs; then invokes BusinessMgr validation. |
+1
View File
@@ -44,6 +44,7 @@ Last updated: 2026-09-15
| Topic | Date noted | Date started | Date completed | Outcome |
|---|---|---|---|---|
| Align System.Data.Odbc with .NET 10 and expand provider coverage | 2026-09-15 | 2026-09-15 | 2026-09-15 | Updated the Common ODBC dependency from 6.0.2 to 10.0.12 and removed six redundant direct references from BusinessMgr consumers. Expanded the database-free provider suite from four to eight tests, covering non-SQL connections, adapter creation, parameter order, scalar types, and typed nulls. The tests exposed and prompted correction of missing explicit `Int64`, `Decimal`, `Boolean`, `Guid`, and string mappings. All 141 Common tests and both BusinessMgr suites pass. |
| Update ZstdNet and cover HTTP decompression | 2026-09-15 | 2026-09-15 | 2026-09-15 | Updated ZstdNet from 1.4.5 to 1.5.7. Added an in-process loopback HTTP test that creates a Zstandard-compressed UTF-8 response and verifies status, headers, native decompression, and decoded content through `SacsHttpClient`. The focused test, all 137 Common tests, and downstream BusinessMgr validation pass. |
| Apply the next low-risk Common package updates | 2026-09-15 | 2026-09-15 | 2026-09-15 | Updated MailKit from 4.17.0 to 4.18.0 and DocumentFormat.OpenXml from 3.3.0 to 3.5.1. Replaced the obsolete ASP.NET Core 2.3 `Http` and `StaticFiles` package references with the .NET 10 `Microsoft.AspNetCore.App` shared-framework reference. Added ten content-type regression cases; email, spreadsheet, all 136 Common tests, and downstream BusinessMgr validation pass. |
| Migrate Common tests from MSTest to NUnit | 2026-09-14 | 2026-09-14 | 2026-09-15 | Replaced MSTest with NUnit 4.6.1, NUnit3TestAdapter 6.3.0, and NUnit.Analyzers 4.15.0. Converted all 27 fixtures and 126 tests to NUnit's constraint-style `Assert.That` model, removed unused MSTest `TestContext` template scaffolding, and brought every migrated fixture into line with the repository's section-comment convention. Full Common and downstream BusinessMgr validation, including coverage collection, passes. |
+1 -1
View File
@@ -10,4 +10,4 @@ Use `-SkipRestore` when packages are already restored. Use `-SkipDownstream` onl
Use `-CollectCoverage` after changing the test platform or coverage collector. It runs an additional Common coverage pass, validates that a non-empty Cobertura report was produced, and passes the same option to downstream BusinessMgr validation.
The script suppresses .NET first-run setup and banners, builds serially to avoid excessive build-worker creation, stores test and optional coverage output under the ignored `TestResults/Validation` folder, audits the NPOI-owning project for vulnerable packages, checks the Git diff, and verifies working-tree line endings without modifying files. Its isolated `DOTNET_CLI_HOME` stores only CLI state; NuGet packages reuse `NUGET_PACKAGES` when already configured or the user's standard `.nuget/packages` cache.
The script suppresses .NET first-run setup and banners, builds serially to avoid excessive build-worker creation, runs the focused ODBC-provider and NPOI spreadsheet suites before the complete test project, stores test and optional coverage output under the ignored `TestResults/Validation` folder, audits the ODBC- and NPOI-owning projects for vulnerable packages, checks the Git diff, and verifies working-tree line endings without modifying files. Its isolated `DOTNET_CLI_HOME` stores only CLI state; NuGet packages reuse `NUGET_PACKAGES` when already configured or the user's standard `.nuget/packages` cache.
+2
View File
@@ -101,6 +101,7 @@ try
}
Invoke-External dotnet @('build', 'SaCommonVS.sln', '--configuration', 'Debug', '--no-restore', '--disable-build-servers', '--verbosity', 'minimal', '--maxcpucount:1')
Invoke-External dotnet @('test', 'SacsCoreUnitTests\SacsCoreUnitTests.csproj', '--configuration', 'Debug', '--no-build', '--no-restore', '--filter', 'FullyQualifiedName~DatabaseProvider_Tester', '--results-directory', $testResultsPath, '--verbosity', 'minimal')
Invoke-External dotnet @('test', 'SacsCoreUnitTests\SacsCoreUnitTests.csproj', '--configuration', 'Debug', '--no-build', '--no-restore', '--filter', 'FullyQualifiedName~SaMsSpreadsheet_POI_Tester', '--results-directory', $testResultsPath, '--verbosity', 'minimal')
Invoke-External dotnet @('test', 'SacsCoreUnitTests\SacsCoreUnitTests.csproj', '--configuration', 'Debug', '--no-build', '--no-restore', '--results-directory', $testResultsPath, '--verbosity', 'minimal')
if ($CollectCoverage)
@@ -109,6 +110,7 @@ try
Invoke-External dotnet @('test', 'SacsCoreUnitTests\SacsCoreUnitTests.csproj', '--configuration', 'Debug', '--no-build', '--no-restore', '--collect', 'XPlat Code Coverage', '--results-directory', $coverageResultsPath, '--verbosity', 'minimal')
Test-CoverageReport $coverageResultsPath
}
Invoke-External dotnet @('list', 'SacsStandardDbas\SacsStandardDbas.csproj', 'package', '--vulnerable', '--include-transitive')
Invoke-External dotnet @('list', 'SacsStandardProc\SacsStandardProc.csproj', 'package', '--vulnerable', '--include-transitive')
Invoke-External git @('diff', '--check')
Test-LineEndings