C#程序实现将Teradata的存储过程转换为Amazon Redshift的pgsql的存储过程

该项目是一款基于 .NET 6+ 的控制台工具,可自动将 Teradata 存储过程及 SQL 代码转换为 Amazon Redshift 兼容代码,通过模块化设计支持 12 类语法元素的精准转换,同时提供单元测试与性能优化方案。

一、项目文件目录

TeradataToRedshiftConverter/
│
├── Program.cs                 // 主程序入口:处理命令行参数、调度转换流程
├── appsettings.json           // (可选)配置文件:存储数据库连接、转换规则等参数
│
├── Models/
│   └── CodeBlock.cs           // 代码块模型:封装代码类型(如存储过程、变量)与原始内容
│
├── Services/
│   ├── InputService.cs        // 输入服务:读取 SQL 文件(支持完整读取与流式分块读取)
│   ├── BlockSplitterService.cs // 代码分块服务:通过正则提取 12 类代码块,支持嵌套结构解析
│   ├── OutputService.cs       // 输出服务:创建输出目录、写入转换后的 Redshift SQL
│   │
│   └── Converters/            // 12 个专项转换器(按语法类型拆分)
│       ├── ProcedureConverter.cs    // 存储过程定义转换(如 REPLACE → CREATE OR REPLACE)
│       ├── VariableConverter.cs     // 变量声明转换(如 DECLARE → 直接变量定义+类型映射)
│       ├── DmlConverter.cs          // DML 语句转换(INSERT/UPDATE/DELETE 语法适配)
│       ├── QueryConverter.cs        // 查询语句转换(TOP → LIMIT、移除 QUALIFY)
│       ├── BranchingConverter.cs    // 分支逻辑转换(IF/CASE 语法适配 Redshift)
│       ├── LoopConverter.cs         // 循环转换(LOOP/WHILE/FOR 语法适配)
│       ├── CursorConverter.cs       // 游标转换(DECLARE/OPEN/FETCH/CLOSE 语法适配)
│       ├── ExceptionConverter.cs    // 异常处理转换(BEGIN...EXCEPTION 语法适配)
│       ├── TransactionConverter.cs  // 事务控制转换(COMMIT/ROLLBACK 语法校验)
│       ├── TempTableConverter.cs    // 临时表转换(VOLATILE TABLE → Redshift 临时表)
│       ├── FunctionMappingConverter.cs // 函数映射转换(如 ADD_MONTHS → DATE_TRUNC+INTERVAL)
│       └── DependencyConverter.cs   // 依赖对象转换(提取 FROM/JOIN 表,生成依赖清单)
│
├── Utils/
│   ├── RegexUtils.cs          // 正则工具集:提供 12 类代码块的提取正则(支持嵌套结构)
│   └── CachedRegexUtils.cs    // 缓存正则工具:优化正则重复编译问题,提升性能
│
└── TeradataToRedshiftConverter.Tests/  // 测试项目
    ├── ConverterTests/        // 转换器单元测试:验证单个转换器的转换准确性
    │   ├── ProcedureConverterTests.cs
    │   ├── VariableConverterTests.cs
    │   ├── FunctionMappingConverterTests.cs
    │   └── NestedStructureTests.cs  // 嵌套结构测试:验证嵌套 IF/LOOP/CURSOR 处理
    ├── IntegrationTests/      // 集成测试:验证完整转换流程(输入→分块→转换→输出)
    │   └── FullConversionTests.cs
    ├── TestHelpers/           // 测试辅助工具
    │   └── NestedSqlBuilder.cs  // 动态生成嵌套 SQL 测试用例
    ├── ReportGenerator/       // 测试报告生成器
    │   └── VisualTestReporter.cs  // 生成 HTML 可视化测试报告(含输入/预期/实际结果)
    └── TestData/              // 测试数据:存储输入样本与预期输出
        ├── Samples/
        │   ├── SimpleProcedure.sql
        │   └── ComplexProcedure.sql
        └── Expected/
            └── SimpleProcedure_Redshift.sql

二、核心文件完整代码

1. 主程序入口(Program.cs)

using System;
using System.Text;
using System.Threading.Tasks;
using TeradataToRedshiftConverter.Services;
using TeradataToRedshiftConverter.Models;

