tbf/Results/Entities/helpers/DatabaseMigrationHelper.cs
Michal Buzik 52bd0d01ce Restore Genesis communication and Q3 calibration from special branches
A – Registration and execution
- Register GenesisCommunication Factory in the component list.
- Separate the Genesis form and sequence from iPerl communication.

B – Communication activities
- Restore initialization, connection, PCB reading, password and login.
- Include grouped login, mode switching and disconnection.
- Support processing up to 10 slots.

C1 – Input calibration factors
- Read three factors from Water meters / Text1–Text3.
- Validate integer values in the range 1–65535.
- Preserve the default of 15625 when all three fields are empty.

D – Q3 calibration
- Connect the Prepare Q3 → measurement → Write Q3 workflow.
- Add channel processing to FlyingStart and FlyingStartMassCollection.
- Reset previous measurement data and validate calculated factors.
- Mark factors as stored only after StoreCalibration succeeds.

E – Results and database
- Store calibration factors separately for each meter and channel.
- Add result entities, mappings and Q3 data.
- Extend DB.cs / EnsureSchema to create and update the schema.
- Preserve compatibility with the existing binary format.

Validation:
- Debug build and 18 tests passed.
- Simulated communication runs follow the same activity sequence.
- The complete Q3 workflow has not yet been verified on hardware.

Known limitation:
- An inherited mismatch in simulated responses and error propagation
  can produce an incorrect OK result; this change does not fix it.
2026-09-09 15:04:49 +02:00

260 lines
16 KiB
C#

