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:
@@ -80,8 +80,8 @@ public sealed class SqlDriverBrowser : IDriverBrowser
|
||||
/// <param name="dialectSelector">
|
||||
/// Maps an authored <see cref="SqlProvider"/> onto the dialect that browses it, or <see langword="null"/>
|
||||
/// for a provider this build does not carry. Defaults to <see cref="DefaultDialectSelector"/>
|
||||
/// (SqlServer only, in v1). A constructor parameter rather than a test hatch: it is also how a P2
|
||||
/// provider gets added without touching this class.
|
||||
/// (SqlServer, Postgres and Oracle). A constructor parameter rather than a test hatch: it is also how
|
||||
/// a further provider gets added without touching this class.
|
||||
/// </param>
|
||||
/// <param name="environmentReader">
|
||||
/// Reads one environment variable. Defaults to <see cref="Environment.GetEnvironmentVariable(string)"/>.
|
||||
@@ -117,14 +117,23 @@ public sealed class SqlDriverBrowser : IDriverBrowser
|
||||
ConnectionStringEnvironmentPrefix + connectionStringRef;
|
||||
|
||||
/// <summary>
|
||||
/// The providers this build can browse. v1 constructs SQL Server only; every other
|
||||
/// <see cref="SqlProvider"/> member is a reserved name, so it resolves to <see langword="null"/> and
|
||||
/// the caller reports it as unavailable rather than crashing on a null dialect.
|
||||
/// The providers this build can browse — SQL Server, PostgreSQL and Oracle. The remaining
|
||||
/// <see cref="SqlProvider"/> members (<see cref="SqlProvider.MySql"/>, <see cref="SqlProvider.Odbc"/>)
|
||||
/// are reserved names, so they resolve to <see langword="null"/> and the caller reports them as
|
||||
/// unavailable rather than crashing on a null dialect.
|
||||
/// <para>Kept in step with <c>SqlDriverFactoryExtensions.CreateDialect</c>: a provider the runtime
|
||||
/// driver constructs but the browser cannot select would leave the operator authoring tags by hand
|
||||
/// against a database the AdminUI refuses to show them.</para>
|
||||
/// </summary>
|
||||
/// <param name="provider">The authored provider.</param>
|
||||
/// <returns>The dialect, or <see langword="null"/> when this build does not carry the provider.</returns>
|
||||
public static ISqlDialect? DefaultDialectSelector(SqlProvider provider) =>
|
||||
provider == SqlProvider.SqlServer ? new SqlServerDialect() : null;
|
||||
public static ISqlDialect? DefaultDialectSelector(SqlProvider provider) => provider switch
|
||||
{
|
||||
SqlProvider.SqlServer => new SqlServerDialect(),
|
||||
SqlProvider.Postgres => new PostgresDialect(),
|
||||
SqlProvider.Oracle => new OracleDialect(),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <exception cref="InvalidOperationException">
|
||||
@@ -143,12 +152,13 @@ public sealed class SqlDriverBrowser : IDriverBrowser
|
||||
var literal = ReadPastedConnectionString(configJson);
|
||||
var config = Deserialize(configJson, literal);
|
||||
|
||||
// Provider first: "this build cannot browse Postgres" is true regardless of how the database is
|
||||
// Provider first: "this build cannot browse MySql" is true regardless of how the database is
|
||||
// named, and answering it before touching the environment keeps the two failures from masking.
|
||||
var dialect = _dialectSelector(config.Provider)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Sql provider '{config.Provider}' is not available in this build; v1 browses "
|
||||
+ $"'{SqlProvider.SqlServer}' only.");
|
||||
$"Sql provider '{config.Provider}' is not available in this build; browsing supports "
|
||||
+ $"'{SqlProvider.SqlServer}', '{SqlProvider.Postgres}' and '{SqlProvider.Oracle}' "
|
||||
+ $"('{SqlProvider.MySql}' and '{SqlProvider.Odbc}' are reserved names).");
|
||||
|
||||
var reference = config.ConnectionStringRef;
|
||||
var connectionString = ResolveConnectionString(literal, reference);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
|
||||
/// </summary>
|
||||
public sealed class SqlDriverConfigDto
|
||||
{
|
||||
/// <summary>Which ADO.NET provider/dialect backs this instance. Defaults to <see cref="SqlProvider.SqlServer"/> — the only provider v1 constructs.</summary>
|
||||
/// <summary>Which ADO.NET provider/dialect backs this instance. Defaults to <see cref="SqlProvider.SqlServer"/>; <see cref="SqlProvider.Postgres"/> and <see cref="SqlProvider.Oracle"/> are also constructed.</summary>
|
||||
public SqlProvider Provider { get; init; } = SqlProvider.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
|
||||
|
||||
/// <summary>Which ADO.NET provider/dialect backs a Sql driver instance. v1 constructs only SqlServer.</summary>
|
||||
/// <summary>
|
||||
/// Which ADO.NET provider/dialect backs a Sql driver instance. <b>Constructed:</b> SqlServer, Postgres,
|
||||
/// Oracle — each over a fully managed provider package, no native client install. <b>Reserved:</b> MySql
|
||||
/// and Odbc are declared names only; authoring one fails at driver construction and at browse.
|
||||
/// </summary>
|
||||
public enum SqlProvider
|
||||
{
|
||||
/// <summary>Microsoft SQL Server via <c>Microsoft.Data.SqlClient</c> — the only provider v1 constructs.</summary>
|
||||
/// <summary>Microsoft SQL Server via <c>Microsoft.Data.SqlClient</c>.</summary>
|
||||
SqlServer,
|
||||
|
||||
/// <summary>PostgreSQL (Npgsql) — reserved for P2; not constructed in v1.</summary>
|
||||
/// <summary>PostgreSQL via the managed <c>Npgsql</c> provider.</summary>
|
||||
Postgres,
|
||||
|
||||
/// <summary>MySQL / MariaDB — reserved; not constructed in v1.</summary>
|
||||
/// <summary>MySQL / MariaDB — reserved; no dialect ships, so this is not constructed.</summary>
|
||||
MySql,
|
||||
|
||||
/// <summary>Generic ODBC — reserved for P2; not constructed in v1.</summary>
|
||||
/// <summary>Generic ODBC — reserved; no dialect ships, so this is not constructed.</summary>
|
||||
Odbc,
|
||||
|
||||
/// <summary>Oracle — reserved; not constructed in v1.</summary>
|
||||
/// <summary>Oracle Database via the fully managed <c>Oracle.ManagedDataAccess.Core</c> provider.</summary>
|
||||
Oracle,
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,11 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Sql;
|
||||
/// </summary>
|
||||
public interface ISqlDialect
|
||||
{
|
||||
/// <summary>Which backend this dialect speaks. v1 constructs <see cref="SqlProvider.SqlServer"/> only.</summary>
|
||||
/// <summary>
|
||||
/// Which backend this dialect speaks. One implementation ships per constructed
|
||||
/// <see cref="SqlProvider"/> member — <see cref="SqlProvider.SqlServer"/>,
|
||||
/// <see cref="SqlProvider.Postgres"/> and <see cref="SqlProvider.Oracle"/>.
|
||||
/// </summary>
|
||||
SqlProvider Provider { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,7 +7,7 @@ using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql;
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft SQL Server dialect (design §2.2) — the only provider v1 constructs. Bracket quoting,
|
||||
/// Microsoft SQL Server dialect (design §2.2) — the default provider. Bracket quoting,
|
||||
/// <c>INFORMATION_SCHEMA</c> catalog SQL, and the design §3.7 type-family map.
|
||||
/// <para>Stateless and immutable, so a single instance is safely shared by the driver's poll reader and
|
||||
/// the AdminUI browse session.</para>
|
||||
|
||||
@@ -201,17 +201,27 @@ public static class SqlDriverFactoryExtensions
|
||||
|
||||
/// <summary>
|
||||
/// Selects the provider seam.
|
||||
/// <para>v1 constructs <see cref="SqlProvider.SqlServer"/> only. The other members exist on the shipped
|
||||
/// enum as reserved names; authoring one is a config mistake that must fail at construction with a
|
||||
/// message that says so, rather than silently degrading to T-SQL against a non-SQL-Server database.</para>
|
||||
/// <para>This build constructs <see cref="SqlProvider.SqlServer"/>, <see cref="SqlProvider.Postgres"/>
|
||||
/// and <see cref="SqlProvider.Oracle"/>. <see cref="SqlProvider.MySql"/> and
|
||||
/// <see cref="SqlProvider.Odbc"/> remain reserved names on the shipped enum; authoring one is a config
|
||||
/// mistake that must fail at construction with a message that says so, rather than silently degrading
|
||||
/// to another backend's SQL dialect against a database that does not speak it.</para>
|
||||
/// <para><b>Every arm returns that provider's OWN dialect — there is no fallback arm.</b> A default
|
||||
/// that resolved to <see cref="SqlServerDialect"/> would construct happily and then emit T-SQL
|
||||
/// (<c>INFORMATION_SCHEMA</c>, <c>TOP 1</c>, <c>@</c> markers) at a Postgres or Oracle server, which
|
||||
/// fails at poll time as an unintelligible provider error rather than here as a config error.</para>
|
||||
/// </summary>
|
||||
private static ISqlDialect CreateDialect(SqlProvider provider, string driverInstanceId)
|
||||
=> provider switch
|
||||
{
|
||||
SqlProvider.SqlServer => new SqlServerDialect(),
|
||||
SqlProvider.Postgres => new PostgresDialect(),
|
||||
SqlProvider.Oracle => new OracleDialect(),
|
||||
_ => throw new InvalidOperationException(
|
||||
$"Sql driver config for '{driverInstanceId}' names provider '{provider}', which is not " +
|
||||
$"available in this build. Only '{nameof(SqlProvider.SqlServer)}' is supported."),
|
||||
$"available in this build. Supported: '{nameof(SqlProvider.SqlServer)}', " +
|
||||
$"'{nameof(SqlProvider.Postgres)}', '{nameof(SqlProvider.Oracle)}'. Reserved (declared on " +
|
||||
$"the enum but not implemented): '{nameof(SqlProvider.MySql)}', '{nameof(SqlProvider.Odbc)}'."),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user