namespace TeradataToRedshiftConverter
{
    class Program
    {
        // 线程安全的输出构建器
        private static readonly StringBuilder outputBuilder = new();

        static void Main(string[] args)
        {
            Console.WriteLine("🚀 Teradata 存储过程 → Redshift 转换工具");

            // 校验命令行参数(输入文件路径、输出文件路径)
            if (args.Length < 2)
            {
                Console.WriteLine("用法: dotnet run <输入文件> <输出文件>");
                return;
            }

            string inputPath = args[0];
            string outputPath = args[1];

            try
            {
                // 1. 读取输入文件(支持完整读取或流式分块读取)
                InputService inputService = new();
                string teradataSql = inputService.ReadSqlFile(inputPath);
                // 流式读取示例:foreach (var chunk in inputService.ReadSqlInChunks(inputPath)) { ... }

                // 2. 代码分块(提取 12 类代码块)
                BlockSplitterService splitter = new();
                var codeBlocks = splitter.SplitIntoBlocks(teradataSql);
                Console.WriteLine($"🔍 成功提取 {codeBlocks.Count} 个代码块");

                // 3. 并行转换每个代码块(按 CPU 核心数控制并发)
                ParallelOptions parallelOptions = new() 
                { 
                    MaxDegreeOfParallelism = Environment.ProcessorCount 
                };
                Parallel.ForEach(codeBlocks, parallelOptions, block =>
                {
                    string convertedCode = ConvertBlock(block);
                    // 加锁保证输出构建器线程安全
                    lock (outputBuilder)
                    {
                        outputBuilder.AppendLine(convertedCode);
                        outputBuilder.AppendLine("-- =======================================");
                    }
                });

                // 4. 写入转换结果
                OutputService outputService = new();
                outputService.WriteRedshiftSql(outputPath, outputBuilder.ToString());

                Console.WriteLine($"✅ 转换完成!输出文件: {outputPath}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"❌ 错误: {ex.Message}");
            }
        }

        /// <summary>
        /// 按代码块类型匹配对应的转换器
        /// </summary>
        static string ConvertBlock(CodeBlock block)
        {
            return block.BlockType switch
            {
                "ProcedureDefinition" => new Converters.ProcedureConverter().Convert(block.Content),
                "VariableDeclaration" => new Converters.VariableConverter().Convert(block.Content),
                "DML" => new Converters.DmlConverter().Convert(block.Content),
                "Query" => new Converters.QueryConverter().Convert(block.Content),
                "Branching" => new Converters.BranchingConverter().Convert(block.Content),
                "Loop" => new Converters.LoopConverter().Convert(block.Content),
                "Cursor" => new Converters.CursorConverter().Convert(block.Content),
                "Exception" => new Converters.ExceptionConverter().Convert(block.Content),
                "Transaction" => new Converters.TransactionConverter().Convert(block.Content),
                "TempTable" => new Converters.TempTableConverter().Convert(block.Content),
                "FunctionMapping" => new Converters.FunctionMappingConverter().Convert(block.Content),
                "Dependency" => new Converters.DependencyConverter().Convert(block.Content),
                _ => $"-- ⚠️ 未处理的块类型: {block.BlockType}\n{block.Content}"
            };
        }
    }
}

2. 模型类(Models/CodeBlock.cs)

namespace TeradataToRedshiftConverter.Models
{
    /// <summary>
    /// 代码块模型:封装代码类型与原始内容
    /// </summary>
    public class CodeBlock
    {
        /// <summary>
        /// 代码块类型(如 ProcedureDefinition、VariableDeclaration)
        /// </summary>
        public string BlockType { get; set; }

        /// <summary>
        /// 代码块原始内容
        /// </summary>
        public string Content { get; set; }
    }
}

3. 输入服务(Services/InputService.cs)

using System;
using System.Collections.Generic;
using System.IO;

namespace TeradataToRedshiftConverter.Services
{
    /// <summary>
    /// 输入服务:读取 Teradata SQL 文件(支持完整读取与流式分块读取)
    /// </summary>
    public class InputService
    {
        /// <summary>
        /// 完整读取 SQL 文件
        /// </summary>
        /// <param name="filePath">文件路径</param>
        /// <returns>SQL 文本内容</returns>
        /// <exception cref="FileNotFoundException">文件不存在时抛出</exception>
        public string ReadSqlFile(string filePath)
        {
            if (!File.Exists(filePath))
                throw new FileNotFoundException($"输入文件未找到: {filePath}");

            // 读取时指定 UTF-8 编码,避免中文乱码
            return File.ReadAllText(filePath, System.Text.Encoding.UTF8);
        }

