using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace ScadaLink.ConfigurationDatabase.Migrations { /// public partial class MigrateParametersToJsonSchema : Migration { /// 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":{:{"type":}},"required":[...]} // // Return {type,itemType?} // → {"type":} or {"type":"array","items":{"type":}} // // 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; "); } /// 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."); } } }