Item 2: a tag's optional `write` block picks the statement the driver builds from its own
catalog-validated identifiers (no authored SQL; values always bound):
- Update (default, unchanged back-compat UPDATE ... WHERE key; 0 rows = BadNoData)
- Insert (key+value[+stamp]; append for a WideRow topByTimestamp tag)
- Upsert: SQL Server MERGE WITH (HOLDLOCK), PostgreSQL INSERT ... ON CONFLICT, MySQL
ON DUPLICATE KEY UPDATE (1-or-2 rows), Oracle MERGE from DUAL
- Procedure: named stored procedure resolved against the routine catalog by the catalog gate
(INFORMATION_SCHEMA.ROUTINES / ALL_PROCEDURES), value/key/timestamp params by name (MySQL positional)
- stampTimestamp sets the tag's timestamp column to the write time (UTC)
Odbc serves Update/Insert only (shared SqlWriteSupport table). Constraint failures map to
BadEntryExists (unique) / BadOutOfRange (other integrity, data exception); everything else,
permission denied included, stays BadCommunicationError. WriteIdempotent stays false.
The parser refuses malformed blocks (strict keys/enums/param charset); DraftValidator emits
blocking SqlWriteBlockInvalid / SqlWriteModeUnsupported from the driver's own parse; the AdminUI
editor surfaces the same message.
Items 1 + 4 (docs/drivers/Sql.md): ODBC stays the ANSI SQL:2008 profile (native providers for
SQL Server/MySQL, DB2 unsupported) with an ops note on driver manager + vendor driver on every
Odbc and AdminUI host; read-only drivers require a SELECT-only login, writable drivers a login
limited to their write targets.
Tests: SqlWriteModeTests (parse, every dialect's SQL, gate, status map, SQLite end-to-end),
DraftValidatorSqlWriteBlockTests, AdminUI round-trip; live SqlWriteModesLiveTests on SQL Server,
PostgreSQL, MySQL, Oracle (seeds gain write_targets + set_target) and an Odbc write leg.
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
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