        /// <summary>
        /// 流式分块读取 SQL 文件(处理大文件时避免内存溢出)
        /// </summary>
        /// <param name="filePath">文件路径</param>
        /// <param name="chunkSize">分块大小(默认 1KB)</param>
        /// <returns>分块的 SQL 文本</returns>
        public IEnumerable<string> ReadSqlInChunks(string filePath, int chunkSize = 1024)
        {
            if (!File.Exists(filePath))
                throw new FileNotFoundException($"输入文件未找到: {filePath}");

            using var reader = new StreamReader(filePath, System.Text.Encoding.UTF8);
            char[] buffer = new char[chunkSize];
            int readLength;

            while ((readLength = reader.ReadBlock(buffer, 0, chunkSize)) > 0)
            {
                // 返回实际读取的内容(避免最后一块包含空字符)
                yield return new string(buffer, 0, readLength);
            }
        }
    }
}

4. 代码分块服务(Services/BlockSplitterService.cs)

using System.Collections.Generic;
using TeradataToRedshiftConverter.Models;
using TeradataToRedshiftConverter.Utils;

namespace TeradataToRedshiftConverter.Services
{
    /// <summary>
    /// 代码分块服务:提取 SQL 中的 12 类代码块,支持嵌套结构解析
    /// </summary>
    public class BlockSplitterService
    {
        public List<CodeBlock> SplitIntoBlocks(string sql)
        {
            var blocks = new List<CodeBlock>();

            // 1. 提取存储过程定义
            AddBlockIfNotEmpty(blocks, "ProcedureDefinition", RegexUtils.ExtractProcedureDefinition(sql));
            // 2. 提取变量声明
            AddBlockIfNotEmpty(blocks, "VariableDeclaration", RegexUtils.ExtractVariableDeclarations(sql));
            // 3. 提取 DML 语句(INSERT/UPDATE/DELETE)
            blocks.AddRange(RegexUtils.ExtractDmlStatements(sql));
            // 4. 提取查询语句(SELECT,排除 INTO 场景)
            blocks.AddRange(RegexUtils.ExtractQueries(sql));
            // 5. 提取分支逻辑(IF/CASE,支持嵌套)
            blocks.AddRange(RegexUtils.ExtractBranchingLogic(sql));
            // 6. 提取循环(LOOP/WHILE/FOR,支持嵌套)
            blocks.AddRange(RegexUtils.ExtractLoops(sql));
            // 7. 提取游标(DECLARE/OPEN/FETCH/CLOSE)
            AddBlockIfNotEmpty(blocks, "Cursor", RegexUtils.ExtractCursor(sql));
            // 8. 提取异常处理(BEGIN...EXCEPTION)
            AddBlockIfNotEmpty(blocks, "Exception", RegexUtils.ExtractExceptionHandling(sql));
            // 9. 提取事务控制(COMMIT/ROLLBACK)
            AddBlockIfNotEmpty(blocks, "Transaction", RegexUtils.ExtractTransactionControl(sql));
            // 10. 提取临时表(VOLATILE/TEMP TABLE)
            AddBlockIfNotEmpty(blocks, "TempTable", RegexUtils.ExtractTempTables(sql));
            // 11. 提取函数调用(如 schema.func())
            AddBlockIfNotEmpty(blocks, "FunctionMapping", RegexUtils.ExtractFunctionCalls(sql));
            // 12. 提取依赖对象(FROM/JOIN 表)
            AddBlockIfNotEmpty(blocks, "Dependency", RegexUtils.ExtractDependencies(sql));

            // 提取嵌套块(补充处理深层嵌套结构)
            blocks.AddRange(RegexUtils.ExtractNestedBlocks(sql, "BEGIN", "END"));

            return blocks;
        }

