feat(sql): Odbc provider — ANSI SQL:2008 profile over System.Data.Odbc (lmxopcua#753)
SqlProvider.Odbc is now constructed; every enum member is a working backend. - OdbcDialect: System.Data.Odbc 10.0.14 (no vulnerable packages), ANSI double- quote quoting, positional `?` markers, INFORMATION_SCHEMA catalog (system schemas of the common backends excluded by name — the live gate caught psqlODBC listing pg_catalog), FETCH FIRST n ROWS ONLY limits, a conservative 1000-parameter chunk. Positional binding is safe because every caller adds parameters in marker order (planner IN list, catalog schema-then-table). - Named queries on a positional provider are checked by `?` count against the declared params (literals/comments ignored), since there is no name to match. - Supported backends are those whose SQL accepts that profile (PostgreSQL via psqlODBC proven, Snowflake-like); SQL Server/MySQL/DB2 should use their native provider. The host needs the ODBC driver manager + vendor driver. - Reserved-provider controls in factory/probe/browser tests now use an undeclared ordinal (99); the AdminUI form offers Odbc and a test pins the dropdown theory to SqlDialects.Supported. - Tests: OdbcDialectTests (unit, any host). OdbcReadTests + Docker/odbc/ run-odbc-gate.sh run the built suite in a throwaway Linux SDK container with unixODBC + psqlODBC against a throwaway seeded PostgreSQL: KV IN-list positional binding, where-pair, topByTimestamp via FETCH FIRST, a two- parameter named query, and the browse walk. macOS has no vendor ODBC driver, so there the suite skips. (lmxopcua#753) Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -58,10 +58,12 @@ public sealed class SqlDriverBrowserTests
|
||||
SqlDriverBrowser.DefaultDialectSelector(SqlProvider.Postgres).ShouldBeOfType<PostgresDialect>();
|
||||
SqlDriverBrowser.DefaultDialectSelector(SqlProvider.Oracle).ShouldBeOfType<OracleDialect>();
|
||||
SqlDriverBrowser.DefaultDialectSelector(SqlProvider.MySql).ShouldBeOfType<MySqlDialect>();
|
||||
SqlDriverBrowser.DefaultDialectSelector(SqlProvider.Odbc).ShouldBeOfType<OdbcDialect>();
|
||||
|
||||
SqlProvider[] carried = [SqlProvider.SqlServer, SqlProvider.Postgres, SqlProvider.Oracle, SqlProvider.MySql];
|
||||
foreach (var provider in Enum.GetValues<SqlProvider>().Where(p => !carried.Contains(p)))
|
||||
SqlDriverBrowser.DefaultDialectSelector(provider).ShouldBeNull();
|
||||
// Every declared member is carried now; an undeclared ordinal is the control that must stay null.
|
||||
foreach (var provider in Enum.GetValues<SqlProvider>())
|
||||
SqlDriverBrowser.DefaultDialectSelector(provider).ShouldNotBeNull();
|
||||
SqlDriverBrowser.DefaultDialectSelector((SqlProvider)99).ShouldBeNull();
|
||||
}
|
||||
|
||||
// ---- connection-string reference resolution ----
|
||||
@@ -189,8 +191,8 @@ public sealed class SqlDriverBrowserTests
|
||||
// ---- provider availability ----
|
||||
|
||||
/// <summary>
|
||||
/// The falsifiability control for the widened browse gate: <see cref="SqlProvider.Odbc"/> is still
|
||||
/// reserved and must be reported as unavailable, naming itself.
|
||||
/// The falsifiability control for the widened browse gate: an undeclared provider ordinal must be
|
||||
/// reported as unavailable, naming itself.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Open_providerThisBuildDoesNotCarry_saysNotAvailableInThisBuild()
|
||||
@@ -199,9 +201,9 @@ public sealed class SqlDriverBrowserTests
|
||||
|
||||
var ex = await Should.ThrowAsync<InvalidOperationException>(() =>
|
||||
browser.OpenAsync(
|
||||
ConfigJson("Odbc", connectionStringRef: "MesStaging"), CancellationToken.None));
|
||||
ConfigJson("99", connectionStringRef: "MesStaging"), CancellationToken.None));
|
||||
|
||||
ex.Message.ShouldContain("Odbc");
|
||||
ex.Message.ShouldContain("99");
|
||||
ex.Message.ShouldContain("not available in this build");
|
||||
}
|
||||
|
||||
@@ -217,7 +219,7 @@ public sealed class SqlDriverBrowserTests
|
||||
|
||||
var ex = await Should.ThrowAsync<InvalidOperationException>(() =>
|
||||
browser.OpenAsync(
|
||||
ConfigJson("Odbc", connectionStringRef: "MesStaging"), CancellationToken.None));
|
||||
ConfigJson("99", connectionStringRef: "MesStaging"), CancellationToken.None));
|
||||
|
||||
ex.Message.ShouldContain("not available in this build");
|
||||
reads.ShouldBeEmpty();
|
||||
|
||||
Reference in New Issue
Block a user