f5c30ed0ef
v2-ci / unit-tests (push) Has been cancelled
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
113 lines
4.6 KiB
SQL
113 lines
4.6 KiB
SQL
-- Seed for the Sql driver's Oracle live gate (OracleReadTests), applied once by the compose
|
|
-- `oracle-seed` one-shot AS THE APPLICATION USER (otopcua) against the OWNED `otopcua-sql-oracle`
|
|
-- container. Oracle equates a schema with a user, so everything below lands in the OTOPCUA schema.
|
|
--
|
|
-- The table shapes mirror the SQL Server fixture (SqlPollServerFixture) row for row — a present
|
|
-- value, a present row with a NULL cell, and a wide-row table whose NEWEST row is deliberately not
|
|
-- its first — so the same assertions can be made against both backends and a divergence between
|
|
-- the two provider paths shows up as a failing assertion rather than as two suites that quietly
|
|
-- test different things.
|
|
--
|
|
-- ⚠️ EVERY IDENTIFIER HERE IS UNQUOTED, AND THAT IS THE POINT. Oracle upper-cases an unquoted
|
|
-- identifier, so the catalog stores OTOPCUA / TAG_VALUES / TAG_NAME while an operator authoring a
|
|
-- tag in the AdminUI naturally types lower case. The suite's case-folding leg authors the lower-case
|
|
-- spelling and expects `SqlCatalogGate` to resolve it through `SqlCatalog.TryMatch` (exact-ordinal
|
|
-- first, then a UNIQUE case-insensitive match) and substitute the catalog's own UPPER-case spelling
|
|
-- before `OracleDialect.QuoteIdentifier` double-quotes it — which is load-bearing, because a quoted
|
|
-- "tag_values" and a quoted "TAG_VALUES" are DIFFERENT objects to Oracle. Quoting a name here would
|
|
-- make that leg meaningless.
|
|
|
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
|
SET FEEDBACK OFF
|
|
SET DEFINE OFF
|
|
|
|
-- Drop-and-recreate rather than merge-or-insert: the seed is an assertion contract, and a leftover
|
|
-- row from an older revision of this file would make a passing test meaningless. ORA-00942 (table
|
|
-- does not exist) is the expected outcome on a first run and is the only error swallowed.
|
|
BEGIN
|
|
EXECUTE IMMEDIATE 'DROP TABLE tag_values PURGE';
|
|
EXCEPTION
|
|
WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF;
|
|
END;
|
|
/
|
|
|
|
BEGIN
|
|
EXECUTE IMMEDIATE 'DROP TABLE latest_status PURGE';
|
|
EXCEPTION
|
|
WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF;
|
|
END;
|
|
/
|
|
|
|
-- Key-value (EAV) source: tag_name → num_value, with a source timestamp.
|
|
-- num_value is NUMBER, Oracle's one numeric family — which OracleDialect.MapColumnType folds to
|
|
-- Float64 (the documented under-specification; see that method's remarks).
|
|
CREATE TABLE tag_values (
|
|
tag_name VARCHAR2(128) NOT NULL,
|
|
num_value NUMBER,
|
|
sample_ts TIMESTAMP(3) NOT NULL
|
|
);
|
|
|
|
INSERT INTO tag_values (tag_name, num_value, sample_ts)
|
|
VALUES ('Line1.Speed', 42, TIMESTAMP '2026-07-24 10:00:00');
|
|
INSERT INTO tag_values (tag_name, num_value, sample_ts)
|
|
VALUES ('Line1.Temp', NULL, TIMESTAMP '2026-07-24 10:00:01');
|
|
INSERT INTO tag_values (tag_name, num_value, sample_ts)
|
|
VALUES ('Line1.Pressure', 3.5, TIMESTAMP '2026-07-24 10:00:02');
|
|
|
|
-- Wide-row source: one row per station, several value columns. Station 8 is inserted LAST and is
|
|
-- also the newest sample, while station 7 is first — so a topByTimestamp plan that lost its
|
|
-- ORDER BY … DESC (or its FETCH FIRST 1 ROWS ONLY) returns a different, wrong value rather than a
|
|
-- coincidentally right one.
|
|
CREATE TABLE latest_status (
|
|
station_id NUMBER(10) NOT NULL,
|
|
oven_temp NUMBER,
|
|
pressure NUMBER,
|
|
sample_ts TIMESTAMP(3) NOT NULL
|
|
);
|
|
|
|
INSERT INTO latest_status (station_id, oven_temp, pressure, sample_ts)
|
|
VALUES (7, 180.5, 1.2, TIMESTAMP '2026-07-24 10:00:00');
|
|
INSERT INTO latest_status (station_id, oven_temp, pressure, sample_ts)
|
|
VALUES (9, NULL, 1.6, TIMESTAMP '2026-07-24 10:00:01');
|
|
INSERT INTO latest_status (station_id, oven_temp, pressure, sample_ts)
|
|
VALUES (8, 210.25, 1.4, TIMESTAMP '2026-07-24 10:00:02');
|
|
|
|
-- Write-mode targets (lmxopcua#781, SqlWriteModesLiveTests): a primary-keyed setpoint table for Update / Insert /
|
|
-- MERGE, and a procedure that journals its arguments (named notation: P_VALUE / P_KEY / P_TS).
|
|
BEGIN
|
|
EXECUTE IMMEDIATE 'DROP TABLE write_targets PURGE';
|
|
EXCEPTION
|
|
WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF;
|
|
END;
|
|
/
|
|
|
|
BEGIN
|
|
EXECUTE IMMEDIATE 'DROP TABLE proc_calls PURGE';
|
|
EXCEPTION
|
|
WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF;
|
|
END;
|
|
/
|
|
|
|
CREATE TABLE write_targets (
|
|
tag_name VARCHAR2(128) NOT NULL PRIMARY KEY,
|
|
sp_value NUMBER NOT NULL,
|
|
stamp_ts TIMESTAMP(3)
|
|
);
|
|
|
|
INSERT INTO write_targets (tag_name, sp_value, stamp_ts) VALUES ('Present', 1, NULL);
|
|
|
|
CREATE TABLE proc_calls (
|
|
tag_name VARCHAR2(128) NOT NULL,
|
|
sp_value NUMBER,
|
|
stamp_ts TIMESTAMP(3)
|
|
);
|
|
|
|
CREATE OR REPLACE PROCEDURE set_target(p_value IN NUMBER, p_key IN VARCHAR2, p_ts IN TIMESTAMP) AS
|
|
BEGIN
|
|
INSERT INTO proc_calls (tag_name, sp_value, stamp_ts) VALUES (p_key, p_value, p_ts);
|
|
END;
|
|
/
|
|
|
|
COMMIT;
|
|
EXIT;
|