        /// <summary>
        /// 非空时添加代码块(避免空内容块)
        /// </summary>
        private void AddBlockIfNotEmpty(List<CodeBlock> blocks, string blockType, string content)
        {
            if (!string.IsNullOrEmpty(content))
            {
                blocks.Add(new CodeBlock 
                { 
                    BlockType = blockType, 
                    Content = content.Trim() 
                });
            }
        }
    }
}

5. 正则工具集(Utils/RegexUtils.cs)

using System.Collections.Generic;
using System.Text.RegularExpressions;
using TeradataToRedshiftConverter.Models;

namespace TeradataToRedshiftConverter.Utils
{
    /// <summary>
    /// 正则工具集:提供 12 类代码块的提取逻辑,支持嵌套结构
    /// </summary>
    public static class RegexUtils
    {
        // 通用正则选项:不区分大小写 + 单行模式(. 匹配换行)
        private const RegexOptions DefaultOptions = RegexOptions.IgnoreCase | RegexOptions.Singleline;

        #region 1. 存储过程定义提取
        public static string ExtractProcedureDefinition(string sql)
        {
            var regex = new Regex(@"(REPLACE\s+PROCEDURE|CREATE\s+PROCEDURE)\s+.+?(?=BEGIN|;)", DefaultOptions);
            var match = regex.Match(sql);
            return match.Success ? match.Value : null;
        }
        #endregion

        #region 2. 变量声明提取
        public static string ExtractVariableDeclarations(string sql)
        {
            var regex = new Regex(@"(DECLARE\s+.+?;)(?:\s*DECLARE\s+.+?;)*", DefaultOptions);
            var match = regex.Match(sql);
            return match.Success ? match.Value : null;
        }
        #endregion

        #region 3. DML 语句提取(INSERT/UPDATE/DELETE)
        public static List<CodeBlock> ExtractDmlStatements(string sql)
        {
            var regex = new Regex(@"(INSERT\s+.+?;|UPDATE\s+.+?;|DELETE\s+.+?;)", DefaultOptions);
            return MatchToBlocks(regex, sql, "DML");
        }
        #endregion

        #region 4. 查询语句提取(SELECT)
        public static List<CodeBlock> ExtractQueries(string sql)
        {
            // 排除 SELECT ... INTO 场景(避免与变量赋值冲突)
            var regex = new Regex(@"(SELECT\s+.+?;)(?!\s*INTO\s+)", DefaultOptions);
            return MatchToBlocks(regex, sql, "Query");
        }
        #endregion

        #region 5. 分支逻辑提取(IF/CASE)
        public static List<CodeBlock> ExtractBranchingLogic(string sql)
        {
            var blocks = new List<CodeBlock>();

            // IF 语句(支持嵌套)
            var ifRegex = new Regex(@"IF\s+.+?\s+THEN.+?(?:ELSIF\s+.+?\s+THEN.+?)*\s*(?:ELSE.+?)?\s*END\s+IF;", DefaultOptions);
            blocks.AddRange(MatchToBlocks(ifRegex, sql, "Branching"));

            // CASE 语句
            var caseRegex = new Regex(@"CASE\s+(?:WHEN\s+.+?\s+THEN\s+.+?)+\s*(?:ELSE\s+.+?)?\s*END\s*(?:CASE)?;", DefaultOptions);
            blocks.AddRange(MatchToBlocks(caseRegex, sql, "Branching"));

            return blocks;
        }
        #endregion

        #region 6. 循环提取(LOOP/WHILE/FOR)
        public static List<CodeBlock> ExtractLoops(string sql)
        {
            var blocks = new List<CodeBlock>();

            // LOOP
            var loopRegex = new Regex(@"LOOP\s+.+?\s+END\s+LOOP;", DefaultOptions);
            blocks.AddRange(MatchToBlocks(loopRegex, sql, "Loop"));

            // WHILE LOOP
            var whileRegex = new Regex(@"WHILE\s+.+?\s+LOOP\s+.+?\s+END\s+LOOP;", DefaultOptions);
            blocks.AddRange(MatchToBlocks(whileRegex, sql, "Loop"));

            // FOR LOOP
            var forRegex = new Regex(@"FOR\s+.+?\s+LOOP\s+.+?\s+END\s+LOOP;", DefaultOptions);
            blocks.AddRange(MatchToBlocks(forRegex, sql, "Loop"));

            return blocks;
        }
        #endregion

