Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.IntegrationTests/Docker/mysql/seed-mysql.sql
T
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

74 lines
3.6 KiB
SQL

-- Seed for the Sql driver's MySQL live gate (MySqlReadTests, lmxopcua#753), applied once by the compose
-- `mysql-seed` one-shot as root against the OWNED `otopcua-sql-mysql` container. `__APP_PASSWORD__` is
-- substituted by the one-shot before this reaches the server.
--
-- Table shapes mirror the SQL Server and PostgreSQL fixtures row for row, so the same assertions hold on all
-- backends. Two things are load-bearing:
--
-- 1. The suite connects as `otopcua`, which holds SELECT on `sqlpoll` and nothing else (plus EXECUTE on the
-- side-effect function, below). A MySQL "schema" is a database, and information_schema shows a principal
-- only the objects it holds a privilege on — so MySqlDialect.ListSchemasSql returning `sqlpoll` for this
-- login is the real provisioning check, not a superuser's view.
-- 2. Every identifier is lower case. The suite authors the table name in UPPER case and expects the catalog
-- gate to resolve it through a unique case-insensitive match: on Linux, MySQL table names are case-
-- SENSITIVE, so without the gate's substitution the quoted upper-case name would name no table.
DROP DATABASE IF EXISTS sqlpoll;
CREATE DATABASE sqlpoll;
USE sqlpoll;
-- Key-value (EAV) source.
CREATE TABLE tag_values (
tag_name varchar(128) NOT NULL,
num_value double NULL,
sample_ts datetime(3) NOT NULL
);
INSERT INTO tag_values (tag_name, num_value, sample_ts) VALUES
('Line1.Speed', 42.0, '2026-07-24 10:00:00'),
('Line1.Temp', NULL, '2026-07-24 10:00:01'),
('Line1.Pressure', 3.5, '2026-07-24 10:00:02');
-- Wide-row source. Station 8 is inserted LAST and is the newest sample; station 7 is first — so a plan that
-- lost its ORDER BY … DESC or its LIMIT 1 returns a different, wrong value.
CREATE TABLE latest_status (
station_id int NOT NULL,
oven_temp double NULL,
pressure double NULL,
sample_ts datetime(3) NOT NULL
);
INSERT INTO latest_status (station_id, oven_temp, pressure, sample_ts) VALUES
(7, 180.5, 1.2, '2026-07-24 10:00:00'),
(9, NULL, 1.6, '2026-07-24 10:00:01'),
(8, 210.25, 1.4, '2026-07-24 10:00:02');
-- Integer-keyed EAV source: an authored text keyValue must compare against an INT key column.
CREATE TABLE int_tag_values (
tag_id int NOT NULL,
num_value double NULL,
sample_ts datetime(3) NOT NULL
);
INSERT INTO int_tag_values (tag_id, num_value, sample_ts) VALUES
(101, 77.5, '2026-07-24 10:00:00'),
(102, 12.0, '2026-07-24 10:00:01');
-- Named-query rollback probe: bump_counter() WRITES a row and returns 1. Called through a SELECT, it passes the
-- read-only guard (nothing lexical says "write"); the driver's always-rolled-back transaction is what must
-- discard the row. InnoDB, so the insert is transactional. SQL SECURITY DEFINER (root), so `otopcua` needs only
-- EXECUTE — it holds no INSERT of its own.
CREATE TABLE side_effects (n int NOT NULL) ENGINE = InnoDB;
DELIMITER //
CREATE FUNCTION bump_counter() RETURNS int
NOT DETERMINISTIC MODIFIES SQL DATA SQL SECURITY DEFINER
BEGIN
INSERT INTO side_effects (n) VALUES (1);
RETURN 1;
END//
DELIMITER ;
-- ── the read-only login ──────────────────────────────────────────────────────────────────────
DROP USER IF EXISTS 'otopcua'@'%';
CREATE USER 'otopcua'@'%' IDENTIFIED BY '__APP_PASSWORD__';
GRANT SELECT ON sqlpoll.* TO 'otopcua'@'%';
GRANT EXECUTE ON FUNCTION sqlpoll.bump_counter TO 'otopcua'@'%';
FLUSH PRIVILEGES;