feat(sql-adminui): Postgres + Oracle in the provider dropdown; docs
This commit is contained in:
+22
-5
@@ -12,9 +12,20 @@ An ordinary SQL database — typically a MES / LIMS / historian staging table th
|
||||
already writes to. There is no protocol here and nothing to "connect" to a machine: the driver runs
|
||||
`SELECT`s on a timer and turns cells into tag values.
|
||||
|
||||
**v1 constructs SQL Server only** (`Microsoft.Data.SqlClient`). `SqlProvider` also declares
|
||||
`Postgres`, `MySql`, `Odbc` and `Oracle`, but those are **reserved names, not working backends** —
|
||||
authoring one fails the driver rather than silently falling back.
|
||||
**Three providers construct**: `SqlServer` (`Microsoft.Data.SqlClient`), `Postgres`
|
||||
(`Npgsql`) and `Oracle` (ODP.NET managed — **Oracle 12.2 or later**, the floor the
|
||||
`FETCH FIRST` paging needs). `SqlProvider` also declares `MySql` and `Odbc`, but those remain
|
||||
**reserved names, not working backends** — authoring one fails the driver rather than silently
|
||||
falling back.
|
||||
|
||||
Two provider-specific things are worth knowing before you author against them:
|
||||
|
||||
- **Identifier case folds, and the catalog gate absorbs it.** Postgres lower-cases and Oracle
|
||||
upper-cases unquoted identifiers. Every authored identifier is resolved to the catalog's own
|
||||
spelling, so an authored `widgets` binds to Oracle's `WIDGETS` rather than failing.
|
||||
- **Oracle `NUMBER` infers as `Float64`.** Column metadata does not carry precision/scale to the
|
||||
type map, so an integer-shaped `NUMBER` still infers double. Use the per-tag `type` override
|
||||
(see [Optional per-tag fields](#optional-per-tag-fields)) when the OPC UA type matters.
|
||||
|
||||
**Read-only, structurally.** `SqlDriver` does not implement `IWritable`, so no configuration can
|
||||
produce a write. The `allowWrites` config field exists and is **inert**; the factory *warns* when it
|
||||
@@ -94,7 +105,7 @@ ignoring it later.
|
||||
|
||||
| Key | Default | Notes |
|
||||
|---|---|---|
|
||||
| `provider` | `SqlServer` | The only provider v1 constructs. |
|
||||
| `provider` | `SqlServer` | `SqlServer`, `Postgres` or `Oracle`. `MySql` / `Odbc` are reserved and rejected. |
|
||||
| `connectionStringRef` | — | **A name, never a connection string.** See below. |
|
||||
| `defaultPollInterval` | `00:00:05` | Applied to any group without its own interval. |
|
||||
| `operationTimeout` | `00:00:15` | Client-side wall-clock deadline; a breach surfaces `BadTimeout`. |
|
||||
@@ -132,7 +143,9 @@ The two classes of authored string are handled differently, and deliberately so:
|
||||
concatenated into command text.
|
||||
- **Identifier-bearing** fields (`table`, `keyColumn`, `valueColumn`, `timestampColumn`,
|
||||
`columnName`, `whereColumn`, `topByTimestamp`) cannot be parameterised in SQL. They are validated
|
||||
against **`INFORMATION_SCHEMA`** and dialect-quoted before they reach a command.
|
||||
against the live catalog — **`INFORMATION_SCHEMA`** on SQL Server and Postgres, the **`ALL_*`**
|
||||
views (`ALL_USERS` / `ALL_TABLES` ∪ `ALL_VIEWS` / `ALL_TAB_COLUMNS`) on Oracle — and
|
||||
dialect-quoted before they reach a command.
|
||||
|
||||
The parser deliberately does **not** sanitise either kind — sanitising a value would corrupt
|
||||
legitimate data, and sanitising an identifier would mask an authoring error the catalog check reports
|
||||
@@ -152,6 +165,10 @@ can commit columns as tags. A committed leaf carries **schema, table and column*
|
||||
cannot address a SQL tag, and an earlier version that emitted only the column produced tags whose
|
||||
typed editor opened empty.
|
||||
|
||||
Browse works against all three constructed providers; the catalog query is the dialect's own
|
||||
(`INFORMATION_SCHEMA` on SQL Server and Postgres — with `pg_catalog` / `information_schema`
|
||||
themselves excluded — and the `ALL_*` views on Oracle).
|
||||
|
||||
## Authoring in the AdminUI
|
||||
|
||||
- **Driver config** — `/raw` → driver node → *Configure*. Sql is a **single-connection** driver: the
|
||||
|
||||
+5
-2
@@ -18,10 +18,13 @@
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Provider</label>
|
||||
<InputSelect @bind-Value="_form.Provider" @bind-Value:after="EmitAsync" class="form-select form-select-sm">
|
||||
@* v1 constructs only SqlServer; the other SqlProvider members are reserved (P2). *@
|
||||
@* SqlServer, Postgres and Oracle construct a dialect; MySql/Odbc stay reserved
|
||||
SqlProvider members and are deliberately not offered (authoring one fails the driver). *@
|
||||
<option value="@SqlProvider.SqlServer">@SqlProvider.SqlServer</option>
|
||||
<option value="@SqlProvider.Postgres">@SqlProvider.Postgres</option>
|
||||
<option value="@SqlProvider.Oracle">@SqlProvider.Oracle</option>
|
||||
</InputSelect>
|
||||
<div class="form-text">Only Microsoft SQL Server is supported in v1.</div>
|
||||
<div class="form-text">SQL Server, PostgreSQL and Oracle supported; MySql/Odbc reserved.</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="form-label">Connection string ref <span class="text-danger">*</span></label>
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Forms;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Sql;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
|
||||
|
||||
@@ -86,4 +87,47 @@ public sealed class SqlDriverFormContractTests
|
||||
json.ShouldContain("\"SqlServer\"");
|
||||
JsonSerializer.Deserialize<SqlDriverConfigDto>(json, FormOptions)!.Provider.ShouldBe(SqlProvider.SqlServer);
|
||||
}
|
||||
|
||||
// ---- the multi-provider dropdown: every offered option survives the form model's own round-trip ----
|
||||
|
||||
/// <summary>
|
||||
/// The dropdown offers SqlServer + Postgres + Oracle, so each must survive
|
||||
/// <c>FormModel.ToDto()</c> → JSON → <c>FormModel.FromDto()</c> as a NAME string. The razor markup
|
||||
/// itself is not reachable (no bUnit), so this pins the model half of the change: a provider the
|
||||
/// dropdown can select but the form model cannot carry back would silently reset to SqlServer on
|
||||
/// the next modal open.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(SqlProvider.SqlServer, "SqlServer")]
|
||||
[InlineData(SqlProvider.Postgres, "Postgres")]
|
||||
[InlineData(SqlProvider.Oracle, "Oracle")]
|
||||
public void Every_dropdown_provider_round_trips_through_the_form_model(SqlProvider provider, string name)
|
||||
{
|
||||
var form = new SqlDriverForm.FormModel { Provider = provider, ConnectionStringRef = "MesStaging" };
|
||||
|
||||
var json = JsonSerializer.Serialize(form.ToDto(), FormOptions);
|
||||
|
||||
json.ShouldContain($"\"provider\":\"{name}\"");
|
||||
json.ShouldNotContain($"\"provider\":{(int)provider}"); // never an ordinal — that faults the factory read
|
||||
var reloaded = SqlDriverForm.FormModel.FromDto(
|
||||
JsonSerializer.Deserialize<SqlDriverConfigDto>(json, FormOptions)!);
|
||||
reloaded.Provider.ShouldBe(provider);
|
||||
reloaded.ConnectionStringRef.ShouldBe("MesStaging");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MySql and Odbc stay reserved-and-rejected (design §2), so the dropdown deliberately does not offer
|
||||
/// them. Nothing in the form model blocks the value — this records the intent so a later "the enum has
|
||||
/// five members, the dropdown should too" edit has to argue with a test.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(SqlProvider.MySql)]
|
||||
[InlineData(SqlProvider.Odbc)]
|
||||
public void Reserved_providers_are_not_offered_but_still_serialize_by_name(SqlProvider reserved)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(
|
||||
new SqlDriverForm.FormModel { Provider = reserved, ConnectionStringRef = "R" }.ToDto(), FormOptions);
|
||||
|
||||
json.ShouldContain($"\"provider\":\"{reserved}\"");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user