        #region 7. 游标提取
        public static string ExtractCursor(string sql)
        {
            var regex = new Regex(@"(DECLARE\s+\w+\s+CURSOR\s+FOR\s+.+?;)(?:\s*OPEN\s+\w+;)?(?:\s*FETCH\s+\w+\s+INTO\s+.+?;)?(?:\s*CLOSE\s+\w+;)?", DefaultOptions);
            var match = regex.Match(sql);
            return match.Success ? match.Value : null;
        }
        #endregion

        #region 8. 异常处理提取
        public static string ExtractExceptionHandling(string sql)
        {
            var regex = new Regex(@"(BEGIN\s+.+?\s+EXCEPTION\s+WHEN\s+.+?\s+THEN\s+.+?\s+END;)", DefaultOptions);
            var match = regex.Match(sql);
            return match.Success ? match.Value : null;
        }
        #endregion

        #region 9. 事务控制提取
        public static string ExtractTransactionControl(string sql)
        {
            var regex = new Regex(@"(COMMIT;|ROLLBACK;)", DefaultOptions);
            var match = regex.Match(sql);
            return match.Success ? match.Value : null;
        }
        #endregion

        #region 10. 临时表提取
        public static string ExtractTempTables(string sql)
        {
            var regex = new Regex(@"(CREATE\s+(?:VOLATILE|TEMP)\s+TABLE\s+.+?;)", DefaultOptions);
            var match = regex.Match(sql);
            return match.Success ? match.Value : null;
        }
        #endregion

        #region 11. 函数调用提取
        public static string ExtractFunctionCalls(string sql)
        {
            var regex = new Regex(@"(\w+\.\w+\([^)]*\))", DefaultOptions);
            var match = regex.Match(sql);
            return match.Success ? match.Value : null;
        }
        #endregion

        #region 12. 依赖对象提取
        public static string ExtractDependencies(string sql)
        {
            var regex = new Regex(@"(FROM\s+(\w+\.)?\w+|JOIN\s+(\w+\.)?\w+)", DefaultOptions);
            var match = regex.Match(sql);
            return match.Success ? match.Value : null;
        }
        #endregion

        #region 嵌套块提取(支持深层嵌套)
        public static List<CodeBlock> ExtractNestedBlocks(string sql, string startKeyword, string endKeyword)
        {
            var blocks = new List<CodeBlock>();
            int depth = 0; // 嵌套深度计数器
            int startIdx = -1; // 嵌套块起始索引

            for (int i = 0; i < sql.Length; i++)
            {
                // 匹配起始关键字(如 BEGIN)
                if (i + startKeyword.Length <= sql.Length && 
                    sql.Substring(i, startKeyword.Length).Equals(startKeyword, StringComparison.OrdinalIgnoreCase))
                {
                    if (depth == 0) startIdx = i; // 记录最外层起始位置
                    depth++;
                    i += startKeyword.Length - 1; // 跳过已匹配的字符
                }
                // 匹配结束关键字(如 END)
                else if (i + endKeyword.Length <= sql.Length && 
                         sql.Substring(i, endKeyword.Length).Equals(endKeyword, StringComparison.OrdinalIgnoreCase))
                {
                    depth--;
                    // 嵌套闭合时,提取完整块
                    if (depth == 0 && startIdx >= 0)
                    {
                        string content = sql.Substring(startIdx, i + endKeyword.Length - startIdx);
                        blocks.Add(new CodeBlock 
                        { 
                            BlockType = "NestedBlock", 
                            Content = content.Trim() 
                        });
                        startIdx = -1;
                    }
                    i += endKeyword.Length - 1; // 跳过已匹配的字符
                }
            }

            return blocks;
        }
        #endregion

