290117660d
- deferment.md: node ACLs no longer authored-but-inert (#520 shipped through stage 7, opt-in Security:Acl:Enabled); failure-visibility trio mostly closed (01/S-1 + 03/S4 fixed, 06/S-1 partial); bulkhead + WriteIdempotentAttribute closed by R2-02; Sql Postgres+Oracle dialects shipped (d54f3c83); akka-hosting partials verified superseded - archreview/00-OVERALL.md: staleness banner on the carried-open block (04/C-1→R2-05, 03/S2+S3→R2-08, 03/P1→R2-07, driver fleet→R2-09/R2-11/#457, hygiene→R2-12, trio per above; what genuinely remains from theme 4) - archreview/plans/STATUS.md: carved out the true exceptions to "every Round-2 live gate GREEN" (R2-03 T11/T12 deferred-live, R2-10 T10 breaker-OPEN never forced); #459 closed by mesh Phase 7 drill D4 (c50ebcf7) - archreview/plans/INTEGRATION-SWEEP-STATUS.md: "follow-ups unstarted" → all 7 shipped as PRs #446–#452 - archreview/06-gateway-integrations.md: 06/S-1 STILL OPEN → PARTIAL (fail-closed advise + unconfirmed meter; residual cross-repo) - 2026-07-31-apikey-acl-enforcement-design.md: status → implemented through stage 7, #520 CLOSED 2026-08-01 - 2026-07-21-per-cluster-mesh-design.md: "Phases 1–7 not started" → PROGRAM COMPLETE 2026-07-24 (Phase 7c50ebcf7) - 2026-07-20-localdb-phase2-live-gate.md: merged as 06b5d81b; open design question shipped by mesh Phase 6 (checks 3/4 still never re-run on the three-mesh topology) - 2026-07-20-localdb-phase1-progress.md: Tasks 10–16 REMAIN → Phase 1 complete, live-gated 8/8, mergedc957db52- 2026-08-06-clusterclient-to-grpc-tracking.md: defect 4 OPEN → fixed byc0475f2d+e54883a8, merged @b360239e- 2026-08-06-clusterclient-to-grpc.md.tasks.json: Task 15 pending → completed (Phase 4 gate PASS 8/8 @0b194003, recordd0ac6fc5) - 2026-07-22-mesh-phase2-live-gate.md: DriverStatusPanel orphan → wired into /hosts by #546 (9420f8ef) - 2026-07-21-issue-485-live-gate.md: pointer from the "NOT fixed here" finding to the #486 fix (DriverHostActor.cs:1696-1717 + FetchAndCache mirror) - 2026-08-05-sql-postgres-oracle.md: chrome/AdminUI gate not deferred — PASSED 2026-08-05, found + fixed the Test-Connect provider gate (4e461951) - v2-ga-lab-gates-plan.md / v2-release-readiness.md / live-hardware-validation-runbooks.md: FOCAS live-CNC smoke #54 RESOLVED on a real 31i-B 2026-06-25 (cnc_rdparam caveat preserved) - Mqtt-Test-Fixture.md: STATE/DCMD "not implemented" → shipped + live-gated (#5082a7d1167, #511857a10b4) - 2026-08-01-continuous-historization-live-gate.md: poison-tag FIFO pin "filed, not fixed" → #538 fixed same day (e7fbe977, gate 4bdce910; Capacity default now 500k) - 2026-05-26-akka-hosting-alignment-plan.md.tasks.json: F8/F9/F10/F13/F14 partial → completed, superseded by the v3 address-space rework (Phase7Composer/ScriptedAlarmActor deleted; evaluators/SDK sinks/security policies production-wired) Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
214 lines
12 KiB
Markdown
214 lines
12 KiB
Markdown
# Sql Postgres + Oracle providers Implementation Plan
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development
|
||
> (opus implementers per user instruction).
|
||
|
||
**Goal:** Make `SqlProvider.Postgres` and `SqlProvider.Oracle` working backends via managed
|
||
ADO.NET providers (Npgsql, Oracle.ManagedDataAccess.Core).
|
||
|
||
**Architecture:** See `docs/plans/2026-08-05-sql-postgres-oracle-design.md` (this branch). Two new
|
||
dialects in the existing `.Dialect` project; dialect-owned parameter markers/bind-names +
|
||
`ConfigureCommand` (Oracle BindByName); provider gates widened; AdminUI dropdown; env-gated docker
|
||
integration suites. ~~Chrome/AdminUI live gate is DEFERRED to the operator.~~ **Update 2026-08-07
|
||
(truth sweep): the chrome/AdminUI gate RAN and PASSED 2026-08-05** — and found a real defect (Test
|
||
Connect was SqlServer-gated for both new providers), fixed in merge `4e461951`; see
|
||
`2026-08-05-sql-postgres-oracle-design.md` §4.3 record.
|
||
|
||
**Tech Stack:** .NET 10, central package management (`Directory.Packages.props`), xUnit v3 +
|
||
Shouldly, SQLite falsifiability control.
|
||
|
||
**Worktree:** `/Users/dohertj2/Desktop/OtOpcUa/.claude/worktrees/sql-multi-provider`
|
||
(branch `feature/sql-multi-provider`). All paths below relative to it.
|
||
|
||
---
|
||
|
||
### Task 0: Package pins
|
||
|
||
**Classification:** trivial
|
||
**Estimated implement time:** ~3 min
|
||
**Parallelizable with:** Task 1, Task 5
|
||
|
||
**Files:**
|
||
- Modify: `Directory.Packages.props`
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect.csproj`
|
||
|
||
Add `PackageVersion` entries for `Npgsql` (alphabetical slot after `MQTTnet`) and
|
||
`Oracle.ManagedDataAccess.Core` (after the `OPCFoundation.*` block) — latest stable on the feed
|
||
(`dotnet package search <id> --exact-match` to confirm; Npgsql 9.x line, ODP.NET Core 23.x line;
|
||
both must target net8.0+). Add both `PackageReference`s (version-less) to the `.Dialect` csproj
|
||
under the existing `Microsoft.Data.SqlClient` reference, extending that ItemGroup's
|
||
rationale comment. `dotnet restore ZB.MOM.WW.OtOpcUa.slnx` must come back clean WITH the audit
|
||
active — if NU1903 fires on a transitive dep, apply the surgical direct-pin pattern documented at
|
||
`Directory.Packages.props:118-145`, never a suppress. Commit
|
||
`build(sql): pin Npgsql + Oracle.ManagedDataAccess.Core (managed providers only)`.
|
||
|
||
### Task 1: Dialect-owned parameter naming + `ConfigureCommand`
|
||
|
||
**Classification:** high-risk
|
||
**Estimated implement time:** ~5 min
|
||
**Parallelizable with:** Task 0, Task 5
|
||
|
||
**Files:**
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/ISqlDialect.cs`
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/SqlServerDialect.cs`
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlGroupPlanner.cs` (:21-25 consts,
|
||
:143, :151, :188)
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlQueryPlan.cs` (bind names alongside
|
||
markers if the plan record needs both)
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlPollReader.cs` (:379-405 — ConfigureCommand
|
||
call + bind-name copy)
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlCatalogLoader.cs` (:97-113, `Bind` :181-188)
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverProbe.cs` (ConfigureCommand)
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Browser/SqlBrowseSession.cs` (ConfigureCommand)
|
||
- Modify: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqliteDialect.cs` (implement new
|
||
members; keep linked copy in Browser.Tests compiling)
|
||
- Test: `SqlGroupPlannerTests.cs`, `SqlPollReaderTests.cs`, `SqlCatalogGateDriverTests.cs` (+ a
|
||
colon-marker fake dialect in the test tree)
|
||
|
||
Per design §2 bullet 2. `ISqlDialect` gains `ParameterMarker(string baseName)`,
|
||
`ParameterBindName(string baseName)`, `ConfigureCommand(DbCommand)`; SqlServerDialect returns
|
||
`"@"+name` / `"@"+name` / no-op — **behaviour today must be byte-identical** (existing planner
|
||
SQL-text assertions must pass untouched; if any needs editing, that is a regression, stop).
|
||
Planner takes markers from its dialect; catalog SQL text and loader bind names route through the
|
||
dialect so the two can never disagree. TDD: the colon-marker fake proves markers flow (observed
|
||
red against the `@k` consts first). Every `CreateCommand` site calls `ConfigureCommand`
|
||
immediately after creation. Commit
|
||
`refactor(sql): dialect-owned parameter markers, bind names and command configuration`.
|
||
|
||
### Task 2: `PostgresDialect`
|
||
|
||
**Classification:** standard
|
||
**Estimated implement time:** ~5 min
|
||
**Parallelizable with:** Task 3, Task 5
|
||
|
||
**Files:**
|
||
- Create: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/PostgresDialect.cs`
|
||
- Test: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/PostgresDialectTests.cs` (new)
|
||
|
||
Per design §2 Postgres bullet. `Factory => NpgsqlFactory.Instance`; `Provider =>
|
||
SqlProvider.Postgres`; `@` markers/bind names; `"`-quoting `""`-escaped, MaxIdentifierLength 63,
|
||
same control-char/Unicode-Format rejection discipline as `SqlServerDialect.QuoteIdentifier`
|
||
(:88-114); `LivenessSql "SELECT 1"`; limit `""`/`" LIMIT 1"`; catalog SQL over
|
||
`information_schema.schemata/tables/columns` excluding `pg_catalog` + `information_schema`,
|
||
`DefaultSchemaSql "SELECT current_schema()"` (parameter markers in catalog SQL via the Task 1
|
||
members); `MapColumnType` per design (unknown → the same fallback SqlServerDialect uses — read it
|
||
first). Tests mirror `SqlServerDialectTests` structure: quoting theory, SQL text, type-map theory,
|
||
marker members. Commit `feat(sql): PostgresDialect (Npgsql)`.
|
||
|
||
### Task 3: `OracleDialect`
|
||
|
||
**Classification:** standard
|
||
**Estimated implement time:** ~5 min
|
||
**Parallelizable with:** Task 2, Task 5
|
||
|
||
**Files:**
|
||
- Create: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/OracleDialect.cs`
|
||
- Test: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/OracleDialectTests.cs` (new)
|
||
|
||
Per design §2 Oracle bullet. `OracleClientFactory.Instance`; `:` markers, **bare** bind names;
|
||
`ConfigureCommand` casts to `OracleCommand` and sets `BindByName = true` (the test constructs a
|
||
real `OracleCommand` and asserts the flag — this is the load-bearing test of the whole branch;
|
||
also assert a foreign `DbCommand` passes through untouched). Quoting `"`/`""`, max 128;
|
||
`LivenessSql "SELECT 1 FROM DUAL"`; limit `""`/`" FETCH FIRST 1 ROWS ONLY"`; catalog SQL:
|
||
`ALL_USERS` schemas, `ALL_TABLES` ∪ `ALL_VIEWS` tables, `ALL_TAB_COLUMNS` columns,
|
||
`sys_context('userenv','current_schema')` default schema — markers via `:schema`/`:table` through
|
||
the Task 1 members; `MapColumnType`: `NUMBER`→Float64 (doc the DeclaredType escape hatch in the
|
||
XML doc comment), `BINARY_DOUBLE`/`BINARY_FLOAT`, `VARCHAR2`/`NVARCHAR2`/`CHAR`/`NCHAR`/`CLOB`,
|
||
`DATE`/`TIMESTAMP*`, `RAW`, plus `FLOAT`/`INTEGER` synonyms. Commit
|
||
`feat(sql): OracleDialect (ODP.NET managed, BindByName)`.
|
||
|
||
### Task 4: Widen the provider gates
|
||
|
||
**Classification:** small
|
||
**Estimated implement time:** ~4 min
|
||
**Parallelizable with:** none
|
||
|
||
**Files:**
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs` (:208-215)
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Browser/SqlDriverBrowser.cs` (:126-127,
|
||
:146-151 comment)
|
||
- Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts/SqlProvider.cs` (doc comments)
|
||
- Test: `SqlDriverFactoryTests.cs` (:133-135 → MySql as rejected control; new: Postgres/Oracle
|
||
construct with their dialects), `SqlDriverBrowserTests.cs` (:188-196 same flip)
|
||
|
||
TDD: red first — author `{"provider":"Postgres"}` and assert construction reaches the
|
||
connection-string resolver (or dialect type assertion via the binding) instead of the
|
||
not-available throw. Rejection message updated to name the three supported providers. Commit
|
||
`feat(sql): construct Postgres and Oracle providers; MySql/Odbc stay reserved`.
|
||
|
||
### Task 5: AdminUI dropdown + docs
|
||
|
||
**Classification:** small
|
||
**Estimated implement time:** ~4 min
|
||
**Parallelizable with:** Task 1, Task 2, Task 3
|
||
|
||
**Files:**
|
||
- Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/Forms/SqlDriverForm.razor`
|
||
(:18-25)
|
||
- Modify: `docs/drivers/Sql.md` (:15-18, :96 provider row, browse section)
|
||
- Test: `tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/` — extend the existing Sql driver-form
|
||
model test file (find `SqlDriverFormModelTests` or nearest equivalent) with round-trip of
|
||
`provider: Postgres` / `Oracle`
|
||
|
||
Dropdown gains `Postgres` + `Oracle` options; helper text → "SQL Server, PostgreSQL and Oracle
|
||
are supported; MySql/Odbc are reserved." Sql.md rewritten per design §5 including the Oracle
|
||
`NUMBER`→Float64 note and the 12.2+ floor. Commit
|
||
`feat(sql-adminui): Postgres + Oracle in the provider dropdown; docs`.
|
||
|
||
### Task 6: Integration fixtures + env-gated suites
|
||
|
||
**Classification:** standard
|
||
**Estimated implement time:** ~5 min
|
||
**Parallelizable with:** none
|
||
|
||
**Files:**
|
||
- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.IntegrationTests/Docker/postgres/docker-compose.yml`
|
||
+ `seed-pg.sql`
|
||
- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.IntegrationTests/Docker/oracle/docker-compose.yml`
|
||
+ `seed-oracle.sql`
|
||
- Create: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.IntegrationTests/PostgresReadTests.cs`,
|
||
`OracleReadTests.cs`
|
||
|
||
Per design §4.2. Compose shapes mirror the existing mssql fixture (`Docker/docker-compose.yml`:
|
||
project label `project: lmxopcua`, one-shot seed service, healthcheck): `postgres:17-alpine`
|
||
port `15432:5432` env `POSTGRES_PASSWORD=${SQL_PG_PASSWORD}`; `gvenzl/oracle-free:23-slim` port
|
||
`11521:1521` env `ORACLE_PASSWORD=${SQL_ORACLE_PASSWORD}` (gvenzl images take `APP_USER`/
|
||
`APP_USER_PASSWORD` for a non-SYS user — use `otopcua`/the password env). Seeds create one
|
||
KeyValue table + one WideRow table with a timestamp column, plus (Oracle) a deliberately
|
||
UPPER-CASE table authored lower-case in the test to pin catalog case-folding. Test classes follow
|
||
`SqlServerReadTests`/`SqlBlackholeTimeoutTests` conventions exactly: env vars
|
||
`SQL_PG_ENDPOINT`/`SQL_PG_USER`/`SQL_PG_PASSWORD`, `SQL_ORACLE_ENDPOINT`/`SQL_ORACLE_USER`/
|
||
`SQL_ORACLE_PASSWORD`; `Assert.SkipWhen` absent; connection strings via each provider's builder
|
||
obtained FROM THE FACTORY (`dialect.Factory.CreateConnectionStringBuilder()`) so the csproj keeps
|
||
its no-direct-provider-reference discipline (`.IntegrationTests.csproj:29-33`). Legs per suite:
|
||
initialize (liveness + catalog), KeyValue read, WideRow topByTimestamp read, browse-session
|
||
walk, case-folding leg. Commit `test(sql): env-gated Postgres + Oracle integration suites`.
|
||
|
||
### Task 7: Full-suite verification + bookkeeping
|
||
|
||
**Classification:** small
|
||
**Estimated implement time:** ~5 min
|
||
**Parallelizable with:** none
|
||
|
||
`dotnet build ZB.MOM.WW.OtOpcUa.slnx` (0 errors), full Sql unit + Browser.Tests + AdminUI.Tests +
|
||
Configuration.Tests (the SqlCredentialGuard suites must stay green), integration suites compile +
|
||
skip clean offline. Update `docs/plans/2026-07-24-driver-expansion-tracking.md` Sql row/deferral
|
||
note. Commit.
|
||
|
||
### Task 8: Fixture deploy + live integration run (main session)
|
||
|
||
**Classification:** standard
|
||
**Estimated implement time:** ~15 min (main session, after merge)
|
||
|
||
Main session, not a subagent: rsync the two fixture dirs to `dohertj2@10.100.0.35:/opt/otopcua-sql-pg/`
|
||
and `/opt/otopcua-sql-oracle/`, `docker compose up -d` each over ssh, then run
|
||
`dotnet test --filter "FullyQualifiedName~PostgresRead|FullyQualifiedName~OracleRead"` with the
|
||
env vars set. (oracle-free first pull is ~1.5 GB — allow time; if the host can't take it, record
|
||
the suite as authored-but-not-yet-run.) Record results in the design doc §Outcome.
|
||
|
||
**Chrome/AdminUI live gate — DEFERRED to the operator.** After merge the operator runs, in a
|
||
chrome-capable session:
|
||
`/superpowers-extended-cc:executing-plans docs/plans/2026-08-05-sql-postgres-oracle.md` — gate
|
||
section: provider dropdown authoring, Test-connect, `/raw` browse + browse-commit against both
|
||
fixtures at `http://localhost:9200`.
|