5 Commits

Author SHA1 Message Date
Joseph Doherty 3c9ab4b668 feat(sql): Odbc provider — ANSI SQL:2008 profile over System.Data.Odbc (lmxopcua#753)
SqlProvider.Odbc is now constructed; every enum member is a working backend.

- OdbcDialect: System.Data.Odbc 10.0.14 (no vulnerable packages), ANSI double-
  quote quoting, positional `?` markers, INFORMATION_SCHEMA catalog (system
  schemas of the common backends excluded by name — the live gate caught
  psqlODBC listing pg_catalog), FETCH FIRST n ROWS ONLY limits, a conservative
  1000-parameter chunk. Positional binding is safe because every caller adds
  parameters in marker order (planner IN list, catalog schema-then-table).
- Named queries on a positional provider are checked by `?` count against the
  declared params (literals/comments ignored), since there is no name to match.
- Supported backends are those whose SQL accepts that profile (PostgreSQL via
  psqlODBC proven, Snowflake-like); SQL Server/MySQL/DB2 should use their native
  provider. The host needs the ODBC driver manager + vendor driver.
- Reserved-provider controls in factory/probe/browser tests now use an
  undeclared ordinal (99); the AdminUI form offers Odbc and a test pins the
  dropdown theory to SqlDialects.Supported.
- Tests: OdbcDialectTests (unit, any host). OdbcReadTests + Docker/odbc/
  run-odbc-gate.sh run the built suite in a throwaway Linux SDK container with
  unixODBC + psqlODBC against a throwaway seeded PostgreSQL: KV IN-list
  positional binding, where-pair, topByTimestamp via FETCH FIRST, a two-
  parameter named query, and the browse walk. macOS has no vendor ODBC driver,
  so there the suite skips.

(lmxopcua#753)

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 17:30:31 -05:00
Joseph Doherty 63372e757a feat(sql): MySql provider over MySqlConnector, one shared provider map (lmxopcua#753)
SqlProvider.MySql is now a constructed backend instead of a reserved name.

- MySqlDialect (Driver.Sql.Dialect): MySqlConnector 2.6.2 (MIT, fully managed,
  no vulnerable packages), backtick quoting with MySQL's 64-char / no-trailing-
  space rules, information_schema catalog SQL with the database as the schema
  (system databases excluded, aliases pinned), trailing LIMIT, a type map that
  also reads the reader-side spellings (INT UNSIGNED -> UInt32, BOOL -> Boolean).
- SqlDialects: the one provider -> dialect map. The factory, the Test Connect
  probe and the browse picker each carried their own switch (and drifted once);
  they now all read this, and their refusals list the supported/reserved sets
  from it.
- AdminUI driver form offers MySql.
- Tests: MySqlDialectTests (golden quoting/catalog/type map/planner SQL); the
  provider-gate tests in factory, probe, browser and form now treat MySql as
  supported and Odbc as the reserved control. MySqlReadTests + Docker/mysql
  (throwaway mysql:8.4, SELECT-only login): liveness, KV/wide-row/topByTimestamp,
  INT keys vs authored text, case folding against case-sensitive table names,
  catalog rejection, browse walk, factory+probe, named query with a bound
  parameter, and a writing stored function whose effect the rolled-back
  transaction discards.

(lmxopcua#753)

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 17:30:31 -05:00
Joseph Doherty 073930e023 fix(sql): withhold the JSON parser's message from the probe's red result (Driver.Sql-009)
SqlDriverProbe surfaced 'Config JSON is invalid: ' + ex.Message, where
SqlDriverBrowser.ParseDocument deliberately withholds the identical message
because the malformed text may itself carry a connection string. The two read
the same blob shape - the AdminUI Sql picker composes a document with a
top-level connectionString literal - so a malformed paste reaches both. The
probe now reports the failure class only, matching the browser.
2026-08-10 07:56:58 -04:00
Joseph Doherty 75a057f268 fix(sql): Test Connect must construct Postgres and Oracle dialects too
SqlDriverProbe.DefaultDialectFor was left SqlServer-only when the provider
gates were widened, so AdminUI Test Connect failed for both new providers
with InvalidOperationException while Deploy and browse worked. Found by the
live chrome/AdminUI gate the plan deferred, not by any test: every case in
SqlDriverProbeTests builds its probe through ForTest(), which injects the
dialect and never reaches the production switch.

Widen the switch arm-for-arm with SqlDriverFactoryExtensions.CreateDialect,
and cover the gate directly via an internal seam — plus a reserved-provider
falsification control and an end-to-end production-constructor case.
2026-08-05 07:43:31 -04:00
Joseph Doherty 4c716242ac feat(sql): SqlDriverProbe SELECT-1 liveness check
The AdminUI "Test Connect" probe for the Sql driver: open a connection, run
the dialect's LivenessSql (SELECT 1) under a linked-CTS deadline, return
green with latency or red with a reason. Implements IDriverProbe; never
throws (malformed JSON, missing connectionStringRef, unprovisioned
connection string, a provider open failure, timeout, cancellation all
become red results).

Parses the SAME factory DTO with the SAME JsonStringEnumConverter as
SqlDriverFactoryExtensions (R2-11 factory parity, mirroring
ModbusDriverProbe) and resolves connectionStringRef the same way, so a
config that Test-Connects is the config that Deploys.

Credential hygiene: the resolved connection string never reaches the result
message. A provider exception can embed the data source in its OWN message
(the real SqlException shape), so the catch-all names the exception TYPE
only, never ex.Message. The regression test's fake connection embeds the
connection string in its thrown message specifically so surfacing ex.Message
would leak it — verified load-bearing by breaking the guard and watching it
go red.

ForTest injects factory+dialect for the offline SQLite fixture path.

Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
2026-07-24 15:11:32 -04:00