        #region 辅助方法:将正则匹配结果转为 CodeBlock 列表
        private static List<CodeBlock> MatchToBlocks(Regex regex, string sql, string blockType)
        {
            var blocks = new List<CodeBlock>();
            foreach (Match match in regex.Matches(sql))
            {
                if (match.Success)
                {
                    blocks.Add(new CodeBlock 
                    { 
                        BlockType = blockType, 
                        Content = match.Value.Trim() 
                    });
                }
            }
            return blocks;
        }
        #endregion
    }
}

6. 核心转换器示例(Services/Converters/FunctionMappingConverter.cs)

using System.Collections.Generic;
using System.Text.RegularExpressions;
using TeradataToRedshiftConverter.Utils;

namespace TeradataToRedshiftConverter.Services.Converters
{
    /// <summary>
    /// 函数映射转换器:将 Teradata 函数转为 Redshift 兼容函数(如 ADD_MONTHS → DATE_TRUNC+INTERVAL)
    /// </summary>
    public class FunctionMappingConverter
    {
        // Teradata → Redshift 函数映射表(不区分大小写)
        private static readonly Dictionary<string, string> FunctionMap = new(StringComparer.OrdinalIgnoreCase)
        {
            #region 日期函数
            {"CURRENT_DATE", "CURRENT_DATE"},
            {"LAST_DAY", "(DATE_TRUNC('month', {0}) + INTERVAL '1 month' - INTERVAL '1 day')::DATE"},
            {"MONTHS_BETWEEN", "EXTRACT(MONTH FROM AGE({1}, {0}))"},
            #endregion

            #region 字符串函数
            {"SUBSTR", "SUBSTRING"},
            {"CHAR_LENGTH", "LENGTH"},
            {"TRIM(BOTH", "TRIM("},
            {"TRIM(LEADING", "LTRIM("},
            {"TRIM(TRAILING", "RTRIM("},
            #endregion

            #region 数学函数
            {"MOD", "MOD"},
            {"ROUND", "ROUND"},
            {"TRUNC", "TRUNC"},
            #endregion

            #region 分析函数
            {"RANK", "RANK"},
            {"ROW_NUMBER", "ROW_NUMBER"},
            {"LAG", "LAG"},
            {"LEAD", "LEAD"},
            #endregion

            #region 类型转换函数
            {"CAST", "CAST"},
            {"TO_CHAR", "TO_CHAR"},
            {"TO_DATE", "TO_DATE"},
            {"TO_NUMBER", "TO_NUMBER"},
            #endregion
        };

        public string Convert(string teradataCode)
        {
            string convertedCode = teradataCode;

            // 1. 处理常规函数名映射(直接替换函数名)
            foreach (var (teradataFunc, redshiftFunc) in FunctionMap)
            {
                // 匹配函数名(确保是独立单词,避免部分匹配)
                string pattern = $@"\b{Regex.Escape(teradataFunc)}\b(?=\()";
                convertedCode = CachedRegexUtils.Replace(
                    convertedCode, 
                    pattern, 
                    redshiftFunc, 
                    RegexOptions.IgnoreCase
                );
            }

            // 2. 处理特殊函数(需调整参数顺序或格式)
            convertedCode = ConvertSpecialFunctions(convertedCode);

            // 3. 添加转换提示(便于人工校验)
            return convertedCode + "\n-- ✅ 函数映射已应用(请检查参数顺序)";
        }

