C#程序实现将Teradata的存储过程转换为Snowflake的sql的存储过程

构建一个完整的Teradata到Snowflake存储过程转换工具,按照步骤生成C#代码。

第一步:项目结构和基础类

1.1 项目配置文件 (TeradataToSnowflakeConverter.csproj)

xml 复制代码
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
    <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
  </ItemGroup>

</Project>

1.2 基础模型类 (Models.cs)

csharp 复制代码
using System.Collections.Generic;

namespace TeradataToSnowflakeConverter
{
    // 存储过程基本信息
    public class StoredProcedureInfo
    {
        public string Name { get; set; } = string.Empty;
        public List<ParameterInfo> Parameters { get; set; } = new List<ParameterInfo>();
        public string Body { get; set; } = string.Empty;
        public string ReturnType { get; set; } = string.Empty;
    }

    // 参数信息
    public class ParameterInfo
    {
        public string Name { get; set; } = string.Empty;
        public string DataType { get; set; } = string.Empty;
        public string Direction { get; set; } = "IN"; // IN, OUT, INOUT
        public string DefaultValue { get; set; } = string.Empty;
    }

    // 转换配置
    public class ConversionConfig
    {
        public bool GenerateComments { get; set; } = true;
        public bool PreserveOriginalFormatting { get; set; } = false;
        public string Indentation { get; set; } = "    ";
        public bool ValidateSyntax { get; set; } = true;
    }

    // 转换结果
    public class ConversionResult
    {
        public bool Success { get; set; }
        public string ConvertedCode { get; set; } = string.Empty;
        public List<string> Warnings { get; set; } = new List<string>();
        public List<string> Errors { get; set; } = new List<string>();
        public List<ConversionLog> Logs { get; set; } = new List<ConversionLog>();
    }

    public class ConversionLog
    {
        public string Type { get; set; } = string.Empty; // INFO, WARNING, ERROR
        public string Message { get; set; } = string.Empty;
        public int LineNumber { get; set; }
    }
}

第二步:语法分析器

2.1 Teradata语法分析器 (TeradataSyntaxParser.cs)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace TeradataToSnowflakeConverter.Parsers
{
    public class TeradataSyntaxParser
    {
        public StoredProcedureInfo ParseProcedure(string teradataCode)
        {
            var procedureInfo = new StoredProcedureInfo();
            
            // 提取存储过程名称
            procedureInfo.Name = ExtractProcedureName(teradataCode);
            
            // 提取参数
            procedureInfo.Parameters = ExtractParameters(teradataCode);
            
            // 提取过程体
            procedureInfo.Body = ExtractProcedureBody(teradataCode);
            
            return procedureInfo;
        }

        private string ExtractProcedureName(string code)
        {
            var match = Regex.Match(code, 
                @"REPLACE\s+PROCEDURE\s+([a-zA-Z_][a-zA-Z0-9_]*)", 
                RegexOptions.IgnoreCase);
            return match.Success ? match.Groups[1].Value : "UNKNOWN_PROCEDURE";
        }

        private List<ParameterInfo> ExtractParameters(string code)
        {
            var parameters = new List<ParameterInfo>();
            
            // 匹配参数部分
            var paramMatch = Regex.Match(code, 
                @"REPLACE\s+PROCEDURE\s+[^(]*\(([^)]*)\)", 
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
            
            if (paramMatch.Success)
            {
                var paramText = paramMatch.Groups[1].Value;
                var paramLines = paramText.Split(',');
                
                foreach (var line in paramLines)
                {
                    var param = ParseParameter(line.Trim());
                    if (param != null)
                        parameters.Add(param);
                }
            }
            
            return parameters;
        }

        private ParameterInfo ParseParameter(string paramLine)
        {
            if (string.IsNullOrWhiteSpace(paramLine)) return null;
            
            var param = new ParameterInfo();
            
            // 解析参数方向 (IN, OUT, INOUT)
            if (paramLine.ToUpper().StartsWith("INOUT"))
            {
                param.Direction = "INOUT";
                paramLine = paramLine.Substring(5).Trim();
            }
            else if (paramLine.ToUpper().StartsWith("OUT"))
            {
                param.Direction = "OUT";
                paramLine = paramLine.Substring(3).Trim();
            }
            else if (paramLine.ToUpper().StartsWith("IN"))
            {
                param.Direction = "IN";
                paramLine = paramLine.Substring(2).Trim();
            }
            
            // 解析参数名称和类型
            var parts = paramLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length >= 2)
            {
                param.Name = parts[0];
                param.DataType = parts[1];
                
                // 处理默认值
                if (paramLine.ToUpper().Contains("DEFAULT"))
                {
                    var defaultMatch = Regex.Match(paramLine, @"DEFAULT\s+(.+)", RegexOptions.IgnoreCase);
                    if (defaultMatch.Success)
                        param.DefaultValue = defaultMatch.Groups[1].Value;
                }
            }
            
            return param;
        }

        private string ExtractProcedureBody(string code)
        {
            // 提取BEGIN和END之间的内容
            var beginMatch = Regex.Match(code, @"BEGIN\s*(.*?)\s*END;?", 
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
            
            return beginMatch.Success ? beginMatch.Groups[1].Value.Trim() : code;
        }

        public List<string> IdentifyControlStructures(string procedureBody)
        {
            var structures = new List<string>();
            
            // 识别IF语句
            var ifMatches = Regex.Matches(procedureBody, 
                @"IF\s+.*?\s+THEN|ELSEIF\s+.*?\s+THEN|ELSE|END\s+IF", 
                RegexOptions.IgnoreCase);
            
            foreach (Match match in ifMatches)
            {
                structures.Add(match.Value);
            }
            
            // 识别循环语句
            var loopMatches = Regex.Matches(procedureBody, 
                @"FOR\s+.*?\s+IN\s+.*?\s+DO|WHILE\s+.*?\s+DO|REPEAT|UNTIL\s+.*?\s+END\s+REPEAT", 
                RegexOptions.IgnoreCase);
            
            foreach (Match match in loopMatches)
            {
                structures.Add(match.Value);
            }
            
            return structures;
        }

        public List<string> ExtractCursorDeclarations(string procedureBody)
        {
            var cursors = new List<string>();
            
            var cursorMatches = Regex.Matches(procedureBody, 
                @"DECLARE\s+.*?\s+CURSOR\s+FOR\s+.*?;", 
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
            
            foreach (Match match in cursorMatches)
            {
                cursors.Add(match.Value);
            }
            
            return cursors;
        }
    }
}

第三步:数据类型映射器

3.1 数据类型转换器 (DataTypeMapper.cs)

csharp 复制代码
using System;
using System.Collections.Generic;

namespace TeradataToSnowflakeConverter.Mappers
{
    public static class DataTypeMapper
    {
        private static readonly Dictionary<string, string> TypeMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            // 数值类型
            {"INTEGER", "INTEGER"},
            {"INT", "INT"},
            {"SMALLINT", "SMALLINT"},
            {"BIGINT", "BIGINT"},
            {"DECIMAL", "DECIMAL"},
            {"NUMERIC", "NUMERIC"},
            {"FLOAT", "FLOAT"},
            {"REAL", "REAL"},
            {"DOUBLE PRECISION", "DOUBLE"},
            
            // 字符类型
            {"CHAR", "CHAR"},
            {"VARCHAR", "VARCHAR"},
            {"LONG VARCHAR", "STRING"},
            {"CLOB", "STRING"},
            {"JSON", "VARIANT"},
            
            // 日期时间类型
            {"DATE", "DATE"},
            {"TIME", "TIME"},
            {"TIMESTAMP", "TIMESTAMP"},
            {"TIMESTAMP WITH TIME ZONE", "TIMESTAMP_TZ"},
            
            // 二进制类型
            {"BYTE", "BINARY"},
            {"VARBYTE", "BINARY"},
            {"BLOB", "BINARY"},
        };

        private static readonly Dictionary<string, string> FunctionMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            // 字符串函数
            {"SUBSTR", "SUBSTRING"},
            {"SUBSTRING", "SUBSTRING"},
            {"TRIM", "TRIM"},
            {"LTRIM", "LTRIM"},
            {"RTRIM", "RTRIM"},
            {"UPPER", "UPPER"},
            {"LOWER", "LOWER"},
            {"CONCAT", "CONCAT"},
            {"CHAR_LENGTH", "LENGTH"},
            {"POSITION", "POSITION"},
            
            // 日期函数
            {"EXTRACT", "EXTRACT"},
            {"CURRENT_DATE", "CURRENT_DATE"},
            {"CURRENT_TIME", "CURRENT_TIME"},
            {"CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP"},
            {"ADD_MONTHS", "DATEADD(MONTH,"},
            {"MONTHS_BETWEEN", "DATEDIFF(MONTH,"},
            
            // 数值函数
            {"ROUND", "ROUND"},
            {"CEIL", "CEIL"},
            {"FLOOR", "FLOOR"},
            {"ABS", "ABS"},
            {"MOD", "MOD"},
            
            // 转换函数
            {"CAST", "CAST"},
            {"TO_CHAR", "TO_CHAR"},
            {"TO_DATE", "TO_DATE"},
            {"TO_NUMBER", "TO_NUMBER"}
        };

        public static string MapDataType(string teradataType)
        {
            if (string.IsNullOrWhiteSpace(teradataType))
                return "VARCHAR";
            
            // 处理带长度的类型,如 VARCHAR(100)
            var baseType = teradataType.Split('(')[0].Trim().ToUpper();
            
            if (TypeMapping.ContainsKey(baseType))
            {
                // 保留原类型的长度和精度信息
                if (teradataType.Contains('('))
                {
                    var parenContent = teradataType.Substring(teradataType.IndexOf('('));
                    return TypeMapping[baseType] + parenContent;
                }
                return TypeMapping[baseType];
            }
            
            return "VARCHAR"; // 默认映射
        }

        public static string MapFunction(string teradataFunction)
        {
            if (string.IsNullOrWhiteSpace(teradataFunction))
                return teradataFunction;
            
            var baseFunction = teradataFunction.Split('(')[0].Trim().ToUpper();
            
            return FunctionMapping.ContainsKey(baseFunction) ? 
                FunctionMapping[baseFunction] : teradataFunction;
        }

        public static bool IsNumericType(string dataType)
        {
            var numericTypes = new HashSet<string> { "INT", "INTEGER", "SMALLINT", "BIGINT", "DECIMAL", "NUMERIC", "FLOAT", "REAL", "DOUBLE" };
            var baseType = dataType.Split('(')[0].Trim().ToUpper();
            return numericTypes.Contains(baseType);
        }

        public static bool IsStringType(string dataType)
        {
            var stringTypes = new HashSet<string> { "CHAR", "VARCHAR", "LONG VARCHAR", "CLOB", "STRING" };
            var baseType = dataType.Split('(')[0].Trim().ToUpper();
            return stringTypes.Contains(baseType);
        }
    }
}

