feat(sql): construct Postgres and Oracle providers; MySql/Odbc stay reserved
Widens the two provider gates so an authored `{"provider":"Postgres"}` or
`{"provider":"Oracle"}` binds that provider's own dialect instead of failing
"not available in this build":
- `SqlDriverFactoryExtensions.CreateDialect` gains a Postgres and an Oracle
arm. There is deliberately no fallback arm — a default resolving to
SqlServerDialect would construct happily and then emit T-SQL at a Postgres
or Oracle server. The rejection message now names the three supported and
the two still-reserved providers.
- `SqlDriverBrowser.DefaultDialectSelector` widens identically, so a provider
the runtime constructs is also browsable; MySql/Odbc keep resolving to null
and the caller keeps reporting them as unavailable.
Tests: the existing rejected-provider controls flip from Postgres to MySql
(factory) and Odbc (browser) so the reserved half stays falsifiable, and the
new legs assert the bound dialect TYPE rather than mere construction —
"no throw" alone would pass against a SqlServer fallback.
Doc-comment sweep (contract addition, one line each): the "v1 constructs
SqlServer only" / "reserved for P2" claims in SqlProvider, SqlDriverConfigDto,
ISqlDialect.Provider and SqlServerDialect are now false and were corrected in
the same change.
This commit is contained in:
@@ -122,6 +122,11 @@ public sealed class SqlDriverFactoryTests
|
||||
|
||||
// ---- provider gate ----
|
||||
|
||||
/// <summary>
|
||||
/// The falsifiability control for the widened gate: <see cref="SqlProvider.MySql"/> is still a
|
||||
/// reserved name and must fail loudly at construction, naming itself. Without this leg, a gate that
|
||||
/// had quietly become "construct anything" would pass every test below.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CreateInstance_aProviderThisBuildCannotConstruct_throwsSayingSo()
|
||||
{
|
||||
@@ -130,12 +135,73 @@ public sealed class SqlDriverFactoryTests
|
||||
SqlConnectionStringResolver.EnvironmentVariableFor(name), "Host=h;Database=db");
|
||||
|
||||
var ex = Should.Throw<InvalidOperationException>(() => SqlDriverFactoryExtensions.CreateInstance(
|
||||
"d1", $$"""{"provider":"Postgres","connectionStringRef":"{{name}}"}""", null));
|
||||
"d1", $$"""{"provider":"MySql","connectionStringRef":"{{name}}"}""", null));
|
||||
|
||||
ex.Message.ShouldContain("Postgres");
|
||||
ex.Message.ShouldContain("MySql");
|
||||
ex.Message.ShouldContain("not available in this build");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The reserved names are named as reserved — an operator reading the rejection must be able to tell
|
||||
/// "misspelled" from "this build does not carry it", so the message enumerates both halves.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CreateInstance_aReservedProvider_namesTheSupportedAndTheReservedSets()
|
||||
{
|
||||
var name = NewRef();
|
||||
using var _ = new EnvironmentVariableScope(
|
||||
SqlConnectionStringResolver.EnvironmentVariableFor(name), "Host=h;Database=db");
|
||||
|
||||
var ex = Should.Throw<InvalidOperationException>(() => SqlDriverFactoryExtensions.CreateInstance(
|
||||
"d1", $$"""{"provider":"Odbc","connectionStringRef":"{{name}}"}""", null));
|
||||
|
||||
ex.Message.ShouldContain(nameof(SqlProvider.SqlServer));
|
||||
ex.Message.ShouldContain(nameof(SqlProvider.Postgres));
|
||||
ex.Message.ShouldContain(nameof(SqlProvider.Oracle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The widened gate, asserted on the thing that actually varies: which dialect the config binds to.
|
||||
/// Binding — not merely "no throw" — is what proves the provider reaches the poll path, since a gate
|
||||
/// that fell through to <c>SqlServerDialect</c> would also construct without complaint and then speak
|
||||
/// T-SQL at a Postgres server.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("SqlServer", typeof(SqlServerDialect))]
|
||||
[InlineData("Postgres", typeof(PostgresDialect))]
|
||||
[InlineData("Oracle", typeof(OracleDialect))]
|
||||
public void ParseBinding_aSupportedProvider_bindsThatProvidersDialect(string provider, Type expected)
|
||||
{
|
||||
var name = NewRef();
|
||||
using var _ = new EnvironmentVariableScope(
|
||||
SqlConnectionStringResolver.EnvironmentVariableFor(name), "Host=h;Database=db");
|
||||
|
||||
var binding = SqlDriverFactoryExtensions.ParseBinding(
|
||||
"d1", $$"""{"provider":"{{provider}}","connectionStringRef":"{{name}}"}""");
|
||||
|
||||
binding.Dialect.ShouldBeOfType(expected);
|
||||
binding.Dialect.Provider.ShouldBe(Enum.Parse<SqlProvider>(provider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end through the registered factory entry point, so the widening is proven on the path the
|
||||
/// bootstrapper actually calls — not only on the internal parse.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("Postgres")]
|
||||
[InlineData("Oracle")]
|
||||
public async Task CreateInstance_aNewlySupportedProvider_constructsPastTheProviderGate(string provider)
|
||||
{
|
||||
var name = NewRef();
|
||||
using var _ = new EnvironmentVariableScope(
|
||||
SqlConnectionStringResolver.EnvironmentVariableFor(name), "Host=h;Database=db");
|
||||
|
||||
await using var driver = SqlDriverFactoryExtensions.CreateInstance(
|
||||
"d1", $$"""{"provider":"{{provider}}","connectionStringRef":"{{name}}"}""", null);
|
||||
|
||||
driver.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
// ---- config validation (the reader and the shell deliberately do NOT do this) ----
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user