        /// <summary>
        /// 处理特殊函数(需自定义逻辑,如参数重排)
        /// </summary>
        private string ConvertSpecialFunctions(string code)
        {
            // 示例1:ADD_MONTHS(date, n) → Redshift 日期计算
            var addMonthsRegex = new Regex(
                @"ADD_MONTHS\((?<date>[^,]+),\s*(?<months>[^)]+)\)", 
                RegexOptions.IgnoreCase | RegexOptions.Singleline
            );
            code = addMonthsRegex.Replace(
                code, 
                match => $"({match.Groups["date"].Value} + INTERVAL '{match.Groups["months"].Value} month')::DATE"
            );

            // 示例2:其他特殊函数可在此扩展(如 TERADATA_SPECIFIC_FUNC → REDSHIFT_FUNC)
            return code;
        }
    }
}

7. 测试报告生成器(Tests/ReportGenerator/VisualTestReporter.cs)

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace TeradataToRedshiftConverter.Tests.ReportGenerator
{
    /// <summary>
    /// 可视化测试报告生成器:生成 HTML 格式报告,含输入/预期/实际结果对比
    /// </summary>
    public class VisualTestReporter
    {
        /// <summary>
        /// 生成测试报告
        /// </summary>
        /// <param name="results">测试结果列表</param>
        /// <param name="outputPath">报告输出路径</param>
        public void GenerateReport(List<TestResult> results, string outputPath)
        {
            if (results == null || results.Count == 0)
                throw new ArgumentException("测试结果不能为空");

            var htmlBuilder = new StringBuilder();
            // HTML 头部(含样式)
            htmlBuilder.AppendLine("<!DOCTYPE html>");
            htmlBuilder.AppendLine("<html lang='zh-CN'>");
            htmlBuilder.AppendLine("<head>");
            htmlBuilder.AppendLine("<meta charset='UTF-8'>");
            htmlBuilder.AppendLine("<title>TeradataToRedshift 转换测试报告</title>");
            htmlBuilder.AppendLine("<style>");
            htmlBuilder.AppendLine("  body { font-family: Arial, sans-serif; margin: 20px; }");
            htmlBuilder.AppendLine("  .report-header { text-align: center; margin-bottom: 30px; }");
            htmlBuilder.AppendLine("  .test-block { margin: 20px 0; padding: 15px; border-radius: 8px; }");
            htmlBuilder.AppendLine("  .pass { background-color: #e8f5e9; border: 1px solid #c8e6c9; }");
            htmlBuilder.AppendLine("  .fail { background-color: #ffebee; border: 1px solid #ffcdd2; }");
            htmlBuilder.AppendLine("  .test-name { margin: 0; font-size: 18px; }");
            htmlBuilder.AppendLine("  .test-meta { color: #666; margin: 5px 0; }");
            htmlBuilder.AppendLine("  .code-block { background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; }");
            htmlBuilder.AppendLine("  .error-message { color: #d32f2f; font-weight: bold; }");
            htmlBuilder.AppendLine("</style>");
            htmlBuilder.AppendLine("</head>");
            htmlBuilder.AppendLine("<body>");

            // 报告头部(统计信息)
            int passCount = results.FindAll(r => r.Passed).Count;
            htmlBuilder.AppendLine("<div class='report-header'>");
            htmlBuilder.AppendLine($"<h1>TeradataToRedshift 转换测试报告</h1>");
            htmlBuilder.AppendLine($"<p>测试时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}</p>");
            htmlBuilder.AppendLine($"<p>总用例数:{results.Count} | 通过率:{passCount}/{results.Count} ({(passCount * 100.0 / results.Count):F1}%)</p>");
            htmlBuilder.AppendLine("</div>");

            // 测试结果详情
            foreach (var result in results)
            {
                string blockClass = result.Passed ? "test-block pass" : "test-block fail";
                htmlBuilder.AppendLine($"<div class='{blockClass}'>");
                htmlBuilder.AppendLine($"<h3 class='test-name'>{result.TestName}</h3>");
                htmlBuilder.AppendLine($"<div class='test-meta'>耗时:{result.DurationMs}ms | 状态:{(result.Passed ? "通过" : "失败")}</div>");

                // 失败时显示错误信息
                if (!result.Passed && !string.IsNullOrEmpty(result.ErrorMessage))
                {
                    htmlBuilder.AppendLine($"<div class='error-message'>错误信息:{result.ErrorMessage}</div>");
                }

                // 显示输入 SQL
                htmlBuilder.AppendLine("<div>");
                htmlBuilder.AppendLine("<h4>输入 SQL:</h4>");
                htmlBuilder.AppendLine($"<pre class='code-block'>{EscapeHtml(result.InputSql)}</pre>");
                htmlBuilder.AppendLine("</div>");

                // 显示预期与实际结果(失败时必显,通过时可选)
                if (!result.Passed || true)
                {
                    htmlBuilder.AppendLine("<div style='display: flex; gap: 20px; margin-top: 10px;'>");
                    // 预期结果
                    htmlBuilder.AppendLine("<div style='flex: 1;'>");
                    htmlBuilder.AppendLine("<h4>预期输出:</h4>");
                    htmlBuilder.AppendLine($"<pre class='code-block'>{EscapeHtml(result.ExpectedOutput)}</pre>");
                    htmlBuilder.AppendLine("</div>");
                    // 实际结果
                    htmlBuilder.AppendLine("<div style='flex: 1;'>");
                    htmlBuilder.AppendLine("<h4>实际输出:</h4>");
                    htmlBuilder.AppendLine($"<pre class='code-block'>{EscapeHtml(result.ActualOutput)}</pre>");
                    htmlBuilder.AppendLine("</div>");
                    htmlBuilder.AppendLine("</div>");
                }

                htmlBuilder.AppendLine("</div>");
            }

            htmlBuilder.AppendLine("</body></html>");

            // 写入文件(确保目录存在)
            string directory = Path.GetDirectoryName(outputPath);
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }
            File.WriteAllText(outputPath, htmlBuilder.ToString(), System.Text.Encoding.UTF8);
        }

        /// <summary>
        /// HTML 转义(避免特殊字符导致页面错乱)
        /// </summary>
        private string EscapeHtml(string text)
        {
            if (string.IsNullOrEmpty(text))
                return "";
            return text.Replace("&", "&amp;")
                      .Replace("<", "&lt;")
                      .Replace(">", "&gt;")
                      .Replace("\"", "&quot;")
                      .Replace("'", "&#39;")
                      .Replace("\n", "<br>")
                      .Replace(" ", "&nbsp;");
        }
    }

