构建一个完整的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"