using System;
using MySql.Data.MySqlClient;
using Common;
namespace Results.Entities.helpers
{
/// <summary>
/// Applies small, explicit and backwards-compatible results DB migrations
/// before NHibernate creates its first session factory.
/// </summary>
public static class DatabaseMigrationHelper
{
public static void EnsureSchema(DBType dbType, string connectionString)
{
switch (dbType)
{
case DBType.MySql:
EnsureMySqlSchema(connectionString);
break;
case DBType.SQLite:
EnsureSQLiteSchema(connectionString);
break;
}
}
private static void EnsureMySqlSchema(string connectionString)
{
using (var conn = new MySqlConnection(connectionString))
{
conn.Open();
using (var create = conn.CreateCommand())
{
create.CommandText = "CREATE TABLE IF NOT EXISTS TestRsltCalibFactor (Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, TestRsltId INT NOT NULL DEFAULT 0, WaterMeterPosition INT NOT NULL DEFAULT 0, IsCalibFactorValid INT NOT NULL DEFAULT 0, VolumeStart DOUBLE NOT NULL DEFAULT 0, VolumeEnd DOUBLE NOT NULL DEFAULT 0, CalibFactorIndex INT NOT NULL DEFAULT 0, BaseCalibFactor INT NOT NULL DEFAULT 0, CalculatedCalibFactor INT NOT NULL DEFAULT 0, Stored INT NOT NULL DEFAULT 0, ErrorStr VARCHAR(240) NULL, TimeStart DOUBLE NOT NULL DEFAULT 0, TimeEnd DOUBLE NOT NULL DEFAULT 0, CalibRawStart DOUBLE NOT NULL DEFAULT 0, CalibRawEnd DOUBLE NOT NULL DEFAULT 0, Error DOUBLE NOT NULL DEFAULT 0)";
create.ExecuteNonQuery();
}
EnsureColumnMySql(conn, "TestRsltCalibFactor", "TestRsltId", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "WaterMeterPosition", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "IsCalibFactorValid", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "VolumeStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "VolumeEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "CalibFactorIndex", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "BaseCalibFactor", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "CalculatedCalibFactor", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "Stored", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "ErrorStr", "VARCHAR(240) NULL");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "TimeStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "TimeEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "CalibRawStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "CalibRawEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRsltCalibFactor", "Error", "DOUBLE NOT NULL DEFAULT 0");
using (var create = conn.CreateCommand())
{
create.CommandText = "CREATE TABLE IF NOT EXISTS MeterTestCalibFactorRslt (Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, MeterTestRslt_id INT NOT NULL DEFAULT 0, CalibFactorIndex INT NOT NULL DEFAULT 0, BaseCalibFactor INT NOT NULL DEFAULT 0, CalculatedCalibFactor INT NOT NULL DEFAULT 0, Stored INT NOT NULL DEFAULT 0, ErrorStr VARCHAR(240) NULL, TimeStart DOUBLE NOT NULL DEFAULT 0, TimeEnd DOUBLE NOT NULL DEFAULT 0, CalibRawStart DOUBLE NOT NULL DEFAULT 0, CalibRawEnd DOUBLE NOT NULL DEFAULT 0, Error DOUBLE NOT NULL DEFAULT 0)";
create.ExecuteNonQuery();
}
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "MeterTestRslt_id", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "CalibFactorIndex", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "BaseCalibFactor", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "CalculatedCalibFactor", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "Stored", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "ErrorStr", "VARCHAR(240) NULL");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "TimeStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "TimeEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "CalibRawStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "CalibRawEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "Error", "DOUBLE NOT NULL DEFAULT 0");
// Genesis Q3 result columns.
EnsureColumnMySql(conn, "WaterMeterData", "Q3Channel", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "WaterMeter", "Q3Channel", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestRslt", "Q3Channel", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRslt", "ConductMean", "FLOAT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRslt", "ConductStart", "FLOAT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRslt", "ConductEnd", "FLOAT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRslt", "ConductMin", "FLOAT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "TestRslt", "ConductMax", "FLOAT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "MeterTestRslt", "FlipMode", "INT NULL");
EnsureColumnMySql(conn, "WaterMeter", "CalibFactorNominal", "DOUBLE NOT NULL DEFAULT 4096");
// Nepouzitelne pre teraz - nechavam kod pre buducnost
//EnsureColumnMySql(conn, "MeterTestRslt", "CalibFactor", "FLOAT NOT NULL DEFAULT 0");
//EnsureColumnTypeMySql(conn, "MeterTestRslt", "CalibFactor", "float", "FLOAT NOT NULL DEFAULT 0");
}
}
private static void EnsureColumnTypeMySql(
MySqlConnection conn,
string tableName,
string columnName,
string expectedDataType,
string columnDefinition)
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = @"
SELECT DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = @tableName
AND COLUMN_NAME = @columnName";
cmd.Parameters.AddWithValue("@tableName", tableName);
cmd.Parameters.AddWithValue("@columnName", columnName);
string currentDataType = cmd.ExecuteScalar() as string;
if (string.Equals(currentDataType, expectedDataType, StringComparison.OrdinalIgnoreCase))
return;
}
using (var alter = conn.CreateCommand())
{
alter.CommandText = "ALTER TABLE `" + tableName + "` MODIFY COLUMN `" +
columnName + "` " + columnDefinition;
alter.ExecuteNonQuery();
}
}
private static void EnsureColumnMySql(
MySqlConnection conn,
string tableName,
string columnName,
string columnDefinition)
{
using (var transaction = conn.BeginTransaction())
{
try
{
bool exists;
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = transaction;
cmd.CommandText = @"
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = @tableName
AND COLUMN_NAME = @columnName";
cmd.Parameters.AddWithValue("@tableName", tableName);
cmd.Parameters.AddWithValue("@columnName", columnName);
exists = Convert.ToInt32(cmd.ExecuteScalar()) > 0;
}
if (!exists)
{
using (var alter = conn.CreateCommand())
{
alter.Transaction = transaction;
alter.CommandText = "ALTER TABLE `" + tableName + "` ADD COLUMN `" +
columnName + "` " + columnDefinition;
alter.ExecuteNonQuery();
}
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
private static void EnsureSQLiteSchema(string databaseFile)
{
using (var conn = new System.Data.SQLite.SQLiteConnection("Data Source=" + databaseFile))
{
conn.Open();
using (var create = conn.CreateCommand())
{
create.CommandText = "CREATE TABLE IF NOT EXISTS TestRsltCalibFactor (Id INTEGER PRIMARY KEY AUTOINCREMENT, TestRsltId INT NOT NULL DEFAULT 0, WaterMeterPosition INT NOT NULL DEFAULT 0, IsCalibFactorValid INT NOT NULL DEFAULT 0, VolumeStart DOUBLE NOT NULL DEFAULT 0, VolumeEnd DOUBLE NOT NULL DEFAULT 0, CalibFactorIndex INT NOT NULL DEFAULT 0, BaseCalibFactor INT NOT NULL DEFAULT 0, CalculatedCalibFactor INT NOT NULL DEFAULT 0, Stored INT NOT NULL DEFAULT 0, ErrorStr VARCHAR(240) NULL, TimeStart DOUBLE NOT NULL DEFAULT 0, TimeEnd DOUBLE NOT NULL DEFAULT 0, CalibRawStart DOUBLE NOT NULL DEFAULT 0, CalibRawEnd DOUBLE NOT NULL DEFAULT 0, Error DOUBLE NOT NULL DEFAULT 0)";
create.ExecuteNonQuery();
}
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "TestRsltId", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "WaterMeterPosition", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "IsCalibFactorValid", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "VolumeStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "VolumeEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "CalibFactorIndex", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "BaseCalibFactor", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "CalculatedCalibFactor", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "Stored", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "ErrorStr", "VARCHAR(240) NULL");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "TimeStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "TimeEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "CalibRawStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "CalibRawEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "Error", "DOUBLE NOT NULL DEFAULT 0");
using (var create = conn.CreateCommand())
{
create.CommandText = "CREATE TABLE IF NOT EXISTS MeterTestCalibFactorRslt (Id INTEGER PRIMARY KEY AUTOINCREMENT, MeterTestRslt_id INT NOT NULL DEFAULT 0, CalibFactorIndex INT NOT NULL DEFAULT 0, BaseCalibFactor INT NOT NULL DEFAULT 0, CalculatedCalibFactor INT NOT NULL DEFAULT 0, Stored INT NOT NULL DEFAULT 0, ErrorStr VARCHAR(240) NULL, TimeStart DOUBLE NOT NULL DEFAULT 0, TimeEnd DOUBLE NOT NULL DEFAULT 0, CalibRawStart DOUBLE NOT NULL DEFAULT 0, CalibRawEnd DOUBLE NOT NULL DEFAULT 0, Error DOUBLE NOT NULL DEFAULT 0)";
create.ExecuteNonQuery();
}
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "MeterTestRslt_id", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "CalibFactorIndex", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "BaseCalibFactor", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "CalculatedCalibFactor", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "Stored", "INT NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "ErrorStr", "VARCHAR(240) NULL");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "TimeStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "TimeEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "CalibRawStart", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "CalibRawEnd", "DOUBLE NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "Error", "DOUBLE NOT NULL DEFAULT 0");
// Genesis Q3 result columns.
EnsureColumnSQLite(conn, "WaterMeterData", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "WaterMeter", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestRslt", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestRslt", "FlipMode", "INTEGER NULL");
EnsureColumnSQLite(conn, "WaterMeter", "CalibFactorNominal", "REAL NOT NULL DEFAULT 4096");
}
}
private static void EnsureColumnSQLite(
System.Data.SQLite.SQLiteConnection conn,
string tableName,
string columnName,
string columnDefinition)
{
bool exists = false;
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "PRAGMA table_info(" + tableName + ")";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
if (string.Equals(reader["name"].ToString(), columnName,
StringComparison.OrdinalIgnoreCase))
{
exists = true;
break;
}
}
}
}
if (!exists)
{
using (var alter = conn.CreateCommand())
{
alter.CommandText = "ALTER TABLE " + tableName + " ADD COLUMN " +
columnName + " " + columnDefinition;
alter.ExecuteNonQuery();
}
}
}
}
}