    /// <summary>
    /// 测试结果模型:存储单条用例的测试数据
    /// </summary>
    public class TestResult
    {
        /// <summary>
        /// 测试用例名称
        /// </summary>
        public string TestName { get; set; }

        /// <summary>
        /// 是否通过
        /// </summary>
        public bool Passed { get; set; }

        /// <summary>
        /// 测试耗时(毫秒)
        /// </summary>
        public long DurationMs { get; set; }

        /// <summary>
        /// 错误信息(失败时填写)
        /// </summary>
        public string ErrorMessage { get; set; }

        /// <summary>
        /// 输入 SQL(Teradata 代码)
        /// </summary>
        public string InputSql { get; set; }

        /// <summary>
        /// 预期输出(Redshift 代码)
        /// </summary>
        public string ExpectedOutput { get; set; }

        /// <summary>
        /// 实际输出(转换后的代码)
        /// </summary>
        public string ActualOutput { get; set; }
    }
}

三、项目功能描述

1. 核心功能

功能模块具体能力
多类型代码转换支持存储过程、变量、DML、查询、分支、循环等 12 类语法元素的精准转换
嵌套结构处理通过深度计数器解析嵌套 IF/LOOP/CURSOR,避免分块遗漏或错误
函数智能映射内置 20+ 常用函数映射(如 ADD_MONTHS → DATE_TRUNC+INTERVAL),支持扩展
大文件流式处理提供分块读取接口,处理 GB 级 SQL 文件时避免内存溢出
并行转换优化按 CPU 核心数控制并发,提升多代码块转换效率

2. 测试与报告

测试类型具体能力
单元测试验证单个转换器的准确性(如函数映射、变量转换),支持参数化测试
嵌套结构测试动态生成深层嵌套 SQL(如 50 层 IF 嵌套),验证分块逻辑稳定性
性能测试对比缓存正则与普通正则的性能差异,确保工具处理效率
可视化报告生成 HTML 报告,含输入/预期/实际结果对比,支持错误信息定位

3. 部署与运行

  1. 环境准备:安装 .NET 6 SDK 或更高版本
  2. 项目构建dotnet build TeradataToRedshiftConverter.sln
  3. 转换命令dotnet run --project TeradataToRedshiftConverter <输入SQL路径> <输出SQL路径>
  4. 测试执行dotnet test --logger "html;logfilename=TestResults/testReport.html"
  5. 报告查看:打开 TestResults/testReport.html 查看可视化测试结果

四、依赖与配置

1. 项目依赖

依赖包名称版本用途
Microsoft.IO.RecyclableMemoryStream2.3.2优化内存流使用,减少 GC 压力
xunit2.4.2单元测试框架
Xunit.Xml.TestLogger2.0.0生成 XML 格式测试日志
ReportGenerator5.1.10生成 HTML 格式测试覆盖率报告

2. 配置文件(appsettings.json 示例)

{
  "ConversionSettings": {
    "MaxDegreeOfParallelism": 4, // 最大并行数,默认 CPU 核心数
    "ChunkSize": 4096,            // 分块读取大小(字节),默认 4KB
    "EnableFunctionMapping": true // 是否启用函数映射,默认 true
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "TeradataToRedshiftConverter": "Debug"
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值