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)

csharp 复制代码
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)

csharp 复制代码
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)

csharp 复制代码
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)

csharp 复制代码
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)

csharp 复制代码
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)

csharp 复制代码
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)

csharp 复制代码
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.RecyclableMemoryStream 2.3.2 优化内存流使用,减少 GC 压力
xunit 2.4.2 单元测试框架
Xunit.Xml.TestLogger 2.0.0 生成 XML 格式测试日志
ReportGenerator 5.1.10 生成 HTML 格式测试覆盖率报告

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

json 复制代码
{
  "ConversionSettings": {
    "MaxDegreeOfParallelism": 4, // 最大并行数,默认 CPU 核心数
    "ChunkSize": 4096,            // 分块读取大小(字节),默认 4KB
    "EnableFunctionMapping": true // 是否启用函数映射,默认 true
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "TeradataToRedshiftConverter": "Debug"
    }
  }
}
相关推荐
青~4 小时前
sql 双游标循环
数据库·sql
一辉ComeOn4 小时前
【大数据高并发核心场景实战】 数据持久化层 - 查询分离
java·大数据·数据库·elasticsearch·缓存·oracle
是萝卜干呀4 小时前
Backend - HTTP请求的常用返回类型(asp .net core MVC)
http·c#·.netcore·iactionresult
雾里云山4 小时前
pgsql常用函数
java·开发语言·数据库·sql
好记忆不如烂笔头abc4 小时前
通过gdb推进修改oracle scn
数据库·oracle
爱思德学术5 小时前
中国计算机学会(CCF)推荐学术会议-B(数据库/数据挖掘/内容检索):PODS 2026
数据库·数据分析·数据可视化·数据库系统
l1t5 小时前
用Lua访问DuckDB数据库
数据库·junit·lua·duckdb
星空露珠5 小时前
数独生成题目lua脚本
数据结构·数据库·算法·游戏·lua
deephub5 小时前
构建有记忆的 AI Agent:SQLite 存储 + 向量检索完整方案示例
数据库·人工智能·sqlite·大语言模型·向量检索·智能体