信创MySQL到达梦数据库的SQL语法转换技术解析
信创背景下MySQL到达梦数据库的SQL语法转换技术解析
一、背景介绍
在信创(信息技术应用创新)产业快速发展的背景下,国产数据库的替代已成为必然趋势。达梦数据库作为国产数据库的重要代表,在企业级应用中逐渐取代MySQL等国外数据库产品。然而,由于SQL语法在不同数据库间存在差异,如何实现平滑迁移成为亟需解决的技术难题。
本文基于实际的SQL语法转换器实现,深入解析MySQL到达梦数据库的SQL转换技术方案。
二、整体架构设计
2.1 核心转换流程
SQL转换器的核心架构采用抽象语法树(AST)解析+策略模式的设计:
// 基类定义通用转换框架
public abstract class BaseConverter {// 存储各类SQL语句的转换结果public List<String> creatTableList = new ArrayList<>();public List<String> commentList = new ArrayList<>();// ...其他语句列表// 策略映射表:语句类型→处理函数private final Map<Class<?>, Consumer<Object>> conversionStrategies = new HashMap<>();// 初始化策略映射public BaseConverter() {conversionStrategies.put(MySqlCreateTableStatement.class, stmt -> convertCreateTabletStatement((MySqlCreateTableStatement) stmt));conversionStrategies.put(MySqlInsertStatement.class, stmt -> convertInsertStatement((MySqlInsertStatement) stmt));// ...其他语句类型映射}
}
2.2 转换执行流程
- SQL解析:使用Druid解析器将原始SQL转换为AST
- 语句分发:根据语句类型选择对应的处理策略
- 语法转换:针对特定数据库进行语法适配
- 结果收集:将转换后的SQL语句分类存储
三、关键技术实现
3.1 函数映射转换
不同数据库的函数差异是转换的重点难点,我们采用函数映射表的方式解决:
// MySQL与达梦函数映射表
private static final Map<String, String> FUNCTION_MAPPING = new HashMap<>();
static {FUNCTION_MAPPING.put("CONCAT", "||");FUNCTION_MAPPING.put("NOW()", "SYSDATE");FUNCTION_MAPPING.put("DATE_FORMAT", "TO_CHAR");FUNCTION_MAPPING.put("SUBSTRING", "SUBSTR");FUNCTION_MAPPING.put("IFNULL", "NVL");FUNCTION_MAPPING.put("UUID", "SYS_GUID");// ...更多函数映射
}
3.2 分页语法转换
MySQL的LIMIT语法与达梦的ROWNUM语法存在显著差异:
private static String convertPagination(String query) {// 转换 LIMIT offset, size 语法Pattern pattern1 = Pattern.compile("LIMIT\\s+(\\d+)\\s*,\\s*(\\d+)", Pattern.CASE_INSENSITIVE);Matcher matcher1 = pattern1.matcher(query);if (matcher1.find()) {String offset = matcher1.group(1);String limit = matcher1.group(2);return matcher1.replaceFirst("OFFSET " + offset + " FETCH FIRST " + limit + " ROWS ONLY");}// 其他分页模式处理...
}
3.3 数据类型映射
数据类型在不同数据库间存在差异,需要特殊处理:
// 数据类型转换示例
if ("VARCHAR".equals(dataTypeName)) {builder.append("\"").append(columnName).append("\" ").append(dataTypeName).append("(").append(sqlIntegerExpr.getNumber()).append(")");
} else if ("TIMESTAMP".equals(dataTypeName) || "DATETIME".equals(dataTypeName)) {builder.append("\"").append(columnName).append("\" ").append("TIMESTAMP(0)");
} else if ("BIT".equals(dataTypeName)) {builder.append("\"").append(columnName).append("\" ").append("TINYINT");
}
// ...更多类型处理
四、复杂语句处理
4.1 CREATE TABLE语句解析
建表语句涉及表结构、约束、索引等多方面内容:
@Override
public void convertCreateTabletStatement(MySqlCreateTableStatement statement) {String tableName = statement.getTableName().replace("`", "").toUpperCase();// 处理表注释if (Objects.nonNull(statement.getComment())) {commentList.add("COMMENT ON TABLE \"" + tableName + "\"" + " IS '" + comment + "';");}// 处理表元素(列、约束等)statement.getTableElementList().forEach(column -> {if (column instanceof SQLColumnDefinition) {// 列定义处理processColumnDefinition((SQLColumnDefinition) column, tableName);} else if (column instanceof MySqlPrimaryKey) {// 主键约束处理processPrimaryKey((MySqlPrimaryKey) column, tableName);}// ...其他元素类型});
}
4.2 SELECT查询处理
查询语句需要处理函数、别名、关联查询等复杂场景:
private String processSelectQuery(SQLSelectQuery query) {if (query instanceof SQLSelectQueryBlock) {SQLSelectQueryBlock block = (SQLSelectQueryBlock) query;// 处理SELECT子句processSelectItems(block.getSelectList());// 处理FROM子句processTableSource(block.getFrom());// 处理WHERE条件processWhereCondition(block.getWhere());// 处理GROUP BY和HAVINGprocessGroupBy(block.getGroupBy());// 处理分页processLimit(block.getLimit());}// ...联合查询处理
}
五、特殊场景处理
5.1 自增字段处理
达梦使用IDENTITY语法替代MySQL的AUTO_INCREMENT:
if (autoIncrement) {if (StringUtils.isNotBlank(increment)) {builder.append(" IDENTITY(").append(increment).append(",1)");} else {builder.append(" IDENTITY(1,1)");}
}
5.2 索引和约束处理
不同数据库的索引创建语法需要适配:
// 唯一索引转换
if ("UNIQUE".equals(type)) {String indexName = "UNIQUE_" + tableName + "_" + StringUtils.join(keys, "_");indexList.add("ALTER TABLE \"" + tableName + "\" ADD CONSTRAINT \"" + indexName + "\" UNIQUE(" + StringUtils.join(keys, ",") + ");");
} else {// 普通索引String indexName = "IDX_" + tableName + "_" + StringUtils.join(keys, "_");indexList.add("CREATE INDEX \"" + indexName + "\" ON \"" + tableName + "\"(" + StringUtils.join(keys, ",") + ");");
}
六、转换效果示例
MySQL原始SQL:
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(50) DEFAULT NULL,`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';SELECT * FROM `user` WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2023-01-01' LIMIT 10;
转换后的达梦SQL:
CREATE TABLE "USER" ("ID" INT IDENTITY(1,1) NOT NULL,"NAME" VARCHAR(50) NULL,"CREATE_TIME" TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP NULL
);
COMMENT ON TABLE "USER" IS '用户表';
COMMENT ON COLUMN "USER"."ID" IS '';SELECT * FROM "USER" WHERE TO_CHAR(CREATE_TIME, 'YYYY-MM-DD') = '2023-01-01' FETCH FIRST 10 ROWS ONLY;
七、原始代码
基础代码
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLTruncateStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement;
import com.alibaba.druid.util.JdbcConstants;
import lombok.extern.slf4j.Slf4j;import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;public abstract class BaseConverter {/*** createTable sql语句*/public List<String> creatTableList = new ArrayList<>();/*** comment sql语句*/public List<String> commentList = new ArrayList<>();/*** index sql语句*/public List<String> indexList = new ArrayList<>();/*** alter sql语句*/public List<String> alterList = new ArrayList<>();/*** insert sql语句*/public List<String> insertList = new ArrayList<>();/*** update sql语句*/public List<String> updateList = new ArrayList<>();/*** select sql语句*/public List<String> selectList = new ArrayList<>();/*** sequence sql语句*/public List<String> sequenceList = new ArrayList<>();/*** alterSequence sql语句*/public List<String> alterSequenceList = new ArrayList<>();/*** sql别名及函数*/public final Map<String, SQLExpr> selectAliasMap = new HashMap<>();/*** SQL语法树映射*/private final Map<Class<?>, Consumer<Object>> conversionStrategies = new HashMap<>();/*** 初始化策略映射*/public BaseConverter() {conversionStrategies.put(MySqlCreateTableStatement.class, stmt -> convertCreateTabletStatement((MySqlCreateTableStatement) stmt));conversionStrategies.put(MySqlInsertStatement.class, stmt -> convertInsertStatement((MySqlInsertStatement) stmt));conversionStrategies.put(SQLCreateIndexStatement.class, stmt -> convertCreateIndexStatement((SQLCreateIndexStatement) stmt));conversionStrategies.put(MySqlUpdateStatement.class, stmt -> convertUpdateStatement((MySqlUpdateStatement) stmt));conversionStrategies.put(MySqlDeleteStatement.class, stmt -> convertDeleteStatement((MySqlDeleteStatement) stmt));conversionStrategies.put(SQLAlterTableStatement.class, stmt -> convertAlterTableStatement((SQLAlterTableStatement) stmt));conversionStrategies.put(MySqlRenameTableStatement.class, stmt -> convertRenameTableStatement((MySqlRenameTableStatement) stmt));conversionStrategies.put(SQLDropTableStatement.class, stmt -> convertDropTableStatement((SQLDropTableStatement) stmt));conversionStrategies.put(SQLTruncateStatement.class, stmt -> convertTruncateStatement((SQLTruncateStatement) stmt));conversionStrategies.put(SQLSelectStatement.class, stmt -> convertQueryStatement((SQLSelectStatement) stmt));}/*** 转换器适配** @param dbType 数据库类型* @return 是否适配*/abstract boolean support(String dbType);/*** 翻译转换** @return 结果集*/public List<String> convert(String schemaName, String originalSql) {List<SQLStatement> stmtList = SQLUtils.parseStatements(originalSql, JdbcConstants.MYSQL);List<String> result = new ArrayList<>();stmtList.forEach(statement -> {// 语法树翻译转化processConvertStatement(statement);List<String> collect = Stream.of(creatTableList, alterSequenceList, sequenceList, commentList, indexList, alterList, insertList, updateList, selectList).flatMap(Collection::stream).filter(Objects::nonNull).collect(Collectors.toList());if (collect.isEmpty()) {collect.add(statement.toString());}result.addAll(collect);});return result;}/*** 语法树翻译转化** @param statement 抽象语法树*/private void processConvertStatement(SQLStatement statement) {Consumer<Object> consumer = conversionStrategies.get(statement.getClass());if (Objects.isNull(consumer)) {log.error("Unsupported statement type: " + statement.getClass());return;}consumer.accept(statement);}/*** DDL-CREATE 语法树转换** @param statement 语法树*/abstract void convertCreateTabletStatement(MySqlCreateTableStatement statement);/*** DML-INSERT 语法树转换** @param statement 语法树*/abstract void convertInsertStatement(MySqlInsertStatement statement);/*** DDL-INDEX 语法树转换** @param statement 语法树*/abstract void convertCreateIndexStatement(SQLCreateIndexStatement statement);/*** DML-UPDATE 语法树转换** @param statement 语法树*/abstract void convertUpdateStatement(MySqlUpdateStatement statement);/*** DML-DELETE 语法树转换** @param statement 语法树*/abstract void convertDeleteStatement(MySqlDeleteStatement statement);/*** DLL-ALTER TABLE 语法树转换** @param statement 语法树*/abstract void convertAlterTableStatement(SQLAlterTableStatement statement);/*** DDL-RENAME 语法树转换** @param statement 语法树*/abstract void convertRenameTableStatement(MySqlRenameTableStatement statement);/*** DDL-Drop语法树转换** @param statement 语法树*/abstract void convertDropTableStatement(SQLDropTableStatement statement);/*** 截断操作语法树转换** @param statement 语法树*/abstract void convertTruncateStatement(SQLTruncateStatement statement);/*** 查询语法树转换** @param statement 语法树*/abstract void convertQueryStatement(SQLSelectStatement statement);}
达梦代码
import com.alibaba.druid.sql.ast.SQLCurrentTimeExpr;
import com.alibaba.druid.sql.ast.SQLDataTypeImpl;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLIndexDefinition;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.SQLOrderingSpecification;
import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLInListExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr;
import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.druid.sql.ast.expr.SQLNullExpr;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddColumn;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddConstraint;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddIndex;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLAssignItem;
import com.alibaba.druid.sql.ast.statement.SQLColumnConstraint;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLConstraint;
import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement;
import com.alibaba.druid.sql.ast.statement.SQLDropTableStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLNotNullConstraint;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectQuery;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableElement;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTruncateStatement;
import com.alibaba.druid.sql.ast.statement.SQLUnionQuery;
import com.alibaba.druid.sql.dialect.mysql.ast.MySqlKey;
import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey;
import com.alibaba.druid.sql.dialect.mysql.ast.MySqlUnique;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlAlterTableChangeColumn;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlAlterTableModifyColumn;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement;
import com.alibaba.druid.sql.visitor.SQLASTVisitor;
import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter;
import com.alibaba.druid.util.JdbcConstants;
import org.apache.commons.lang3.StringUtils;import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class DamengConverter extends BaseConverter {/*** 初始化策略映射*/public DamengConverter() {super();}/*** 定义MySQL和达梦数据库的函数映射*/private static final Map<String, String> FUNCTION_MAPPING = new HashMap<>();static {FUNCTION_MAPPING.put("CONCAT", "||");FUNCTION_MAPPING.put("NOW()", "SYSDATE");FUNCTION_MAPPING.put("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP");FUNCTION_MAPPING.put("DATE_FORMAT", "TO_CHAR");FUNCTION_MAPPING.put("SUBSTRING", "SUBSTR");FUNCTION_MAPPING.put("DATEDIFF", "-");FUNCTION_MAPPING.put("IFNULL", "NVL");FUNCTION_MAPPING.put("UUID", "SYS_GUID");FUNCTION_MAPPING.put("GROUP_CONCAT", "WM_CONCAT");FUNCTION_MAPPING.put("FIND_IN_SET", "INSTR");FUNCTION_MAPPING.put("UNIX_TIMESTAMP", "EXTRACT(EPOCH FROM SYSDATE)");FUNCTION_MAPPING.put("LOCATE", "INSTR");FUNCTION_MAPPING.put("COALESCE", "COALESCE");FUNCTION_MAPPING.put("REPLACE", "REPLACE");}/*** 转换器适配** @param dbType 数据库类型* @return 是否适配*/@Overridepublic boolean support(String dbType) {return JdbcConstants.DM.name().equals(dbType);}private static String convertPagination(String query) {// 匹配 LIMIT offset, sizePattern pattern1 = Pattern.compile("LIMIT\\s+(\\d+)\\s*,\\s*(\\d+)", Pattern.CASE_INSENSITIVE);Matcher matcher1 = pattern1.matcher(query);if (matcher1.find()) {String offset = matcher1.group(1);String limit = matcher1.group(2);return matcher1.replaceFirst("OFFSET " + offset + " FETCH FIRST " + limit + " ROWS ONLY");}// 匹配 LIMIT size OFFSET offsetPattern pattern2 = Pattern.compile("LIMIT\\s+(\\d+)\\s+OFFSET\\s+(\\d+)", Pattern.CASE_INSENSITIVE);Matcher matcher2 = pattern2.matcher(query);if (matcher2.find()) {String limit = matcher2.group(1);String offset = matcher2.group(2);return matcher2.replaceFirst("OFFSET " + offset + " FETCH FIRST " + limit + " ROWS ONLY");}return query;}@Overridepublic void convertQueryStatement(SQLSelectStatement statement) {SQLSelect select = statement.getSelect();if (select != null) {SQLSelectQuery query = select.getQuery();if (query != null) {String convertedSql = processSelectQuery(query);if (StringUtils.isNotBlank(convertedSql)) {selectList.add(convertedSql);}}}}private String processSelectQuery(SQLSelectQuery query) {StringBuilder sb = new StringBuilder();if (query instanceof SQLSelectQueryBlock) {SQLSelectQueryBlock block = (SQLSelectQueryBlock) query;// SELECT 列sb.append("SELECT ");List<SQLSelectItem> selectItems = block.getSelectList();for (int i = 0; i < selectItems.size(); i++) {if (i > 0) {sb.append(", ");}sb.append(processSelectItem(selectItems.get(i)));}// FROM 子句SQLTableSource from = block.getFrom();if (from != null) {sb.append(" FROM ").append(processTableSource(from));}// WHERE 条件SQLExpr where = block.getWhere();if (where != null) {sb.append(" WHERE ").append(processWhereCondition(where));}collectSelectAliases(block);// GROUP BYList<SQLExpr> groupBy = block.getGroupBy() != null ? block.getGroupBy().getItems() : Collections.emptyList();if (!groupBy.isEmpty()) {sb.append(" GROUP BY ");for (int i = 0; i < groupBy.size(); i++) {if (i > 0) {sb.append(", ");}sb.append(groupBy.get(i).toString());}SQLExpr having = block.getGroupBy().getHaving();if (having != null) {SQLExpr replacedHaving = replaceAliasWithExpr(having);sb.append(" HAVING ").append(replacedHaving.toString());}}// ORDER BYSQLOrderBy orderBy = block.getOrderBy();if (orderBy != null) {List<SQLSelectOrderByItem> orderByItems = orderBy.getItems();if (orderByItems != null && !orderByItems.isEmpty()) {sb.append(" ORDER BY ");for (int i = 0; i < orderByItems.size(); i++) {SQLSelectOrderByItem item = orderByItems.get(i);if (i > 0) {sb.append(", ");}sb.append(item.getExpr().toString());if (item.getType() == SQLOrderingSpecification.DESC) {sb.append(" DESC");} else {sb.append(" ASC");}}}}// LIMIT / OFFSET 分页SQLLimit limit = block.getLimit();if (limit != null) {sb.append(" ").append(convertPagination(limit.toString()));}} else if (query instanceof SQLUnionQuery) {SQLUnionQuery unionQuery = (SQLUnionQuery) query;sb.append("(").append(processSelectQuery(unionQuery.getLeft())).append(")");sb.append(" ").append(unionQuery.getOperator()).append(" ");sb.append("(").append(processSelectQuery(unionQuery.getRight())).append(")");}return sb.toString();}private void collectSelectAliases(SQLSelectQueryBlock block) {selectAliasMap.clear();for (SQLSelectItem item : block.getSelectList()) {if (Objects.isNull(item.getAlias())) {continue;}selectAliasMap.put(item.getAlias(), item.getExpr());}}private SQLExpr replaceAliasWithExpr(SQLExpr expr) {if (expr instanceof SQLIdentifierExpr) {String aliasName = ((SQLIdentifierExpr) expr).getName();return selectAliasMap.getOrDefault(aliasName, expr);} else if (expr instanceof SQLBinaryOpExpr) {SQLBinaryOpExpr binaryExpr = (SQLBinaryOpExpr) expr;SQLExpr left = replaceAliasWithExpr(binaryExpr.getLeft());SQLExpr right = replaceAliasWithExpr(binaryExpr.getRight());return new SQLBinaryOpExpr(left, binaryExpr.getOperator(), right);}// 可继续扩展其他类型...return expr;}private String processSelectItem(SQLSelectItem item) {item.getExpr().accept(createFunctionReplaceVisitor());String expr = item.getExpr().toString();if (item.getAlias() != null) {return expr + " AS " + item.getAlias();}return expr;}private String processTableSource(SQLTableSource source) {if (source instanceof SQLExprTableSource) {SQLIdentifierExpr expr = (SQLIdentifierExpr) ((SQLExprTableSource) source).getExpr();return expr.getName().replace("`", "");} else if (source instanceof SQLJoinTableSource) {SQLJoinTableSource join = (SQLJoinTableSource) source;String left = processTableSource(join.getLeft());String right = processTableSource(join.getRight());String on = join.getCondition() != null ? " ON " + join.getCondition().toString() : "";return left + " " + join.getJoinType().nameLCase + " " + right + on;} else if (source instanceof SQLSubqueryTableSource) {SQLSelect subQuery = ((SQLSubqueryTableSource) source).getSelect();String alias = ((SQLSubqueryTableSource) source).getAlias();return "(" + processSelectQuery(subQuery.getQuery()) + ") AS " + alias;}return source.toString();}private String processWhereCondition(SQLExpr where) {where.accept(createFunctionReplaceVisitor());return ((SQLSelectQueryBlock) where.getParent()).getWhere().toString();}private SQLASTVisitor createFunctionReplaceVisitor() {return new SQLASTVisitorAdapter() {@Overridepublic boolean visit(SQLMethodInvokeExpr expr) {handleFunction(expr);return true;}@Overridepublic boolean visit(SQLAggregateExpr expr) {handleFunction(expr);return true;}private void handleFunction(SQLMethodInvokeExpr expr) {String funcName = expr.getMethodName();System.out.println("funcName: " + funcName);String gbaseFunc = FUNCTION_MAPPING.get(funcName.toUpperCase());String gbaseFunc2 = gbaseFunc == null ? FUNCTION_MAPPING.get(funcName.toUpperCase() + "()") : gbaseFunc;if (gbaseFunc2 != null) {// 特殊处理 GROUP_CONCAT -> STRING_AGG 需要加参数if ("GROUP_CONCAT".equalsIgnoreCase(funcName)) {handleGroupConcat(expr, gbaseFunc2);} else if ("FIND_IN_SET".equalsIgnoreCase(funcName)) {// FIND_IN_SET(a, b) => STRPOS(b, a) > 0List<SQLExpr> parameters = expr.getParameters();if (parameters.size() >= 2) {SQLExpr a = parameters.get(0); // 'apple'SQLExpr b = parameters.get(1); // fruits// 构建 STRPOS(b, a)SQLMethodInvokeExpr strpos = new SQLMethodInvokeExpr("INSTR");strpos.addParameter(b);strpos.addParameter(a);// 构建 STRPOS(...) > 0SQLBinaryOpExpr gtZero = new SQLBinaryOpExpr(strpos, SQLBinaryOperator.GreaterThan, new SQLIntegerExpr(0));SQLObject parent = expr.getParent();if (parent instanceof SQLBinaryOpExpr) {((SQLBinaryOpExpr) parent).replace(expr, gtZero);} else if (parent instanceof SQLSelectItem) {((SQLSelectItem) parent).setExpr(gtZero);} else if (parent instanceof SQLExprStatement) {((SQLExprStatement) parent).setExpr(gtZero);} else if (parent instanceof SQLSelectQueryBlock) {// expr.setUsing(gtZero);//expr.replace(expr, gtZero);((SQLSelectQueryBlock) parent).replace(expr, gtZero);} else if (parent instanceof SQLInListExpr) {// 处理 FIND_IN_SET 在 IN 子句中的情况SQLInListExpr inListExpr = (SQLInListExpr) parent;inListExpr.addTarget(gtZero);} else {// 其他复杂结构需要单独处理System.err.println("Unsupported parent type: " + parent.getClass());}}} else {expr.setMethodName(gbaseFunc2.replace("()", ""));}}}private void handleGroupConcat(SQLMethodInvokeExpr expr, String gbaseFunc2) {if (expr instanceof SQLAggregateExpr) {SQLAggregateExpr aggExpr = (SQLAggregateExpr) expr;List<SQLExpr> parameters = aggExpr.getArguments();if (parameters.size() >= 1) {SQLExpr col = parameters.get(0);SQLCharExpr defaultSep = new SQLCharExpr(",");SQLMethodInvokeExpr stringAgg = new SQLMethodInvokeExpr("WM_CONCAT"); // STRING_AGGstringAgg.addParameter(col);stringAgg.addParameter(defaultSep);SQLObject parent = expr.getParent();if (parent instanceof SQLBinaryOpExpr) {((SQLBinaryOpExpr) parent).replace(expr, stringAgg);} else if (parent instanceof SQLSelectItem) {((SQLSelectItem) parent).setExpr(stringAgg);}}} else if (expr instanceof SQLMethodInvokeExpr) {// 处理 GROUP_CONCAT 作为普通函数的情况(如果有的话)SQLMethodInvokeExpr methodExpr = (SQLMethodInvokeExpr) expr;List<SQLExpr> parameters = methodExpr.getParameters();if (parameters.size() == 1) {SQLExpr col = parameters.get(0);SQLCharExpr defaultSep = new SQLCharExpr(",");SQLMethodInvokeExpr stringAgg = new SQLMethodInvokeExpr("STRING_AGG");stringAgg.addParameter(col);stringAgg.addParameter(defaultSep);SQLObject parent = expr.getParent();if (parent instanceof SQLBinaryOpExpr) {((SQLBinaryOpExpr) parent).replace(expr, stringAgg);} else if (parent instanceof SQLSelectItem) {((SQLSelectItem) parent).setExpr(stringAgg);}}}}};}@Overridepublic void convertCreateTabletStatement(MySqlCreateTableStatement statement) {String tableName = statement.getTableName().replace("`", "").replace("\"", "").toUpperCase();String comment = null;if (Objects.nonNull(statement.getComment())) {comment = statement.getComment().toString().replace("'", "").replace("\"", "");}List<SQLTableElement> tableElementList = statement.getTableElementList();List<SQLAssignItem> tableOptions = statement.getTableOptions();Map<String, Object> sqlAssignMap = new HashMap<>();tableOptions.forEach(option -> {sqlAssignMap.put(option.getTarget().toString(), option.getValue());});if (StringUtils.isNotBlank(comment)) {commentList.add("COMMENT ON TABLE \"" + tableName + "\"" + " IS '" + comment + "';");}StringBuilder builder = new StringBuilder("CREATE TABLE ");builder.append("\"").append(tableName).append("\"").append("\n(");tableElementList.forEach(column -> {if (column instanceof SQLColumnDefinition) {SQLCharExpr commentSqlExpr = (SQLCharExpr) ((SQLColumnDefinition) column).getComment();String columnName = ((SQLColumnDefinition) column).getColumnName().replace("`", "").replace("\"", "").toUpperCase();String columnComment = null;if (Objects.nonNull(commentSqlExpr)) {columnComment = commentSqlExpr.getText();}String autoIncrement = null;if (sqlAssignMap.containsKey("AUTO_INCREMENT")) {autoIncrement = sqlAssignMap.get("AUTO_INCREMENT").toString();}builder.append(buildColumn((SQLColumnDefinition) column, autoIncrement));builder.append(",\n");if (StringUtils.isNotBlank(columnComment)) {commentList.add("COMMENT ON COLUMN \"" + tableName + "\".\"" + columnName + "\" IS '" + columnComment + "';");}} else if (column instanceof MySqlPrimaryKey) {MySqlPrimaryKey mySqlPrimaryKey = (MySqlPrimaryKey) column;List<SQLSelectOrderByItem> sqlSelectOrderByItems = mySqlPrimaryKey.getIndexDefinition().getColumns();SQLIdentifierExpr expr = (SQLIdentifierExpr) sqlSelectOrderByItems.get(0).getExpr();alterList.add("ALTER TABLE \"" + tableName + "\" ADD CONSTRAINT PRIMARY KEY(\"" + expr.getName().replace("`", "").toUpperCase() + "\");");} else if (column instanceof MySqlUnique) {MySqlUnique mySqlKey = (MySqlUnique) column;SQLIndexDefinition sqlIndexDefinition = mySqlKey.getIndexDefinition();List<SQLSelectOrderByItem> sqlSelectOrderByItems = sqlIndexDefinition.getColumns();List<String> keys = new ArrayList<>();sqlSelectOrderByItems.forEach(keyItem -> {if (keyItem.getExpr() instanceof SQLIdentifierExpr) {SQLIdentifierExpr expr = (SQLIdentifierExpr) keyItem.getExpr();keys.add("\"" + expr.getName().replace("`", "").toUpperCase() + "\"");} else if (keyItem.getExpr() instanceof SQLMethodInvokeExpr) {SQLMethodInvokeExpr invokeExpr = (SQLMethodInvokeExpr) keyItem.getExpr();keys.add("\"" + invokeExpr.getMethodName().replace("`", "").toUpperCase() + "\"");}});String indexName = "UNIQUE_" + tableName + "_" + StringUtils.join(keys, "_").replace("\"", "");alterList.add("ALTER TABLE \"" + tableName + "\" ADD CONSTRAINT \"" + indexName + "\" UNIQUE(" + StringUtils.join(keys, ",") + ");");} else if (column instanceof MySqlKey) {MySqlKey mySqlKey = (MySqlKey) column;SQLIndexDefinition sqlIndexDefinition = mySqlKey.getIndexDefinition();List<SQLSelectOrderByItem> sqlSelectOrderByItems = sqlIndexDefinition.getColumns();List<String> keys = new ArrayList<>();sqlSelectOrderByItems.forEach(keyItem -> {SQLIdentifierExpr expr = (SQLIdentifierExpr) keyItem.getExpr();keys.add("\"" + expr.getName().replace("`", "").toUpperCase() + "\"");});String indexName = "IDX_" + tableName + "_" + StringUtils.join(keys, "_").replace("\"", "");indexList.add("CREATE INDEX \"" + indexName + "\" ON \"" + tableName + "\"(" + StringUtils.join(keys, ",") + ");");}});builder.replace(builder.length() - 2, builder.length() - 1, "");builder.append(");\n");creatTableList.add(builder.toString());}@Overridepublic void convertInsertStatement(MySqlInsertStatement statement) {String tableName = statement.getTableName().toString().replace("`", "").toUpperCase();StringBuilder builder = new StringBuilder("SET IDENTITY_INSERT \"");builder.append(tableName).append("\" ON;");insertList.add(builder.toString());String sql = statement.toString().replace("`", "").replace("CURRENT_TIMESTAMP()", "CURRENT_TIMESTAMP").replace("current_timestamp()", "CURRENT_TIMESTAMP").replace("b'0'", "0").replace("b'1'", "1");insertList.add(sql);StringBuilder builder1 = new StringBuilder("SET IDENTITY_INSERT \"");builder1.append(tableName).append("\" OFF;");insertList.add(builder1.toString());}@Overridepublic void convertCreateIndexStatement(SQLCreateIndexStatement statement) {String tableName = statement.getTableName().replace("`", "").toUpperCase();SQLIndexDefinition sqlIndexDefinition = statement.getIndexDefinition();List<SQLSelectOrderByItem> sqlSelectOrderByItems = sqlIndexDefinition.getColumns();String type = null;if (StringUtils.isNotBlank(sqlIndexDefinition.getType())) {type = sqlIndexDefinition.getType().toUpperCase();}List<String> keys = new ArrayList<>();sqlSelectOrderByItems.forEach(keyItem -> {if (keyItem.getExpr() instanceof SQLIdentifierExpr) {SQLIdentifierExpr expr = (SQLIdentifierExpr) keyItem.getExpr();keys.add("\"" + expr.getName().replace("`", "").toUpperCase() + "\"");} else if (keyItem.getExpr() instanceof SQLMethodInvokeExpr) {SQLMethodInvokeExpr invokeExpr = (SQLMethodInvokeExpr) keyItem.getExpr();keys.add("\"" + invokeExpr.getMethodName().replace("`", "").toUpperCase() + "\"");}});if (StringUtils.isNotBlank(type) && "UNIQUE".equals(type)) {String indexName = "UNIQUE_" + tableName + "_" + StringUtils.join(keys, "_").replace("\"", "");indexList.add("ALTER TABLE \"" + tableName + "\" ADD CONSTRAINT \"" + indexName + "\" UNIQUE(" + StringUtils.join(keys, ",") + ");");} else {String indexName = "IDX_" + tableName + "_" + StringUtils.join(keys, "_").replace("\"", "");indexList.add("CREATE INDEX \"" + indexName + "\" ON \"" + tableName + "\"(" + StringUtils.join(keys, ",") + ");");}}@Overridepublic void convertUpdateStatement(MySqlUpdateStatement statement) {String sql = statement.toString().replace("`", "").replace("CURRENT_TIMESTAMP()", "CURRENT_TIMESTAMP").replace("current_timestamp()", "CURRENT_TIMESTAMP").replace("b'0'", "0").replace("b'1'", "1");updateList.add(sql);}@Overridepublic void convertDeleteStatement(MySqlDeleteStatement statement) {String sql = statement.toString().replace("`", "").replace("b'0'", "0").replace("b'1'", "1");updateList.add(sql);}@Overridepublic void convertAlterTableStatement(SQLAlterTableStatement statement) {String tableName = statement.getTableName().toString().replace("`", "").toUpperCase();statement.getItems().forEach(sqlAlterTableItem -> {if (sqlAlterTableItem instanceof SQLAlterTableAddColumn) {SQLAlterTableAddColumn addColumn = (SQLAlterTableAddColumn) sqlAlterTableItem;addColumn.getColumns().forEach(addColumnItem -> {StringBuilder builder = new StringBuilder("ALTER TABLE \"");builder.append(tableName).append("\"");builder.append(" ADD ").append(buildColumn(addColumnItem, null)).append(";\n");SQLCharExpr commentSqlExpr = (SQLCharExpr) addColumnItem.getComment();String columnName = addColumnItem.getColumnName().replace("`", "").replace("\"", "").toUpperCase();String columnComment = null;if (Objects.nonNull(commentSqlExpr)) {columnComment = commentSqlExpr.getText();}if (StringUtils.isNotBlank(columnComment)) {commentList.add("COMMENT ON COLUMN \"" + tableName + "\".\"" + columnName + "\" IS '" + columnComment + "';");}alterList.add(builder.toString());});} else if (sqlAlterTableItem instanceof MySqlAlterTableModifyColumn) {MySqlAlterTableModifyColumn alterColumn = (MySqlAlterTableModifyColumn) sqlAlterTableItem;SQLColumnDefinition definition = alterColumn.getNewColumnDefinition();StringBuilder builder = new StringBuilder("ALTER TABLE \"");builder.append(tableName).append("\"");builder.append(" MODIFY ").append(buildColumn(definition, null)).append(";\n");SQLCharExpr commentSqlExpr = (SQLCharExpr) definition.getComment();String columnName = definition.getColumnName().replace("`", "").replace("\"", "").toUpperCase();String columnComment = null;if (Objects.nonNull(commentSqlExpr)) {columnComment = commentSqlExpr.getText();}if (StringUtils.isNotBlank(columnComment)) {commentList.add("COMMENT ON COLUMN \"" + tableName + "\".\"" + columnName + "\" IS '" + columnComment + "';");}alterList.add(builder.toString());} else if (sqlAlterTableItem instanceof SQLAlterTableAddIndex) {SQLAlterTableAddIndex addIndex = (SQLAlterTableAddIndex) sqlAlterTableItem;SQLIndexDefinition sqlIndexDefinition = addIndex.getIndexDefinition();List<SQLSelectOrderByItem> sqlSelectOrderByItems = sqlIndexDefinition.getColumns();List<String> keys = new ArrayList<>();sqlSelectOrderByItems.forEach(keyItem -> {if (keyItem.getExpr() instanceof SQLIdentifierExpr) {SQLIdentifierExpr expr = (SQLIdentifierExpr) keyItem.getExpr();keys.add("\"" + expr.getName().replace("`", "").toUpperCase() + "\"");} else if (keyItem.getExpr() instanceof SQLMethodInvokeExpr) {SQLMethodInvokeExpr invokeExpr = (SQLMethodInvokeExpr) keyItem.getExpr();keys.add("\"" + invokeExpr.getMethodName().replace("`", "").toUpperCase() + "\"");}});String indexName = "IDX_" + tableName + "_" + StringUtils.join(keys, "_").replace("\"", "");alterList.add("CREATE INDEX \"" + indexName + "\" ON \"" + tableName + "\"(" + StringUtils.join(keys, ",") + ");");} else if (sqlAlterTableItem instanceof SQLAlterTableAddConstraint) {SQLAlterTableAddConstraint addIndex = (SQLAlterTableAddConstraint) sqlAlterTableItem;SQLConstraint sqlConstraint = addIndex.getConstraint();if (sqlConstraint instanceof MySqlPrimaryKey) {MySqlPrimaryKey mySqlPrimaryKey = (MySqlPrimaryKey) sqlConstraint;List<SQLSelectOrderByItem> sqlSelectOrderByItems = mySqlPrimaryKey.getIndexDefinition().getColumns();SQLIdentifierExpr expr = (SQLIdentifierExpr) sqlSelectOrderByItems.get(0).getExpr();alterList.add("ALTER TABLE \"" + tableName + "\" ADD CONSTRAINT PRIMARY KEY(\"" + expr.getName().replace("`", "").toUpperCase() + "\");");} else if (sqlConstraint instanceof MySqlUnique) {MySqlUnique mySqlKey = (MySqlUnique) sqlConstraint;SQLIndexDefinition sqlIndexDefinition = mySqlKey.getIndexDefinition();List<SQLSelectOrderByItem> sqlSelectOrderByItems = sqlIndexDefinition.getColumns();List<String> keys = new ArrayList<>();sqlSelectOrderByItems.forEach(keyItem -> {if (keyItem.getExpr() instanceof SQLIdentifierExpr) {SQLIdentifierExpr expr = (SQLIdentifierExpr) keyItem.getExpr();keys.add("\"" + expr.getName().replace("`", "").toUpperCase() + "\"");} else if (keyItem.getExpr() instanceof SQLMethodInvokeExpr) {SQLMethodInvokeExpr invokeExpr = (SQLMethodInvokeExpr) keyItem.getExpr();keys.add("\"" + invokeExpr.getMethodName().replace("`", "").toUpperCase() + "\"");}});String indexName = "UNIQUE_" + tableName + "_" + StringUtils.join(keys, "_").replace("\"", "");alterList.add("ALTER TABLE \"" + tableName + "\" ADD CONSTRAINT \"" + indexName + "\" UNIQUE(\"" + StringUtils.join(keys, ",") + "\");");} else if (sqlConstraint instanceof MySqlKey) {MySqlKey mySqlKey = (MySqlKey) sqlConstraint;SQLIndexDefinition sqlIndexDefinition = mySqlKey.getIndexDefinition();List<SQLSelectOrderByItem> sqlSelectOrderByItems = sqlIndexDefinition.getColumns();List<String> keys = new ArrayList<>();sqlSelectOrderByItems.forEach(keyItem -> {if (keyItem.getExpr() instanceof SQLIdentifierExpr) {SQLIdentifierExpr expr = (SQLIdentifierExpr) keyItem.getExpr();keys.add("\"" + expr.getName().replace("`", "").toUpperCase() + "\"");} else if (keyItem.getExpr() instanceof SQLMethodInvokeExpr) {SQLMethodInvokeExpr invokeExpr = (SQLMethodInvokeExpr) keyItem.getExpr();keys.add("\"" + invokeExpr.getMethodName().replace("`", "").toUpperCase() + "\"");}});String indexName = "IDX_" + tableName + "_" + StringUtils.join(keys, "_").replace("\"", "");alterList.add("CREATE INDEX \"" + indexName + "\" ON \"" + tableName + "\"(" + StringUtils.join(keys, ",") + ");");}} else if (sqlAlterTableItem instanceof MySqlAlterTableChangeColumn) {String columnName = ((MySqlAlterTableChangeColumn) sqlAlterTableItem).getColumnName().getSimpleName().replace("`", "").replace("\"", "").toUpperCase();SQLColumnDefinition newColumn = ((MySqlAlterTableChangeColumn) sqlAlterTableItem).getNewColumnDefinition();String newColumnName = newColumn.getColumnName().replace("`", "").replace("\"", "").toUpperCase();alterList.add("ALTER TABLE \"" + tableName + "\" ALTER \"" + columnName + "\" RENAME TO \"" + newColumnName + "\";");String builder = "ALTER TABLE \"" + tableName + "\"" +" MODIFY " + buildColumn(newColumn, null) + ";\n";SQLCharExpr commentSqlExpr = (SQLCharExpr) newColumn.getComment();String columnComment = null;if (Objects.nonNull(commentSqlExpr)) {columnComment = commentSqlExpr.getText();}if (StringUtils.isNotBlank(columnComment)) {commentList.add("COMMENT ON COLUMN \"" + tableName + "\".\"" + newColumnName + "\" IS '" + columnComment + "';");}alterList.add(builder);}});}@Overridepublic void convertRenameTableStatement(MySqlRenameTableStatement statement) {MySqlRenameTableStatement.Item item = (MySqlRenameTableStatement.Item) statement.getItems().get(0);String name = item.getName().getSimpleName().replace("`", "").toUpperCase();String toName = item.getTo().getSimpleName().replace("`", "").toUpperCase();alterList.add("ALTER TABLE \"" + name + "\" RENAME TO \"" + toName + "\";");}@Overridepublic void convertDropTableStatement(SQLDropTableStatement statement) {String table = ((SQLIdentifierExpr) statement.getTableSources().get(0).getExpr()).getName().replace("`", "").toUpperCase();alterList.add("DROP TABLE IF EXISTS \"" + table + "\";");}@Overridepublic void convertTruncateStatement(SQLTruncateStatement statement) {String table = ((SQLIdentifierExpr) statement.getTableSources().get(0).getExpr()).getName().replace("`", "").toUpperCase();alterList.add("TRUNCATE TABLE \"" + table + "\";");}private String buildColumn(SQLColumnDefinition definition, String increment) {StringBuilder builder = new StringBuilder("");String columnName = definition.getColumnName().replace("`", "").replace("\"", "").toUpperCase();Boolean autoIncrement = definition.isAutoIncrement();SQLDataTypeImpl dataType = (SQLDataTypeImpl) definition.getDataType();String dataTypeName = dataType.getName().toUpperCase();List<SQLExpr> sqlExprs = dataType.getArguments();List<SQLColumnConstraint> constraints = definition.getConstraints();if ("VARCHAR".equals(dataTypeName)) {SQLIntegerExpr sqlIntegerExpr = (SQLIntegerExpr) sqlExprs.get(0);builder.append("\"").append(columnName).append("\" ").append(dataTypeName).append("(").append(sqlIntegerExpr.getNumber()).append(")");} else if ("TIMESTAMP".equals(dataTypeName) || "DATETIME".equals(dataTypeName)) {builder.append("\"").append(columnName).append("\" ").append("TIMESTAMP(0)");} else if ("BIT".equals(dataTypeName)) {builder.append("\"").append(columnName).append("\" ").append("TINYINT");} else if ("LONGTEXT".equals(dataTypeName)) {builder.append("\"").append(columnName).append("\" ").append("TEXT");} else if ("MEDIUMTEXT".equals(dataTypeName)) {builder.append("\"").append(columnName).append("\" TEXT");} else if ("DECIMAL".equals(dataTypeName)) {SQLIntegerExpr precision = (SQLIntegerExpr) sqlExprs.get(0);SQLIntegerExpr scale = (SQLIntegerExpr) sqlExprs.get(1);builder.append("\"").append(columnName).append("\" ").append(" DECIMAL(").append(precision.getNumber()).append(", ").append(scale.getNumber()).append(")");} else {builder.append("\"").append(columnName).append("\" ").append(dataTypeName);}if (autoIncrement) {if (StringUtils.isNotBlank(increment)) {builder.append(" IDENTITY(").append(increment).append(",1)");} else {builder.append(" IDENTITY(1,1)");}}SQLExpr sqlExpr = definition.getDefaultExpr();if (Objects.nonNull(sqlExpr)) {builder.append(" DEFAULT ");if (sqlExpr instanceof SQLIntegerExpr) {SQLIntegerExpr sqlIntegerExpr = (SQLIntegerExpr) sqlExpr;builder.append(sqlIntegerExpr.getValue());} else if (sqlExpr instanceof SQLBinaryExpr) {SQLBinaryExpr sqlBinaryExpr = (SQLBinaryExpr) sqlExpr;builder.append(sqlBinaryExpr.getValue());} else if (sqlExpr instanceof SQLCurrentTimeExpr) {builder.append("CURRENT_TIMESTAMP()");} else if (sqlExpr instanceof SQLCharExpr) {SQLCharExpr sqlCharExpr = (SQLCharExpr) sqlExpr;builder.append("'").append(sqlCharExpr.getText()).append("'");}}if (Objects.nonNull(constraints) && !constraints.isEmpty() && constraints.get(0) instanceof SQLNotNullConstraint) {builder.append(" NOT NULL");} else {if (Objects.isNull(sqlExpr) || sqlExpr instanceof SQLNullExpr) {builder.append(" NULL");}}return builder.toString();}
}