Files
lmxopcua/docs/plans/2026-08-05-sql-postgres-oracle.md
T
Joseph Doherty 290117660d
v2-ci / build (push) Successful in 6m26s
v2-ci / unit-tests (push) Failing after 18m0s
docs: truth sweep — reconcile stale deferment/archreview/plan records with shipped state
- 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 7 c50ebcf7)
- 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, merged c957db52
- 2026-08-06-clusterclient-to-grpc-tracking.md: defect 4 OPEN → fixed by c0475f2d + e54883a8, merged @ b360239e
- 2026-08-06-clusterclient-to-grpc.md.tasks.json: Task 15 pending → completed (Phase 4 gate PASS 8/8 @ 0b194003, record d0ac6fc5)
- 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 (#508 2a7d1167, #511 857a10b4)
- 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
2026-08-07 02:02:54 -04:00

214 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.