Files
scadalink-design/src/ScadaLink.ConfigurationDatabase/Migrations/20260512211204_MigrateParametersToJsonSchema.cs
Joseph Doherty 783da8e21a feat(ui): structured editors for script schemas and alarm triggers
Replace raw-JSON text inputs with rich UI: script parameter/return types use
a JSON Schema builder (SchemaBuilder + JsonSchemaShapeParser, with a migration
to convert existing definitions); alarm trigger config uses a type-aware
editor with a flattened attribute picker (AlarmTriggerEditor). AlarmActor
gains optional direction (rising/falling/either) on RateOfChange triggers.
2026-05-13 00:33:00 -04:00

197 lines
7.0 KiB
C#

using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ScadaLink.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class MigrateParametersToJsonSchema : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Convert legacy flat-shape parameter / return JSON in TemplateScripts,
// SharedScripts, and ApiMethods to JSON Schema.
//
// Parameters [{name,type,required,itemType?}]
// → {"type":"object","properties":{<name>:{"type":<jsType>}},"required":[...]}
//
// Return {type,itemType?}
// → {"type":<jsType>} or {"type":"array","items":{"type":<inner>}}
//
// Idempotent: only rows whose value starts with '[' (parameters) or that
// contain the legacy 'List' sentinel (return) are touched. Already-converted
// rows are skipped.
migrationBuilder.Sql(@"
IF OBJECT_ID('dbo.fn_LegacyTypeToJsonSchemaType', 'FN') IS NOT NULL
DROP FUNCTION dbo.fn_LegacyTypeToJsonSchemaType;
");
migrationBuilder.Sql(@"
CREATE FUNCTION dbo.fn_LegacyTypeToJsonSchemaType(@legacy NVARCHAR(50))
RETURNS NVARCHAR(50)
AS
BEGIN
RETURN
CASE LOWER(ISNULL(@legacy, 'string'))
WHEN 'boolean' THEN 'boolean'
WHEN 'bool' THEN 'boolean'
WHEN 'integer' THEN 'integer'
WHEN 'int' THEN 'integer'
WHEN 'int32' THEN 'integer'
WHEN 'int64' THEN 'integer'
WHEN 'float' THEN 'number'
WHEN 'double' THEN 'number'
WHEN 'decimal' THEN 'number'
WHEN 'number' THEN 'number'
WHEN 'string' THEN 'string'
WHEN 'datetime' THEN 'string'
WHEN 'object' THEN 'object'
WHEN 'list' THEN 'array'
WHEN 'array' THEN 'array'
ELSE 'string'
END;
END;
");
migrationBuilder.Sql(@"
IF OBJECT_ID('dbo.fn_LegacyParametersToJsonSchema', 'FN') IS NOT NULL
DROP FUNCTION dbo.fn_LegacyParametersToJsonSchema;
");
migrationBuilder.Sql(@"
CREATE FUNCTION dbo.fn_LegacyParametersToJsonSchema(@legacy NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
IF @legacy IS NULL OR LTRIM(@legacy) = '' RETURN NULL;
IF LEFT(LTRIM(@legacy), 1) <> '[' RETURN @legacy; -- already schema-shaped
DECLARE @props NVARCHAR(MAX) = (
SELECT STRING_AGG(
CONCAT(
'""',
STRING_ESCAPE(JSON_VALUE(p.value, '$.name'), 'json'),
'"":',
CASE
WHEN LOWER(ISNULL(JSON_VALUE(p.value, '$.type'), 'string')) IN ('list', 'array')
THEN CONCAT(
'{""type"":""array"",""items"":{""type"":""',
dbo.fn_LegacyTypeToJsonSchemaType(JSON_VALUE(p.value, '$.itemType')),
'""}}')
ELSE CONCAT(
'{""type"":""',
dbo.fn_LegacyTypeToJsonSchemaType(JSON_VALUE(p.value, '$.type')),
'""}')
END),
',')
WITHIN GROUP (ORDER BY p.[key])
FROM OPENJSON(@legacy) p
WHERE JSON_VALUE(p.value, '$.name') IS NOT NULL
AND JSON_VALUE(p.value, '$.name') <> ''
);
DECLARE @required NVARCHAR(MAX) = (
SELECT STRING_AGG(
CONCAT('""', STRING_ESCAPE(JSON_VALUE(p.value, '$.name'), 'json'), '""'),
',')
WITHIN GROUP (ORDER BY p.[key])
FROM OPENJSON(@legacy) p
WHERE JSON_VALUE(p.value, '$.name') IS NOT NULL
AND JSON_VALUE(p.value, '$.name') <> ''
AND LOWER(ISNULL(JSON_VALUE(p.value, '$.required'), 'true')) <> 'false'
);
RETURN
'{""type"":""object"",""properties"":{' + ISNULL(@props, '') + '}'
+ CASE WHEN @required IS NULL OR @required = '' THEN ''
ELSE ',""required"":[' + @required + ']'
END
+ '}';
END;
");
migrationBuilder.Sql(@"
IF OBJECT_ID('dbo.fn_LegacyReturnToJsonSchema', 'FN') IS NOT NULL
DROP FUNCTION dbo.fn_LegacyReturnToJsonSchema;
");
migrationBuilder.Sql(@"
CREATE FUNCTION dbo.fn_LegacyReturnToJsonSchema(@legacy NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
IF @legacy IS NULL OR LTRIM(@legacy) = '' RETURN NULL;
IF LEFT(LTRIM(@legacy), 1) <> '{' RETURN @legacy;
DECLARE @legacyType NVARCHAR(50) = JSON_VALUE(@legacy, '$.type');
IF @legacyType IS NULL RETURN @legacy;
-- Already JSON Schema (lowercase types, no itemType legacy sentinel): leave it.
IF @legacyType IN ('boolean','integer','number','string','object','array')
AND JSON_VALUE(@legacy, '$.itemType') IS NULL
RETURN @legacy;
IF LOWER(@legacyType) = 'list'
BEGIN
DECLARE @inner NVARCHAR(50) =
dbo.fn_LegacyTypeToJsonSchemaType(JSON_VALUE(@legacy, '$.itemType'));
RETURN CONCAT('{""type"":""array"",""items"":{""type"":""', @inner, '""}}');
END;
RETURN CONCAT('{""type"":""', dbo.fn_LegacyTypeToJsonSchemaType(@legacyType), '""}');
END;
");
migrationBuilder.Sql(@"
UPDATE TemplateScripts
SET ParameterDefinitions = dbo.fn_LegacyParametersToJsonSchema(ParameterDefinitions)
WHERE ParameterDefinitions IS NOT NULL
AND LEFT(LTRIM(ParameterDefinitions), 1) = '[';
UPDATE TemplateScripts
SET ReturnDefinition = dbo.fn_LegacyReturnToJsonSchema(ReturnDefinition)
WHERE ReturnDefinition IS NOT NULL
AND LEFT(LTRIM(ReturnDefinition), 1) = '{';
UPDATE SharedScripts
SET ParameterDefinitions = dbo.fn_LegacyParametersToJsonSchema(ParameterDefinitions)
WHERE ParameterDefinitions IS NOT NULL
AND LEFT(LTRIM(ParameterDefinitions), 1) = '[';
UPDATE SharedScripts
SET ReturnDefinition = dbo.fn_LegacyReturnToJsonSchema(ReturnDefinition)
WHERE ReturnDefinition IS NOT NULL
AND LEFT(LTRIM(ReturnDefinition), 1) = '{';
UPDATE ApiMethods
SET ParameterDefinitions = dbo.fn_LegacyParametersToJsonSchema(ParameterDefinitions)
WHERE ParameterDefinitions IS NOT NULL
AND LEFT(LTRIM(ParameterDefinitions), 1) = '[';
UPDATE ApiMethods
SET ReturnDefinition = dbo.fn_LegacyReturnToJsonSchema(ReturnDefinition)
WHERE ReturnDefinition IS NOT NULL
AND LEFT(LTRIM(ReturnDefinition), 1) = '{';
");
migrationBuilder.Sql(@"
DROP FUNCTION IF EXISTS dbo.fn_LegacyParametersToJsonSchema;
DROP FUNCTION IF EXISTS dbo.fn_LegacyReturnToJsonSchema;
DROP FUNCTION IF EXISTS dbo.fn_LegacyTypeToJsonSchemaType;
");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// Lossy: JSON Schema can express fields (descriptions, defaults, enums,
// nested objects) that the legacy flat shape cannot represent. Reverse
// migration is not supported.
throw new System.NotSupportedException(
"Reverse migration from JSON Schema to legacy flat shape is not supported because the conversion is lossy.");
}
}
}