-- 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 and UPDATE on the one write-target table, 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 ; -- Write target (lmxopcua#753): the ONLY table the suite login may UPDATE. `dup` deliberately holds two rows -- for one key, so a write keyed on it matches two rows and must be rolled back. tag_values stays SELECT-only, -- so a write tag pointed there proves the server's own grant refuses it. CREATE TABLE setpoints ( tag_name varchar(128) NOT NULL, sp_value double NULL ) ENGINE = InnoDB; INSERT INTO setpoints (tag_name, sp_value) VALUES ('Line1.SpeedSp', 10.0), ('Line1.TempSp', 20.0), ('dup', 1.0), ('dup', 2.0); -- Write-mode targets (lmxopcua#781, SqlWriteModesLiveTests): a primary-keyed setpoint table — the key ON DUPLICATE -- KEY UPDATE fires on — and a DEFINER procedure that journals its arguments (MySQL CALL is positional: value, key, -- write time, the order the driver passes them). CREATE TABLE write_targets ( tag_name varchar(128) NOT NULL PRIMARY KEY, sp_value double NOT NULL, stamp_ts datetime(3) NULL ) ENGINE = InnoDB; INSERT INTO write_targets (tag_name, sp_value, stamp_ts) VALUES ('Present', 1.0, NULL); CREATE TABLE proc_calls ( tag_name varchar(128) NOT NULL, sp_value double NULL, stamp_ts datetime(3) NULL ) ENGINE = InnoDB; DELIMITER // CREATE PROCEDURE set_target(IN p_value double, IN p_key varchar(128), IN p_ts datetime(3)) MODIFIES SQL DATA SQL SECURITY DEFINER BEGIN INSERT INTO proc_calls (tag_name, sp_value, stamp_ts) VALUES (p_key, p_value, p_ts); 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'@'%'; GRANT UPDATE ON sqlpoll.setpoints TO 'otopcua'@'%'; GRANT INSERT, UPDATE ON sqlpoll.write_targets TO 'otopcua'@'%'; GRANT EXECUTE ON PROCEDURE sqlpoll.set_target TO 'otopcua'@'%'; FLUSH PRIVILEGES;