第四步:语法转换器

4.1 基础语法转换器 (SyntaxConverter.cs)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using TeradataToSnowflakeConverter.Mappers;

namespace TeradataToSnowflakeConverter.Converters
{
    public class SyntaxConverter
    {
        private readonly ConversionConfig _config;
        private readonly ConversionResult _result;

        public SyntaxConverter(ConversionConfig config)
        {
            _config = config;
            _result = new ConversionResult();
        }

        public ConversionResult ConvertProcedure(StoredProcedureInfo procedureInfo)
        {
            try
            {
                var snowflakeCode = new StringBuilder();
                
                // 生成存储过程头部
                snowflakeCode.AppendLine(GenerateProcedureHeader(procedureInfo));
                snowflakeCode.AppendLine("AS");
                snowflakeCode.AppendLine("$$");
                snowflakeCode.AppendLine();
                
                // 生成变量声明部分
                snowflakeCode.AppendLine(GenerateVariableDeclarations(procedureInfo.Body));
                
                // 生成过程体
                snowflakeCode.AppendLine(ConvertProcedureBody(procedureInfo.Body));
                
                snowflakeCode.AppendLine();
                snowflakeCode.AppendLine("$$");
                snowflakeCode.AppendLine(";");
                
                _result.Success = true;
                _result.ConvertedCode = snowflakeCode.ToString();
            }
            catch (Exception ex)
            {
                _result.Success = false;
                _result.Errors.Add($"转换过程中发生错误: {ex.Message}");
            }
            
            return _result;
        }

        private string GenerateProcedureHeader(StoredProcedureInfo procedureInfo)
        {
            var header = new StringBuilder();
            header.Append("CREATE OR REPLACE PROCEDURE ");
            header.Append(procedureInfo.Name);
            header.Append("(");
            
            for (int i = 0; i < procedureInfo.Parameters.Count; i++)
            {
                var param = procedureInfo.Parameters[i];
                header.Append($"{param.Name} {DataTypeMapper.MapDataType(param.DataType)}");
                
                if (i < procedureInfo.Parameters.Count - 1)
                    header.Append(", ");
            }
            
            header.Append(")");
            return header.ToString();
        }

        private string GenerateVariableDeclarations(string procedureBody)
        {
            var declarations = new StringBuilder();
            var variables = ExtractVariables(procedureBody);
            
            foreach (var variable in variables)
            {
                declarations.AppendLine($"    {variable.Name} {variable.DataType};");
            }
            
            return declarations.Length > 0 ? declarations.ToString() : "    -- 无变量声明";
        }

        private List<ParameterInfo> ExtractVariables(string procedureBody)
        {
            var variables = new List<ParameterInfo>();
            
            // 提取DECLARE语句中的变量
            var declareMatches = Regex.Matches(procedureBody, 
                @"DECLARE\s+(\w+)\s+(\w+(?:\(\d+(?:,\d+)?\))?)", 
                RegexOptions.IgnoreCase);
            
            foreach (Match match in declareMatches)
            {
                if (match.Groups.Count >= 3)
                {
                    variables.Add(new ParameterInfo
                    {
                        Name = match.Groups[1].Value,
                        DataType = DataTypeMapper.MapDataType(match.Groups[2].Value)
                    });
                }
            }
            
            return variables;
        }

        private string ConvertProcedureBody(string procedureBody)
        {
            var convertedBody = procedureBody;
            
            // 转换变量声明
            convertedBody = ConvertVariableDeclarations(convertedBody);
            
            // 转换SET语句
            convertedBody = ConvertSetStatements(convertedBody);
            
            // 转换IF语句
            convertedBody = ConvertIfStatements(convertedBody);
            
            // 转换循环语句
            convertedBody = ConvertLoopStatements(convertedBody);
            
            // 转换游标
            convertedBody = ConvertCursors(convertedBody);
            
            // 转换DML语句
            convertedBody = ConvertDmlStatements(convertedBody);
            
            // 转换函数调用
            convertedBody = ConvertFunctionCalls(convertedBody);
            
            return convertedBody;
        }

        private string ConvertVariableDeclarations(string body)
        {
            // Teradata使用DECLARE,Snowflake直接在BEGIN后声明
            return Regex.Replace(body, @"DECLARE\s+", "", RegexOptions.IgnoreCase);
        }

        private string ConvertSetStatements(string body)
        {
            // Teradata: SET v = value; Snowflake: v := value;
            return Regex.Replace(body, @"SET\s+(\w+)\s*=\s*", "$1 :=", RegexOptions.IgnoreCase);
        }

        private string ConvertIfStatements(string body)
        {
            var converted = body;
            
            // 转换IF-THEN-ELSEIF-ELSE-END IF
            converted = Regex.Replace(converted, @"IF\s+(.*?)\s+THEN", "IF ($1) THEN", RegexOptions.IgnoreCase);
            converted = Regex.Replace(converted, @"ELSEIF\s+(.*?)\s+THEN", "ELSEIF ($1) THEN", RegexOptions.IgnoreCase);
            converted = Regex.Replace(converted, @"END\s+IF", "END IF;", RegexOptions.IgnoreCase);
            
            return converted;
        }

        private string ConvertLoopStatements(string body)
        {
            var converted = body;
            
            // 转换FOR循环
            converted = Regex.Replace(converted, 
                @"FOR\s+(\w+)\s+IN\s+(.*?)\s+DO", 
                "FOR $1 IN $2 DO", RegexOptions.IgnoreCase);
            
            // 转换WHILE循环
            converted = Regex.Replace(converted, 
                @"WHILE\s+(.*?)\s+DO", 
                "WHILE ($1) DO", RegexOptions.IgnoreCase);
            
            return converted;
        }

        private string ConvertCursors(string body)
        {
            // 游标转换需要更复杂的处理,这里提供基础框架
            var converted = body;
            
            // 简化游标声明
            converted = Regex.Replace(converted, 
                @"DECLARE\s+(\w+)\s+CURSOR\s+FOR\s+(.*?);", 
                "LET $1 CURSOR FOR $2;", RegexOptions.IgnoreCase);
            
            return converted;
        }

        private string ConvertDmlStatements(string body)
        {
            var converted = body;
            
            // 转换SELECT INTO语句
            converted = ConvertSelectIntoStatements(converted);
            
            // 转换MERGE语句
            converted = ConvertMergeStatements(converted);
            
            return converted;
        }

        private string ConvertSelectIntoStatements(string body)
        {
            // Teradata: SELECT col INTO var FROM table; 
            // Snowflake: var := (SELECT col FROM table);
            return Regex.Replace(body, 
                @"SELECT\s+(.*?)\s+INTO\s+(\w+)\s+FROM", 
                "$2 := (SELECT $1 FROM", RegexOptions.IgnoreCase);
        }

        private string ConvertMergeStatements(string body)
        {
            // MERGE语句语法略有不同,需要更精细的转换
            // 这里提供基础转换框架
            return body.Replace("MERGE INTO", "MERGE INTO")
                      .Replace("USING VALUES", "USING (VALUES");
        }

        private string ConvertFunctionCalls(string body)
        {
            var converted = body;
            
            foreach (var functionMapping in DataTypeMapper.GetFunctionMappings())
            {
                converted = Regex.Replace(converted, 
                    $@"\b{functionMapping.Key}\b", 
                    functionMapping.Value, RegexOptions.IgnoreCase);
            }
            
            return converted;
        }
    }
}

第五步:控制流转换器

