该项目是一款基于 .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; }
}
}
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("&", "&")
.Replace("<", "<")
.Replace(">", ">")
.Replace("\"", """)
.Replace("'", "'")
.Replace("\n", "<br>")
.Replace(" ", " ");
}
}
/// <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. 部署与运行
- 环境准备:安装 .NET 6 SDK 或更高版本
- 项目构建 :
dotnet build TeradataToRedshiftConverter.sln
- 转换命令 :
dotnet run --project TeradataToRedshiftConverter <输入SQL路径> <输出SQL路径>
- 测试执行 :
dotnet test --logger "html;logfilename=TestResults/testReport.html"
- 报告查看 :打开
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"
}
}
}