5.1 控制流转换器 (ControlFlowConverter.cs)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace TeradataToSnowflakeConverter.Converters
{
    public class ControlFlowConverter
    {
        public string ConvertIfStatements(string teradataCode)
        {
            var lines = teradataCode.Split('\n');
            var result = new StringBuilder();
            var ifStack = new Stack<string>();
            var indentLevel = 0;

            foreach (var line in lines)
            {
                var trimmedLine = line.Trim();
                var convertedLine = ConvertIfLine(trimmedLine, ref indentLevel, ifStack);
                
                // 添加适当的缩进
                result.Append(' ', indentLevel * 4);
                result.AppendLine(convertedLine);
            }

            return result.ToString();
        }

        private string ConvertIfLine(string line, ref int indentLevel, Stack<string> ifStack)
        {
            var upperLine = line.ToUpper();

            if (upperLine.StartsWith("IF ") && upperLine.Contains(" THEN"))
            {
                // 转换 IF condition THEN
                var condition = ExtractCondition(line, "IF", "THEN");
                ifStack.Push("IF");
                indentLevel++;
                return $"IF ({condition}) THEN";
            }
            else if (upperLine.StartsWith("ELSEIF ") && upperLine.Contains(" THEN"))
            {
                // 转换 ELSEIF condition THEN
                var condition = ExtractCondition(line, "ELSEIF", "THEN");
                return $"ELSEIF ({condition}) THEN";
            }
            else if (upperLine == "ELSE")
            {
                return "ELSE";
            }
            else if (upperLine.StartsWith("END IF") || upperLine == "END IF;")
            {
                if (ifStack.Count > 0 && ifStack.Pop() == "IF")
                {
                    indentLevel--;
                }
                return "END IF;";
            }
            else if (upperLine.StartsWith("CASE "))
            {
                // 转换 CASE 语句
                return ConvertCaseStatement(line);
            }
            else
            {
                return line;
            }
        }

        private string ExtractCondition(string line, string startKeyword, string endKeyword)
        {
            var startIndex = line.IndexOf(startKeyword, StringComparison.OrdinalIgnoreCase) + startKeyword.Length;
            var endIndex = line.IndexOf(endKeyword, StringComparison.OrdinalIgnoreCase);
            
            if (endIndex > startIndex)
            {
                return line.Substring(startIndex, endIndex - startIndex).Trim();
            }
            return line.Substring(startIndex).Trim();
        }

        private string ConvertCaseStatement(string line)
        {
            // 简化处理,实际需要更复杂的解析
            return line.Replace("CASE", "CASE")
                      .Replace("END CASE", "END")
                      .Replace("END CASE;", "END;");
        }

        public string ConvertLoopStatements(string teradataCode)
        {
            var converted = teradataCode;
            
            // 转换 FOR 循环
            converted = ConvertForLoops(converted);
            
            // 转换 WHILE 循环
            converted = ConvertWhileLoops(converted);
            
            // 转换 REPEAT 循环
            converted = ConvertRepeatLoops(converted);
            
            return converted;
        }

        private string ConvertForLoops(string code)
        {
            // Teradata: FOR var IN (SELECT ...) DO ... END FOR;
            // Snowflake: FOR var IN (SELECT ...) DO ... END FOR;
            return Regex.Replace(code,
                @"FOR\s+(\w+)\s+IN\s*\((.*?)\)\s+DO",
                "FOR $1 IN ($2) DO",
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
        }

        private string ConvertWhileLoops(string code)
        {
            // Teradata: WHILE condition DO ... END WHILE;
            // Snowflake: WHILE (condition) DO ... END WHILE;
            return Regex.Replace(code,
                @"WHILE\s+(.*?)\s+DO",
                "WHILE ($1) DO",
                RegexOptions.IgnoreCase);
        }

        private string ConvertRepeatLoops(string code)
        {
            // Teradata: REPEAT ... UNTIL condition; END REPEAT;
            // Snowflake: 需要使用 LOOP 和 EXIT WHEN 来模拟
            
            var matches = Regex.Matches(code,
                @"REPEAT(.*?)UNTIL\s+(.*?);\s*END\s+REPEAT;",
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
            
            foreach (Match match in matches)
            {
                var loopBody = match.Groups[1].Value.Trim();
                var condition = match.Groups[2].Value.Trim();
                
                var snowflakeLoop = $@"
{loopBody}
    IF ({condition}) THEN
        LEAVE;
    END IF;
END LOOP;";
                
                code = code.Replace(match.Value, $"LOOP{snowflakeLoop}");
            }
            
            return code;
        }
    }
}

第六步:游标转换器

6.1 游标转换器 (CursorConverter.cs)

csharp 复制代码
using System;
using System.Text;
using System.Text.RegularExpressions;

namespace TeradataToSnowflakeConverter.Converters
{
    public class CursorConverter
    {
        public string ConvertCursors(string teradataCode)
        {
            var converted = teradataCode;
            
            // 转换游标声明
            converted = ConvertCursorDeclarations(converted);
            
            // 转换游标操作
            converted = ConvertCursorOperations(converted);
            
            return converted;
        }

        private string ConvertCursorDeclarations(string code)
        {
            // Teradata: DECLARE cursor_name CURSOR FOR SELECT ...;
            // Snowflake: LET cursor_name CURSOR FOR SELECT ...;
            return Regex.Replace(code,
                @"DECLARE\s+(\w+)\s+CURSOR\s+FOR\s+(.*?);",
                "LET $1 CURSOR FOR $2;",
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
        }

        private string ConvertCursorOperations(string code)
        {
            var converted = code;
            
            // 转换 OPEN 语句
            converted = Regex.Replace(converted,
                @"OPEN\s+(\w+);",
                "OPEN $1;",
                RegexOptions.IgnoreCase);
            
            // 转换 FETCH 语句
            converted = ConvertFetchStatements(converted);
            
            // 转换 CLOSE 语句
            converted = Regex.Replace(converted,
                @"CLOSE\s+(\w+);",
                "CLOSE $1;",
                RegexOptions.IgnoreCase);
            
            return converted;
        }

        private string ConvertFetchStatements(string code)
        {
            // Teradata: FETCH cursor_name INTO var1, var2, ...;
            // Snowflake: FETCH cursor_name INTO var1, var2, ...;
            // 但需要处理变量声明的差异
            
            var matches = Regex.Matches(code,
                @"FETCH\s+(\w+)\s+INTO\s+(.*?);",
                RegexOptions.IgnoreCase);
            
            foreach (Match match in matches)
            {
                var cursorName = match.Groups[1].Value;
                var variables = match.Groups[2].Value;
                
                // Snowflake 需要确保变量已声明
                var snowflakeFetch = $"FETCH {cursorName} INTO {variables};";
                
                code = code.Replace(match.Value, snowflakeFetch);
            }
            
            return code;
        }

        public string ConvertCursorLoops(string code)
        {
            // 转换游标循环模式
            var converted = code;
            
            // 模式1: DECLARE + OPEN + FETCH + WHILE 循环
            converted = ConvertDeclareOpenFetchLoop(converted);
            
            // 模式2: FOR 游标循环
            converted = ConvertForCursorLoop(converted);
            
            return converted;
        }

        private string ConvertDeclareOpenFetchLoop(string code)
        {
            // 匹配典型的游标循环模式
            var pattern = @"(DECLARE\s+\w+\s+CURSOR\s+FOR.*?;)\s*" +
                         @"(OPEN\s+\w+;)\s*" +
                         @"FETCH\s+\w+\s+INTO.*?;\s*" +
                         @"WHILE\s+.*?\s+DO\s*" +
                         @"(.*?)" +
                         @"FETCH\s+\w+\s+INTO.*?;\s*" +
                         @"END\s+WHILE;";
            
            return Regex.Replace(code, pattern, match =>
            {
                var cursorDecl = match.Groups[1].Value;
                var openStmt = match.Groups[2].Value;
                var loopBody = match.Groups[3].Value;
                
                // 转换为 Snowflake 的 FOR 游标循环
                var cursorName = Regex.Match(cursorDecl, @"DECLARE\s+(\w+)\s+CURSOR", RegexOptions.IgnoreCase).Groups[1].Value;
                
                return $@"
{cursorDecl.Replace("DECLARE", "LET")}
FOR record IN {cursorName} DO
{loopBody}
END FOR;";
            }, RegexOptions.IgnoreCase | RegexOptions.Singleline);
        }

        private string ConvertForCursorLoop(string code)
        {
            // 转换 FOR 游标循环
            return Regex.Replace(code,
                @"FOR\s+(\w+)\s+AS\s+(\w+)\s+CURSOR\s+FOR\s+(.*?)\s+DO",
                "FOR $1 IN ($3) DO",
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
        }
    }
}

第七步:DML语句转换器

7.1 DML转换器 (DmlConverter.cs)

csharp 复制代码
using System;
using System.Text;
using System.Text.RegularExpressions;

namespace TeradataToSnowflakeConverter.Converters
{
    public class DmlConverter
    {
        public string ConvertDmlStatements(string teradataCode)
        {
            var converted = teradataCode;
            
            // 转换 INSERT 语句
            converted = ConvertInsertStatements(converted);
            
            // 转换 UPDATE 语句
            converted = ConvertUpdateStatements(converted);
            
            // 转换 DELETE 语句
            converted = ConvertDeleteStatements(converted);
            
            // 转换 MERGE 语句
            converted = ConvertMergeStatements(converted);
            
            // 转换 SELECT INTO 语句
            converted = ConvertSelectIntoStatements(converted);
            
            return converted;
        }

        private string ConvertInsertStatements(string code)
        {
            var converted = code;
            
            // 处理多表插入 (Teradata 特性)
            converted = ConvertMultiTableInsert(converted);
            
            // 处理 VALUES 语法差异
            converted = Regex.Replace(converted,
                @"INSERT\s+INTO\s+(\w+)\s+VALUES\s*\((.*?)\)",
                "INSERT INTO $1 VALUES ($2)",
                RegexOptions.IgnoreCase);
            
            return converted;
        }

        private string ConvertMultiTableInsert(string code)
        {
            // Teradata 多表插入语法需要转换为多个独立的 INSERT
            var pattern = @"INSERT\s+INTO\s+(\w+)\s+SELECT.*?\s+FROM.*?\s+WHERE.*?;\s*INSERT\s+INTO\s+(\w+)\s+SELECT.*?\s+FROM.*?\s+WHERE.*?;";
            
            // 这里简化处理,实际需要更复杂的解析
            return code; // 暂时返回原代码,需要更精细的转换
        }

        private string ConvertUpdateStatements(string code)
        {
            // 转换关联更新
            return Regex.Replace(code,
                @"UPDATE\s+(\w+)\s+FROM\s+(\w+)\s+SET\s+(.*?)\s+WHERE\s+(.*?);",
                "UPDATE $1 SET $3 FROM $2 WHERE $4;",
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
        }

        private string ConvertDeleteStatements(string code)
        {
            // 转换关联删除
            return Regex.Replace(code,
                @"DELETE\s+FROM\s+(\w+)\s+WHERE\s+EXISTS\s*\(\s*SELECT.*?\)",
                "DELETE FROM $1 WHERE EXISTS (SELECT 1 FROM ...)",
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
        }

        private string ConvertMergeStatements(string code)
        {
            var converted = code;
            
            // 转换 MERGE 语句的基本结构
            converted = Regex.Replace(converted,
                @"MERGE\s+INTO\s+(\w+)\s+USING\s+(\w+)\s+ON\s*\((.*?)\)",
                "MERGE INTO $1 USING $2 ON ($3)",
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
            
            // 转换 WHEN MATCHED 子句
            converted = Regex.Replace(converted,
                @"WHEN\s+MATCHED\s+THEN\s+UPDATE\s+SET\s+(.*?)(?=WHEN|$)",
                "WHEN MATCHED THEN UPDATE SET $1",
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
            
            // 转换 WHEN NOT MATCHED 子句
            converted = Regex.Replace(converted,
                @"WHEN\s+NOT\s+MATCHED\s+THEN\s+INSERT\s*\((.*?)\)\s+VALUES\s*\((.*?)\)",
                "WHEN NOT MATCHED THEN INSERT ($1) VALUES ($2)",
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
            
            return converted;
        }

        private string ConvertSelectIntoStatements(string code)
        {
            // Teradata: SELECT col1, col2 INTO var1, var2 FROM table;
            // Snowflake: SELECT col1, col2 INTO :var1, :var2 FROM table;
            // 或者在存储过程中使用 := 赋值
            
            return Regex.Replace(code,
                @"SELECT\s+(.*?)\s+INTO\s+(\w+(?:\s*,\s*\w+)*)\s+FROM",
                match =>
                {
                    var selectList = match.Groups[1].Value;
                    var variables = match.Groups[2].Value.Split(',');
                    var varArray = new StringBuilder();
                    
                    for (int i = 0; i < variables.Length; i++)
                    {
                        varArray.Append(variables[i].Trim());
                        if (i < variables.Length - 1)
                            varArray.Append(", ");
                    }
                    
                    return $"SELECT {selectList} INTO {varArray} FROM";
                },
                RegexOptions.IgnoreCase);
        }

        public string ConvertTransactionalStatements(string code)
        {
            var converted = code;
            
            // 转换 BEGIN TRANSACTION
            converted = Regex.Replace(converted,
                @"BEGIN\s+TRANSACTION",
                "BEGIN",
                RegexOptions.IgnoreCase);
            
            // 转换 COMMIT
            converted = Regex.Replace(converted,
                @"COMMIT;",
                "COMMIT;",
                RegexOptions.IgnoreCase);
            
            // 转换 ROLLBACK
            converted = Regex.Replace(converted,
                @"ROLLBACK;",
                "ROLLBACK;",
                RegexOptions.IgnoreCase);
            
            return converted;
        }
    }
}

第八步:异常处理转换器

8.1 异常处理转换器 (ExceptionHandlerConverter.cs)

csharp 复制代码
using System;
using System.Text;
using System.Text.RegularExpressions;

namespace TeradataToSnowflakeConverter.Converters
{
    public class ExceptionHandlerConverter
    {
        public string ConvertExceptionHandlers(string teradataCode)
        {
            var converted = teradataCode;
            
            // 转换 DECLARE EXIT HANDLER
            converted = ConvertExitHandlers(converted);
            
            // 转换 DECLARE CONTINUE HANDLER
            converted = ConvertContinueHandlers(converted);
            
            // 添加 TRY-CATCH 块
            converted = WrapWithTryCatch(converted);
            
            return converted;
        }

        private string ConvertExitHandlers(string code)
        {
            // Teradata: DECLARE EXIT HANDLER FOR SQLEXCEPTION ...
            // Snowflake: 使用 EXCEPTION 块
            
            return Regex.Replace(code,
                @"DECLARE\s+EXIT\s+HANDLER\s+FOR\s+SQLEXCEPTION\s+BEGIN(.*?)END;",
                match =>
                {
                    var handlerBody = match.Groups[1].Value;
                    return $@"
EXCEPTION
    WHEN OTHER THEN
        {handlerBody}
        RAISE;
";
                },
                RegexOptions.IgnoreCase | RegexOptions.Singleline);
        }

        private string ConvertContinueHandlers(string code)
        {
            // Teradata: DECLARE CONTINUE HANDLER FOR NOT FOUND ...
            // Snowflake: 使用特定的异常处理
            
            return Regex.Replace(code,
                @"DECLARE\s+CONTINUE\s+HANDLER\s+FOR\s+NOT\s+FOUND",
                "-- CONTINUE HANDLER for NOT FOUND converted to exception handling",
                RegexOptions.IgnoreCase);
        }

        private string WrapWithTryCatch(string code)
        {
            // 检测是否已经有异常处理逻辑
            if (code.Contains("EXCEPTION") || code.Contains("WHEN OTHER THEN"))
                return code;
            
            // 简单的异常处理包装
            return $@"
BEGIN
    {code}
EXCEPTION
    WHEN OTHER THEN
        RAISE;
END;";
        }

        public string ConvertErrorFunctions(string code)
        {
            var converted = code;
            
            // 转换 SQLCODE 和 SQLERRM
            converted = Regex.Replace(converted,
                @"SQLCODE",
                "SQLCODE",
                RegexOptions.IgnoreCase);
            
            converted = Regex.Replace(converted,
                @"SQLERRM",
                "SQLERRM",
                RegexOptions.IgnoreCase);
            
            return converted;
        }

        public string AddSnowflakeExceptionBlocks(string code)
        {
            // 为可能抛出异常的代码块添加异常处理
            var lines = code.Split('\n');
            var result = new StringBuilder();
            var inBeginBlock = false;
            var blockLevel = 0;

            foreach (var line in lines)
            {
                var trimmedLine = line.Trim();

                if (trimmedLine.StartsWith("BEGIN"))
                {
                    blockLevel++;
                    if (blockLevel == 1) // 最外层的 BEGIN
                    {
                        inBeginBlock = true;
                        result.AppendLine("BEGIN");
                        result.AppendLine("  -- Added exception handling for Snowflake");
                        continue;
                    }
                }
                else if (trimmedLine.StartsWith("END;") || trimmedLine == "END")
                {
                    blockLevel--;
                    if (blockLevel == 0 && inBeginBlock)
                    {
                        result.AppendLine("EXCEPTION");
                        result.AppendLine("  WHEN OTHER THEN");
                        result.AppendLine("    RAISE;");
                        result.AppendLine("END;");
                        inBeginBlock = false;
                        continue;
                    }
                }

                result.AppendLine(line);
            }

            return result.ToString();
        }
    }
}

第九步:主程序和控制类

9.1 转换引擎主类 (ConversionEngine.cs)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Text;
using TeradataToSnowflakeConverter.Converters;
using TeradataToSnowflakeConverter.Parsers;

namespace TeradataToSnowflakeConverter
{
    public class ConversionEngine
    {
        private readonly TeradataSyntaxParser _parser;
        private readonly SyntaxConverter _syntaxConverter;
        private readonly ControlFlowConverter _controlFlowConverter;
        private readonly CursorConverter _cursorConverter;
        private readonly DmlConverter _dmlConverter;
        private readonly ExceptionHandlerConverter _exceptionConverter;
        private readonly ConversionConfig _config;

        public ConversionEngine(ConversionConfig config)
        {
            _config = config;
            _parser = new TeradataSyntaxParser();
            _syntaxConverter = new SyntaxConverter(config);
            _controlFlowConverter = new ControlFlowConverter();
            _cursorConverter = new CursorConverter();
            _dmlConverter = new DmlConverter();
            _exceptionConverter = new ExceptionHandlerConverter();
        }

        public ConversionResult Convert(string teradataCode)
        {
            var result = new ConversionResult();
            var logs = new List<ConversionLog>();

            try
            {
                logs.Add(new ConversionLog { Type = "INFO", Message = "开始解析Teradata存储过程" });
                
                // 1. 解析Teradata代码
                var procedureInfo = _parser.ParseProcedure(teradataCode);
                logs.Add(new ConversionLog { Type = "INFO", Message = $"解析完成,存储过程: {procedureInfo.Name}" });

                // 2. 转换控制流语句
                logs.Add(new ConversionLog { Type = "INFO", Message = "开始转换控制流语句" });
                var controlFlowConverted = _controlFlowConverter.ConvertIfStatements(procedureInfo.Body);
                controlFlowConverted = _controlFlowConverter.ConvertLoopStatements(controlFlowConverted);

                // 3. 转换游标
                logs.Add(new ConversionLog { Type = "INFO", Message = "开始转换游标" });
                var cursorConverted = _cursorConverter.ConvertCursors(controlFlowConverted);
                cursorConverted = _cursorConverter.ConvertCursorLoops(cursorConverted);

                // 4. 转换DML语句
                logs.Add(new ConversionLog { Type = "INFO", Message = "开始转换DML语句" });
                var dmlConverted = _dmlConverter.ConvertDmlStatements(cursorConverted);
                dmlConverted = _dmlConverter.ConvertTransactionalStatements(dmlConverted);

                // 5. 转换异常处理
                logs.Add(new ConversionLog { Type = "INFO", Message = "开始转换异常处理" });
                var exceptionConverted = _exceptionConverter.ConvertExceptionHandlers(dmlConverted);
                exceptionConverted = _exceptionConverter.ConvertErrorFunctions(exceptionConverted);
                exceptionConverted = _exceptionConverter.AddSnowflakeExceptionBlocks(exceptionConverted);

                // 6. 更新过程体并执行最终转换
                procedureInfo.Body = exceptionConverted;
                var finalResult = _syntaxConverter.ConvertProcedure(procedureInfo);

                result.Success = true;
                result.ConvertedCode = finalResult.ConvertedCode;
                result.Logs.AddRange(logs);
                result.Logs.AddRange(finalResult.Logs);

                logs.Add(new ConversionLog { Type = "INFO", Message = "转换完成" });
            }
            catch (Exception ex)
            {
                logs.Add(new ConversionLog { Type = "ERROR", Message = $"转换过程中发生错误: {ex.Message}" });
                result.Success = false;
                result.Errors.Add(ex.Message);
                result.Logs.AddRange(logs);
            }

            return result;
        }

        public ConversionResult ConvertBatch(List<string> teradataProcedures)
        {
            var result = new ConversionResult();
            var allConvertedCode = new StringBuilder();
            var successCount = 0;
            var errorCount = 0;

            foreach (var procedureCode in teradataProcedures)
            {
                try
                {
                    var procedureResult = Convert(procedureCode);
                    if (procedureResult.Success)
                    {
                        allConvertedCode.AppendLine(procedureResult.ConvertedCode);
                        allConvertedCode.AppendLine("--" + new string('=', 80));
                        allConvertedCode.AppendLine();
                        successCount++;
                    }
                    else
                    {
                        errorCount++;
                        result.Errors.AddRange(procedureResult.Errors);
                    }
                    result.Logs.AddRange(procedureResult.Logs);
                }
                catch (Exception ex)
                {
                    errorCount++;
                    result.Errors.Add($"批量转换过程中出错: {ex.Message}");
                }
            }

            result.Success = errorCount == 0;
            result.ConvertedCode = allConvertedCode.ToString();
            result.Warnings.Add($"转换完成: 成功 {successCount} 个,失败 {errorCount} 个");

            return result;
        }

        public ValidationResult ValidateSyntax(string snowflakeCode)
        {
            var validator = new SyntaxValidator();
            return validator.ValidateSnowflakeSyntax(snowflakeCode);
        }

        public ComparisonResult CompareBehavior(string teradataCode, string snowflakeCode)
        {
            var comparer = new BehaviorComparer();
            return comparer.Compare(teradataCode, snowflakeCode);
        }
    }
}

9.2 主程序入口 (Program.cs)

csharp 复制代码
using System;
using System.IO;
using System.Collections.Generic;

namespace TeradataToSnowflakeConverter
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Teradata 到 Snowflake 存储过程转换工具");
            Console.WriteLine("=====================================");
            Console.WriteLine();

            if (args.Length > 0)
            {
                // 命令行模式
                ProcessCommandLine(args);
            }
            else
            {
                // 交互模式
                RunInteractiveMode();
            }
        }

        static void ProcessCommandLine(string[] args)
        {
            var config = new ConversionConfig();
            var engine = new ConversionEngine(config);

            try
            {
                if (args[0] == "-f" && args.Length >= 2)
                {
                    // 文件模式
                    var inputFile = args[1];
                    var outputFile = args.Length >= 3 ? args[2] : Path.ChangeExtension(inputFile, ".snowflake.sql");

                    if (File.Exists(inputFile))
                    {
                        var teradataCode = File.ReadAllText(inputFile);
                        var result = engine.Convert(teradataCode);

                        WriteConversionResult(result, outputFile);
                    }
                    else
                    {
                        Console.WriteLine($"错误: 输入文件不存在: {inputFile}");
                    }
                }
                else if (args[0] == "-d" && args.Length >= 2)
                {
                    // 目录模式
                    var inputDir = args[1];
                    var outputDir = args.Length >= 3 ? args[2] : Path.Combine(inputDir, "converted");

                    ProcessDirectory(engine, inputDir, outputDir);
                }
                else
                {
                    ShowHelp();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"错误: {ex.Message}");
            }
        }

        static void RunInteractiveMode()
        {
            var config = new ConversionConfig();
            var engine = new ConversionEngine(config);

            while (true)
            {
                Console.WriteLine();
                Console.WriteLine("请选择操作:");
                Console.WriteLine("1. 转换单个存储过程");
                Console.WriteLine("2. 批量转换目录中的存储过程");
                Console.WriteLine("3. 验证语法");
                Console.WriteLine("4. 配置转换选项");
                Console.WriteLine("5. 退出");
                Console.Write("请输入选择 (1-5): ");

                var choice = Console.ReadLine();

                switch (choice)
                {
                    case "1":
                        ConvertSingleProcedure(engine);
                        break;
                    case "2":
                        ConvertDirectory(engine);
                        break;
                    case "3":
                        ValidateSyntax(engine);
                        break;
                    case "4":
                        ConfigureOptions(config);
                        break;
                    case "5":
                        return;
                    default:
                        Console.WriteLine("无效选择,请重新输入。");
                        break;
                }
            }
        }

        static void ConvertSingleProcedure(ConversionEngine engine)
        {
            Console.Write("请输入Teradata存储过程代码文件路径: ");
            var inputFile = Console.ReadLine();

            if (File.Exists(inputFile))
            {
                var teradataCode = File.ReadAllText(inputFile);
                var result = engine.Convert(teradataCode);

                Console.Write("请输入输出文件路径 (可选): ");
                var outputFile = Console.ReadLine();

                if (string.IsNullOrEmpty(outputFile))
                {
                    outputFile = Path.ChangeExtension(inputFile, ".snowflake.sql");
                }

                WriteConversionResult(result, outputFile);
            }
            else
            {
                Console.WriteLine("文件不存在!");
            }
        }

        static void ConvertDirectory(ConversionEngine engine)
        {
            Console.Write("请输入包含Teradata存储过程的目录路径: ");
            var inputDir = Console.ReadLine();

            if (Directory.Exists(inputDir))
            {
                Console.Write("请输入输出目录路径 (可选): ");
                var outputDir = Console.ReadLine();

                if (string.IsNullOrEmpty(outputDir))
                {
                    outputDir = Path.Combine(inputDir, "converted");
                }

                ProcessDirectory(engine, inputDir, outputDir);
            }
            else
            {
                Console.WriteLine("目录不存在!");
            }
        }

        static void ProcessDirectory(ConversionEngine engine, string inputDir, string outputDir)
        {
            Directory.CreateDirectory(outputDir);

            var sqlFiles = Directory.GetFiles(inputDir, "*.sql", SearchOption.AllDirectories);
            var procedures = new List<string>();
            var convertedCount = 0;

            foreach (var file in sqlFiles)
            {
                try
                {
                    var content = File.ReadAllText(file);
                    if (content.ToUpper().Contains("REPLACE PROCEDURE"))
                    {
                        procedures.Add(content);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"读取文件 {file} 时出错: {ex.Message}");
                }
            }

            Console.WriteLine($"找到 {procedures.Count} 个存储过程文件");

            var result = engine.ConvertBatch(procedures);

            if (result.Success)
            {
                var outputFile = Path.Combine(outputDir, "all_converted_procedures.sql");
                File.WriteAllText(outputFile, result.ConvertedCode);
                Console.WriteLine($"转换完成! 结果已保存到: {outputFile}");
            }

            // 同时保存单个文件
            foreach (var procedure in procedures)
            {
                try
                {
                    var singleResult = engine.Convert(procedure);
                    if (singleResult.Success)
                    {
                        var fileName = $"procedure_{convertedCount + 1}.sql";
                        var outputFile = Path.Combine(outputDir, fileName);
                        File.WriteAllText(outputFile, singleResult.ConvertedCode);
                        convertedCount++;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"转换单个过程时出错: {ex.Message}");
                }
            }

            Console.WriteLine($"成功转换 {convertedCount} 个存储过程");
        }

        static void ValidateSyntax(ConversionEngine engine)
        {
            Console.Write("请输入要验证的Snowflake SQL文件路径: ");
            var filePath = Console.ReadLine();

            if (File.Exists(filePath))
            {
                var code = File.ReadAllText(filePath);
                var result = engine.ValidateSyntax(code);

                Console.WriteLine($"语法验证结果: {(result.IsValid ? "有效" : "无效")}");
                if (!result.IsValid)
                {
                    Console.WriteLine("错误信息:");
                    foreach (var error in result.Errors)
                    {
                        Console.WriteLine($"  - {error}");
                    }
                }
            }
            else
            {
                Console.WriteLine("文件不存在!");
            }
        }

        static void ConfigureOptions(ConversionConfig config)
        {
            Console.WriteLine("当前配置:");
            Console.WriteLine($"生成注释: {config.GenerateComments}");
            Console.WriteLine($"保留格式: {config.PreserveOriginalFormatting}");
            Console.WriteLine($"语法验证: {config.ValidateSyntax}");

            Console.Write("是否修改配置? (y/n): ");
            if (Console.ReadLine().ToLower() == "y")
            {
                // 这里可以添加详细的配置修改逻辑
                Console.WriteLine("配置修改功能待实现");
            }
        }

        static void WriteConversionResult(ConversionResult result, string outputFile)
        {
            if (result.Success)
            {
                File.WriteAllText(outputFile, result.ConvertedCode);
                Console.WriteLine($"转换成功! 结果已保存到: {outputFile}");

                // 显示警告信息
                if (result.Warnings.Count > 0)
                {
                    Console.WriteLine("警告:");
                    foreach (var warning in result.Warnings)
                    {
                        Console.WriteLine($"  - {warning}");
                    }
                }

                // 显示转换日志
                if (result.Logs.Count > 0)
                {
                    Console.WriteLine("转换日志:");
                    foreach (var log in result.Logs)
                    {
                        Console.WriteLine($"  [{log.Type}] {log.Message}");
                    }
                }
            }
            else
            {
                Console.WriteLine("转换失败!");
                foreach (var error in result.Errors)
                {
                    Console.WriteLine($"  - {error}");
                }
            }
        }

        static void ShowHelp()
        {
            Console.WriteLine("用法:");
            Console.WriteLine("  TeradataToSnowflakeConverter -f <input_file> [output_file]");
            Console.WriteLine("  TeradataToSnowflakeConverter -d <input_directory> [output_directory]");
            Console.WriteLine();
            Console.WriteLine("示例:");
            Console.WriteLine("  TeradataToSnowflakeConverter -f procedure.td.sql procedure.sf.sql");
            Console.WriteLine("  TeradataToSnowflakeConverter -d ./td_procedures ./sf_procedures");
        }
    }
}

第十步:验证和测试框架

10.1 语法验证器 (SyntaxValidator.cs)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace TeradataToSnowflakeConverter
{
    public class ValidationResult
    {
        public bool IsValid { get; set; }
        public List<string> Errors { get; set; } = new List<string>();
        public List<string> Warnings { get; set; } = new List<string>();
    }

    public class SyntaxValidator
    {
        public ValidationResult ValidateSnowflakeSyntax(string code)
        {
            var result = new ValidationResult();
            
            // 检查基本的Snowflake语法规则
            CheckProcedureStructure(code, result);
            CheckVariableDeclarations(code, result);
            CheckControlFlowStatements(code, result);
            CheckSqlStatements(code, result);
            
            result.IsValid = result.Errors.Count == 0;
            return result;
        }

        private void CheckProcedureStructure(string code, ValidationResult result)
        {
            if (!code.ToUpper().Contains("CREATE OR REPLACE PROCEDURE"))
            {
                result.Errors.Add("缺少存储过程声明");
            }

            if (!code.Contains("$$") || code.IndexOf("$$") == code.LastIndexOf("$$"))
            {
                result.Errors.Add("存储过程体分隔符 $$ 不完整");
            }
        }

        private void CheckVariableDeclarations(string code, ValidationResult result)
        {
            // 检查变量声明语法
            var declareMatches = Regex.Matches(code, @"DECLARE\s+\w+\s+\w+", RegexOptions.IgnoreCase);
            foreach (Match match in declareMatches)
            {
                result.Warnings.Add($"检测到DECLARE语句,Snowflake中使用LET: {match.Value}");
            }

            // 检查SET语句语法
            var setMatches = Regex.Matches(code, @"SET\s+\w+\s*=", RegexOptions.IgnoreCase);
            foreach (Match match in setMatches)
            {
                result.Warnings.Add($"检测到SET语句,Snowflake中使用:=赋值: {match.Value}");
            }
        }

        private void CheckControlFlowStatements(string code, ValidationResult result)
        {
            // 检查IF语句配对
            var ifCount = Regex.Matches(code, @"\bIF\b", RegexOptions.IgnoreCase).Count;
            var endIfCount = Regex.Matches(code, @"\bEND IF\b", RegexOptions.IgnoreCase).Count;
            if (ifCount != endIfCount)
            {
                result.Errors.Add($"IF语句不匹配: IF({ifCount}) != END IF({endIfCount})");
            }

            // 检查循环语句
            CheckLoopStatements(code, result);
        }

        private void CheckLoopStatements(string code, ValidationResult result)
        {
            var forCount = Regex.Matches(code, @"\bFOR\b", RegexOptions.IgnoreCase).Count;
            var endForCount = Regex.Matches(code, @"\bEND FOR\b", RegexOptions.IgnoreCase).Count;
            if (forCount != endForCount)
            {
                result.Errors.Add($"FOR循环不匹配: FOR({forCount}) != END FOR({endForCount})");
            }

            var whileCount = Regex.Matches(code, @"\bWHILE\b", RegexOptions.IgnoreCase).Count;
            var endWhileCount = Regex.Matches(code, @"\bEND WHILE\b", RegexOptions.IgnoreCase).Count;
            if (whileCount != endWhileCount)
            {
                result.Errors.Add($"WHILE循环不匹配: WHILE({whileCount}) != END WHILE({endWhileCount})");
            }
        }

        private void CheckSqlStatements(string code, ValidationResult result)
        {
            // 检查分号结束
            var lines = code.Split('\n');
            for (int i = 0; i < lines.Length; i++)
            {
                var line = lines[i].Trim();
                if (IsSqlStatement(line) && !line.EndsWith(";") && !line.EndsWith("$$"))
                {
                    result.Warnings.Add($"第{i+1}行: SQL语句可能缺少分号");
                }
            }
        }

        private bool IsSqlStatement(string line)
        {
            var sqlKeywords = new[] { "SELECT", "INSERT", "UPDATE", "DELETE", "MERGE", "CREATE", "DROP" };
            foreach (var keyword in sqlKeywords)
            {
                if (line.ToUpper().StartsWith(keyword))
                    return true;
            }
            return false;
        }
    }
}

10.2 行为比较器 (BehaviorComparer.cs)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace TeradataToSnowflakeConverter
{
    public class ComparisonResult
    {
        public bool IsEquivalent { get; set; }
        public List<string> Differences { get; set; } = new List<string>();
        public List<string> Warnings { get; set; } = new List<string>();
        public double SimilarityScore { get; set; }
    }

    public class BehaviorComparer
    {
        public ComparisonResult Compare(string teradataCode, string snowflakeCode)
        {
            var result = new ComparisonResult();
            
            // 比较基本结构
            CompareProcedureStructure(teradataCode, snowflakeCode, result);
            
            // 比较业务逻辑
            CompareBusinessLogic(teradataCode, snowflakeCode, result);
            
            // 比较性能特征
            ComparePerformanceCharacteristics(teradataCode, snowflakeCode, result);
            
            result.SimilarityScore = CalculateSimilarityScore(teradataCode, snowflakeCode);
            result.IsEquivalent = result.SimilarityScore >= 0.8; // 80%相似度认为等价
            
            return result;
        }

        private void CompareProcedureStructure(string tdCode, string sfCode, ComparisonResult result)
        {
            // 比较参数列表
            var tdParams = ExtractParameters(tdCode);
            var sfParams = ExtractParameters(sfCode);
            
            if (tdParams.Count != sfParams.Count)
            {
                result.Differences.Add($"参数数量不同: Teradata({tdParams.Count}) vs Snowflake({sfParams.Count})");
            }

            // 比较返回类型
            var tdReturnType = ExtractReturnType(tdCode);
            var sfReturnType = ExtractReturnType(sfCode);
            
            if (tdReturnType != sfReturnType)
            {
                result.Warnings.Add($"返回类型可能不同: Teradata({tdReturnType}) vs Snowflake({sfReturnType})");
            }
        }

        private List<string> ExtractParameters(string code)
        {
            var parameters = new List<string>();
            var matches = Regex.Matches(code, @"REPLACE\s+PROCEDURE\s+\w+\s*\(([^)]*)\)", RegexOptions.IgnoreCase);
            
            if (matches.Count > 0)
            {
                var paramText = matches[0].Groups[1].Value;
                var paramList = paramText.Split(',');
                foreach (var param in paramList)
                {
                    parameters.Add(param.Trim());
                }
            }
            
            return parameters;
        }

        private string ExtractReturnType(string code)
        {
            // 简化实现,实际需要更复杂的解析
            if (code.ToUpper().Contains("RETURNS"))
            {
                var match = Regex.Match(code, @"RETURNS\s+(\w+)", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value;
            }
            return "VOID";
        }

        private void CompareBusinessLogic(string tdCode, string sfCode, ComparisonResult result)
        {
            // 比较DML操作数量
            var tdDmlCount = CountDmlOperations(tdCode);
            var sfDmlCount = CountDmlOperations(sfCode);
            
            if (tdDmlCount != sfDmlCount)
            {
                result.Differences.Add($"DML操作数量不同: Teradata({tdDmlCount}) vs Snowflake({sfDmlCount})");
            }

            // 比较游标使用
            var tdCursorCount = CountCursors(tdCode);
            var sfCursorCount = CountCursors(sfCode);
            
            if (tdCursorCount != sfCursorCount)
            {
                result.Warnings.Add($"游标使用方式可能不同: Teradata({tdCursorCount}) vs Snowflake({sfCursorCount})");
            }
        }

        private int CountDmlOperations(string code)
        {
            var dmlKeywords = new[] { "INSERT", "UPDATE", "DELETE", "MERGE" };
            var count = 0;
            
            foreach (var keyword in dmlKeywords)
            {
                count += Regex.Matches(code, $@"\b{keyword}\b", RegexOptions.IgnoreCase).Count;
            }
            
            return count;
        }

        private int CountCursors(string code)
        {
            return Regex.Matches(code, @"\bCURSOR\b", RegexOptions.IgnoreCase).Count;
        }

        private void ComparePerformanceCharacteristics(string tdCode, string sfCode, ComparisonResult result)
        {
            // 检查可能影响性能的模式
            CheckNestedLoops(tdCode, sfCode, result);
            CheckTransactionUsage(tdCode, sfCode, result);
        }

        private void CheckNestedLoops(string tdCode, string sfCode, ComparisonResult result)
        {
            // 检查嵌套循环
            // 简化实现
        }

        private void CheckTransactionUsage(string tdCode, string sfCode, ComparisonResult result)
        {
            var tdTxnCount = Regex.Matches(tdCode, @"\bBEGIN\s+TRANSACTION\b", RegexOptions.IgnoreCase).Count;
            var sfTxnCount = Regex.Matches(sfCode, @"\bBEGIN\b", RegexOptions.IgnoreCase).Count;
            
            if (tdTxnCount != sfTxnCount)
            {
                result.Warnings.Add($"事务使用方式不同: Teradata({tdTxnCount}) vs Snowflake({sfTxnCount})");
            }
        }

        private double CalculateSimilarityScore(string tdCode, string sfCode)
        {
            // 简化的相似度计算
            var tdWords = GetSignificantWords(tdCode);
            var sfWords = GetSignificantWords(sfCode);
            
            var commonWords = 0;
            foreach (var word in tdWords)
            {
                if (sfWords.Contains(word))
                    commonWords++;
            }
            
            return tdWords.Count > 0 ? (double)commonWords / tdWords.Count : 0.0;
        }

        private HashSet<string> GetSignificantWords(string code)
        {
            var words = new HashSet<string>();
            var matches = Regex.Matches(code, @"\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|JOIN|GROUP BY|ORDER BY)\b", RegexOptions.IgnoreCase);
            
            foreach (Match match in matches)
            {
                words.Add(match.Value.ToUpper());
            }
            
            return words;
        }
    }
}

第十一步:扩展DataTypeMapper

11.1 扩展数据类型映射 (DataTypeMapper.cs扩展)

csharp 复制代码
using System;
using System.Collections.Generic;

namespace TeradataToSnowflakeConverter.Mappers
{
    public static partial class DataTypeMapper
    {
        public static Dictionary<string, string> GetFunctionMappings()
        {
            return FunctionMapping;
        }

        public static string MapExpression(string teradataExpression)
        {
            if (string.IsNullOrWhiteSpace(teradataExpression))
                return teradataExpression;

            var converted = teradataExpression;
            
            // 转换函数调用
            foreach (var mapping in FunctionMapping)
            {
                converted = ConvertFunctionCall(converted, mapping.Key, mapping.Value);
            }
            
            // 转换操作符
            converted = ConvertOperators(converted);
            
            return converted;
        }

        private static string ConvertFunctionCall(string expression, string tdFunction, string sfFunction)
        {
            var pattern = $@"\b{tdFunction}\s*\(";
            return System.Text.RegularExpressions.Regex.Replace(
                expression, pattern, $"{sfFunction}(", 
                System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        }

        private static string ConvertOperators(string expression)
        {
            // Teradata 和 Snowflake 操作符基本相同
            // 这里可以处理特殊操作符差异
            return expression
                .Replace("||", "||")  // 字符串连接
                .Replace("^=", "<>")  // 不等于操作符
                .Replace("~=", "<>"); // 不等于操作符
        }

        public static bool RequiresExplicitCast(string sourceType, string targetType)
        {
            var sourceBase = sourceType.Split('(')[0].Trim().ToUpper();
            var targetBase = targetType.Split('(')[0].Trim().ToUpper();
            
            // 需要显式转换的情况
            var explicitCastScenarios = new[]
            {
                ("DATE", "VARCHAR"),
                ("TIMESTAMP", "VARCHAR"),
                ("VARCHAR", "NUMERIC"),
                ("VARCHAR", "DATE")
            };
            
            foreach (var scenario in explicitCastScenarios)
            {
                if (sourceBase == scenario.Item1 && targetBase == scenario.Item2)
                    return true;
            }
            
            return false;
        }
    }
}

第十二步:测试用例框架

12.1 基础测试框架 (TestBase.cs)

csharp 复制代码
using System;
using System.IO;
using Xunit;
using Xunit.Abstractions;

namespace TeradataToSnowflakeConverter.Tests
{
    public class TestBase
    {
        protected readonly ITestOutputHelper Output;
        protected readonly ConversionEngine Engine;
        protected readonly ConversionConfig Config;

        public TestBase(ITestOutputHelper output)
        {
            Output = output;
            Config = new ConversionConfig
            {
                GenerateComments = true,
                ValidateSyntax = true
            };
            Engine = new ConversionEngine(Config);
        }

        protected void LogTestInfo(string testName, string input, string output)
        {
            Output.WriteLine($"=== {testName} ===");
            Output.WriteLine("输入 (Teradata):");
            Output.WriteLine(input);
            Output.WriteLine("输出 (Snowflake):");
            Output.WriteLine(output);
            Output.WriteLine("");
        }

        protected void AssertConversionSuccess(ConversionResult result)
        {
            Assert.True(result.Success, $"转换失败: {string.Join(", ", result.Errors)}");
            Assert.False(string.IsNullOrWhiteSpace(result.ConvertedCode));
        }

        protected void AssertSyntaxValid(string snowflakeCode)
        {
            var validationResult = Engine.ValidateSyntax(snowflakeCode);
            Assert.True(validationResult.IsValid, 
                $"语法验证失败: {string.Join(", ", validationResult.Errors)}");
        }
    }
}

12.2 基础语法转换测试 (BasicSyntaxTests.cs)

csharp 复制代码
using Xunit;

namespace TeradataToSnowflakeConverter.Tests
{
    public class BasicSyntaxTests : TestBase
    {
        public BasicSyntaxTests(ITestOutputHelper output) : base(output) { }

        [Fact]
        public void TestProcedureDeclaration()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestProcedure(IN param1 INTEGER, IN param2 VARCHAR(100))
BEGIN
    -- 测试过程
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("存储过程声明测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("CREATE OR REPLACE PROCEDURE TestProcedure", result.ConvertedCode);
            Assert.Contains("param1 INTEGER", result.ConvertedCode);
            Assert.Contains("param2 VARCHAR(100)", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }

        [Fact]
        public void TestVariableDeclaration()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestVars()
BEGIN
    DECLARE v_count INTEGER;
    DECLARE v_name VARCHAR(100) DEFAULT 'test';
    SET v_count = 10;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("变量声明测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.DoesNotContain("DECLARE v_count INTEGER", result.ConvertedCode); // 应该在BEGIN后声明
            Assert.Contains("v_count :=", result.ConvertedCode); // SET语句转换
            AssertSyntaxValid(result.ConvertedCode);
        }

        [Fact]
        public void TestIfStatement()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestIf()
BEGIN
    DECLARE v_value INTEGER DEFAULT 5;
    
    IF v_value > 10 THEN
        SET v_value = 20;
    ELSEIF v_value > 0 THEN
        SET v_value = 15;
    ELSE
        SET v_value = 0;
    END IF;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("IF语句测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("IF (v_value > 10) THEN", result.ConvertedCode);
            Assert.Contains("ELSEIF (v_value > 0) THEN", result.ConvertedCode);
            Assert.Contains("END IF;", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }
    }
}

12.3 控制流测试 (ControlFlowTests.cs)

csharp 复制代码
using Xunit;

namespace TeradataToSnowflakeConverter.Tests
{
    public class ControlFlowTests : TestBase
    {
        public ControlFlowTests(ITestOutputHelper output) : base(output) { }

        [Fact]
        public void TestForLoop()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestForLoop()
BEGIN
    DECLARE i INTEGER;
    
    FOR i IN 1 TO 10 DO
        -- 循环体
        SET i = i + 1;
    END FOR;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("FOR循环测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("FOR i IN 1 TO 10 DO", result.ConvertedCode);
            Assert.Contains("END FOR;", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }

        [Fact]
        public void TestWhileLoop()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestWhile()
BEGIN
    DECLARE counter INTEGER DEFAULT 0;
    
    WHILE counter < 10 DO
        SET counter = counter + 1;
    END WHILE;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("WHILE循环测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("WHILE (counter < 10) DO", result.ConvertedCode);
            Assert.Contains("END WHILE;", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }

        [Fact]
        public void TestCaseStatement()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestCase()
BEGIN
    DECLARE v_grade CHAR(1) DEFAULT 'A';
    DECLARE v_result VARCHAR(20);
    
    CASE v_grade
        WHEN 'A' THEN SET v_result = '优秀';
        WHEN 'B' THEN SET v_result = '良好';
        ELSE SET v_result = '其他';
    END CASE;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("CASE语句测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            AssertSyntaxValid(result.ConvertedCode);
        }
    }
}

12.4 游标测试 (CursorTests.cs)

csharp 复制代码
using Xunit;

namespace TeradataToSnowflakeConverter.Tests
{
    public class CursorTests : TestBase
    {
        public CursorTests(ITestOutputHelper output) : base(output) { }

        [Fact]
        public void TestBasicCursor()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestCursor()
BEGIN
    DECLARE cur CURSOR FOR SELECT id, name FROM users WHERE status = 'A';
    DECLARE v_id INTEGER;
    DECLARE v_name VARCHAR(100);
    
    OPEN cur;
    FETCH cur INTO v_id, v_name;
    CLOSE cur;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("基础游标测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("LET cur CURSOR FOR SELECT id, name FROM users WHERE status = 'A'", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }

        [Fact]
        public void TestCursorLoop()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestCursorLoop()
BEGIN
    DECLARE cur CURSOR FOR SELECT id, name FROM users;
    DECLARE v_id INTEGER;
    DECLARE v_name VARCHAR(100);
    DECLARE v_done INTEGER DEFAULT 0;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
    
    OPEN cur;
    FETCH cur INTO v_id, v_name;
    WHILE v_done = 0 DO
        -- 处理数据
        FETCH cur INTO v_id, v_name;
    END WHILE;
    CLOSE cur;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("游标循环测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            AssertSyntaxValid(result.ConvertedCode);
        }
    }
}

12.5 DML语句测试 (DmlTests.cs)

csharp 复制代码
using Xunit;

namespace TeradataToSnowflakeConverter.Tests
{
    public class DmlTests : TestBase
    {
        public DmlTests(ITestOutputHelper output) : base(output) { }

        [Fact]
        public void TestSelectInto()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestSelectInto()
BEGIN
    DECLARE v_count INTEGER;
    DECLARE v_total DECIMAL(10,2);
    
    SELECT COUNT(*), SUM(amount) INTO v_count, v_total 
    FROM transactions 
    WHERE transaction_date > CURRENT_DATE - 30;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("SELECT INTO测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("v_count :=", result.ConvertedCode);
            Assert.Contains("v_total :=", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }

        [Fact]
        public void TestInsertUpdateDelete()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestDML()
BEGIN
    INSERT INTO audit_log (action, user_id, timestamp)
    VALUES ('LOGIN', 123, CURRENT_TIMESTAMP);
    
    UPDATE users SET last_login = CURRENT_TIMESTAMP 
    WHERE user_id = 123;
    
    DELETE FROM temp_sessions 
    WHERE created_date < CURRENT_DATE - 7;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("DML语句测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("INSERT INTO audit_log", result.ConvertedCode);
            Assert.Contains("UPDATE users", result.ConvertedCode);
            Assert.Contains("DELETE FROM temp_sessions", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }

        [Fact]
        public void TestMergeStatement()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestMerge()
BEGIN
    MERGE INTO target_table t
    USING source_table s
    ON t.id = s.id
    WHEN MATCHED THEN 
        UPDATE SET t.name = s.name, t.value = s.value
    WHEN NOT MATCHED THEN 
        INSERT (id, name, value) VALUES (s.id, s.name, s.value);
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("MERGE语句测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("MERGE INTO target_table t", result.ConvertedCode);
            Assert.Contains("WHEN MATCHED THEN", result.ConvertedCode);
            Assert.Contains("WHEN NOT MATCHED THEN", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }
    }
}

12.6 异常处理测试 (ExceptionTests.cs)

csharp 复制代码
using Xunit;

namespace TeradataToSnowflakeConverter.Tests
{
    public class ExceptionTests : TestBase
    {
        public ExceptionTests(ITestOutputHelper output) : base(output) { }

        [Fact]
        public void TestExceptionHandler()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestException()
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        INSERT INTO error_log (error_message, error_time)
        VALUES (SQLERRM, CURRENT_TIMESTAMP);
    END;
    
    -- 可能抛出异常的操作
    UPDATE accounts SET balance = balance - 100 WHERE account_id = 123;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("异常处理测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("EXCEPTION", result.ConvertedCode);
            Assert.Contains("WHEN OTHER THEN", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }
    }
}

12.7 数据类型和函数测试 (DataTypeFunctionTests.cs)

csharp 复制代码
using Xunit;

namespace TeradataToSnowflakeConverter.Tests
{
    public class DataTypeFunctionTests : TestBase
    {
        public DataTypeFunctionTests(ITestOutputHelper output) : base(output) { }

        [Fact]
        public void TestDataTypeConversion()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestDataTypes()
BEGIN
    DECLARE v_int INTEGER;
    DECLARE v_dec DECIMAL(10,2);
    DECLARE v_char CHAR(10);
    DECLARE v_varchar VARCHAR(100);
    DECLARE v_date DATE;
    DECLARE v_ts TIMESTAMP;
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("数据类型转换测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            AssertSyntaxValid(result.ConvertedCode);
        }

        [Fact]
        public void TestFunctionConversion()
        {
            // Arrange
            var teradataCode = @"
REPLACE PROCEDURE TestFunctions()
BEGIN
    DECLARE v_length INTEGER;
    DECLARE v_upper VARCHAR(100);
    DECLARE v_date DATE;
    
    SET v_length = CHAR_LENGTH('hello');
    SET v_upper = UPPER('hello world');
    SET v_date = ADD_MONTHS(CURRENT_DATE, 1);
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("函数转换测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            Assert.Contains("LENGTH", result.ConvertedCode); // CHAR_LENGTH -> LENGTH
            Assert.Contains("UPPER", result.ConvertedCode);
            AssertSyntaxValid(result.ConvertedCode);
        }
    }
}

12.8 集成测试 (IntegrationTests.cs)

csharp 复制代码
using System.IO;
using Xunit;

namespace TeradataToSnowflakeConverter.Tests
{
    public class IntegrationTests : TestBase
    {
        public IntegrationTests(ITestOutputHelper output) : base(output) { }

        [Fact]
        public void TestCompleteProcedure()
        {
            // Arrange - 完整的存储过程示例
            var teradataCode = @"
REPLACE PROCEDURE CalculateMonthlyReport(
    IN p_year INTEGER,
    IN p_month INTEGER,
    OUT p_total_amount DECIMAL(15,2),
    OUT p_record_count INTEGER
)
BEGIN
    DECLARE v_start_date DATE;
    DECLARE v_end_date DATE;
    DECLARE v_current_amount DECIMAL(15,2);
    DECLARE v_done INTEGER DEFAULT 0;
    
    DECLARE cur_transactions CURSOR FOR
        SELECT amount FROM transactions
        WHERE transaction_date BETWEEN v_start_date AND v_end_date;
    
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        SET p_total_amount = 0;
        SET p_record_count = 0;
        ROLLBACK;
    END;
    
    -- 计算日期范围
    SET v_start_date = (p_year * 10000 + p_month * 100 + 1) - DATE;
    SET v_end_date = LAST_DAY(v_start_date);
    
    SET p_total_amount = 0;
    SET p_record_count = 0;
    
    BEGIN TRANSACTION;
    
    OPEN cur_transactions;
    FETCH cur_transactions INTO v_current_amount;
    
    WHILE v_done = 0 DO
        SET p_total_amount = p_total_amount + v_current_amount;
        SET p_record_count = p_record_count + 1;
        FETCH cur_transactions INTO v_current_amount;
    END WHILE;
    
    CLOSE cur_transactions;
    
    -- 插入汇总记录
    INSERT INTO monthly_reports (report_year, report_month, total_amount, record_count)
    VALUES (p_year, p_month, p_total_amount, p_record_count);
    
    COMMIT;
    
END;";

            // Act
            var result = Engine.Convert(teradataCode);
            LogTestInfo("完整存储过程测试", teradataCode, result.ConvertedCode);

            // Assert
            AssertConversionSuccess(result);
            AssertSyntaxValid(result.ConvertedCode);
            
            // 验证关键元素
            Assert.Contains("CREATE OR REPLACE PROCEDURE CalculateMonthlyReport", result.ConvertedCode);
            Assert.Contains("p_total_amount DECIMAL(15,2)", result.ConvertedCode);
            Assert.Contains("EXCEPTION", result.ConvertedCode);
            Assert.Contains("WHEN OTHER THEN", result.ConvertedCode);
        }

        [Fact]
        public void TestBatchConversion()
        {
            // Arrange
            var procedures = new[]
            {
                @"REPLACE PROCEDURE Proc1() BEGIN SELECT 1; END;",
                @"REPLACE PROCEDURE Proc2(IN param INTEGER) BEGIN UPDATE table SET col = param; END;",
                @"REPLACE PROCEDURE Proc3() BEGIN DELETE FROM table WHERE id = 1; END;"
            };

            // Act
            var result = Engine.ConvertBatch(procedures);

            // Assert
            Assert.True(result.Success);
            Assert.Contains("CREATE OR REPLACE PROCEDURE Proc1", result.ConvertedCode);
            Assert.Contains("CREATE OR REPLACE PROCEDURE Proc2", result.ConvertedCode);
            Assert.Contains("CREATE OR REPLACE PROCEDURE Proc3", result.ConvertedCode);
        }
    }
}

12.9 测试项目配置 (TeradataToSnowflakeConverter.Tests.csproj)

xml 复制代码
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
    <PackageReference Include="xunit" Version="2.6.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="6.0.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\TeradataToSnowflakeConverter\TeradataToSnowflakeConverter.csproj" />
  </ItemGroup>

</Project>

12.10 测试运行脚本 (run_tests.bat)

bash 复制代码
@echo off
echo Running Teradata to Snowflake Converter Tests...
echo.

dotnet test --logger "console;verbosity=detailed"

if %errorlevel% equ 0 (
    echo.
    echo All tests passed!
) else (
    echo.
    echo Some tests failed!
)

pause

测试用例覆盖的功能点:

基础语法转换:存储过程声明、变量声明、赋值语句

控制流语句:IF/ELSE、CASE、FOR循环、WHILE循环

游标操作:游标声明、FETCH、游标循环

DML语句:SELECT INTO、INSERT、UPDATE、DELETE、MERGE

异常处理:异常处理器、事务回滚

数据类型和函数:类型映射、函数转换

集成测试:完整存储过程转换、批量转换

要运行测试,可以使用以下命令:

bash 复制代码
# 运行所有测试
dotnet test

# 运行特定测试类
dotnet test --filter "TeradataToSnowflakeConverter.Tests.BasicSyntaxTests"

# 运行特定测试方法
dotnet test --filter "TestProcedureDeclaration"

# 生成测试覆盖率报告
dotnet test --collect:"XPlat Code Coverage"
相关推荐
李高钢5 小时前
c#获取当前程序所在目录避坑
开发语言·数据库·c#
Victory_20255 小时前
c# stateless介绍
c#
金仓拾光集5 小时前
金仓数据库践行社会责任:以技术驱动绿色计算与数据普惠
运维·数据库·oracle·kingbase·数据库平替用金仓·金仓数据库
观测云5 小时前
阿里云 OceanBase 可观测最佳实践
阿里云·云计算·oceanbase
gc_22996 小时前
学习C#调用OpenXml操作word文档的基本用法(3:Style类分析-1)
c#·style·openxml
金仓拾光集6 小时前
金仓数据库赋能地铁AFC系统升级:核心技术实现与落地
运维·数据库·ux·kingbase·kingbasees·数据库平替用金仓·金仓数据库
2503_928411566 小时前
10.31 MySQL数据记录操作
数据库·sql·mysql
CryptoRzz7 小时前
印度实时股票数据源接口对接文档-IPO新股、k线数据
java·开发语言·数据库·区块链
还是大剑师兰特7 小时前
C#面试题及详细答案120道(51-60)-- LINQ与Lambda
c#·大剑师