背景

现在信创是搞得如火如荼,在这个浪潮下,数据库也是从之前熟悉的Mysql换到了某国产数据库。

该数据库我倒是想吐槽吐槽,它是基于Postgre 9.x的基础上改的,至于改了啥,我也没去详细了解,当初的数据库POC测试和后续的选型没太参与,但对于我一个开发人员的角度来说,它给我带来的不便主要是客户端GUI工具这块。

我们读写数据库,程序这块还好,CURD代码用到的语法,基本是sql标准兼容的那些,没用多少mysql的特殊语法,所以这块没啥感觉。

客户端GUI这块,pg的客户端软件目前知道几个:

  • navicat,公司没采购正版,用不了,替代软件是开源的dbeaver
  • pgAdmin,pg官方客户端,结果不知道这个国产化过程中改了啥,用pgAdmin连上就各种报错,放弃
  • dbeaver,这个倒是可以用,就是我感觉操作太麻烦了,太繁琐

基于以上原因,一直用dbeaver来着,之前两次把mysql项目的表结构换成pg,一次是写了个乱七八糟的代码来做建表语句转换,一次是用dbeaver建的,太繁琐了。

这次又来了个项目,我就换回了我熟悉的sqlyog(一款mysql客户端),几下就把表建好了(mysql版本),然后写了个工具代码,来把mysql的DDL转换成pg的。

下面简单介绍下这个转换代码。

技术选型

以前写这种代码,都是各种字符串操作(正则、匹配、替换等等),反正代码最终是非常难以维护。这次就先去网上查了下,发现有人有类似需求,还发了文章:https://zhuanlan.zhihu.com/p/314069540

我发现其中利用了一个java库,JSqlParser(https://github.com/JSQLParser/JSqlParser),我在网上也找了下其他的库,java这块没有更好的了,遥遥领先。

其官方说明:

JSqlParser parses an SQL statement and translate it into a hierarchy of Java classes.

它支持解析sql语句这种非结构化文本为结构化数据,比如,针对如下的一个建库sql:

CREATE TABLE `xxl_job_log_report` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
`running_count` int(11) NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
`suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
`fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

可以解析为如下的类及属性:

如上就包含了索引、列定义、建表选项等等。

我们接下来就只需要根据这些字段,获取数据并转换为对应的Postgre的语法即可。

转换效果

源码:https://github.com/cctvckl/convertMysqlDdlToPostgre.git

对于以上的类,给大家看看转换效果:

CREATE TABLE xxl_job_log_report (
id serial PRIMARY KEY,
trigger_day timestamp NULL,
running_count int NOT NULL DEFAULT '0',
suc_count int NOT NULL DEFAULT '0',
fail_count int NOT NULL DEFAULT '0',
update_time timestamp NULL
);
COMMENT ON COLUMN xxl_job_log_report.trigger_day IS '调度-时间';
COMMENT ON COLUMN xxl_job_log_report.running_count IS '运行中-日志数量';
COMMENT ON COLUMN xxl_job_log_report.suc_count IS '执行成功-日志数量';
COMMENT ON COLUMN xxl_job_log_report.fail_count IS '执行失败-日志数量';

这个sql,基本都满足我们的要求了。

当然,我这个工具类,还没特别完善,对于索引这块,只支持了主键索引,其他索引类型,后面空了我补一下。

支持的DDL类型,目前仅限于create table和drop table,目前能满足我个人需求了,反正mysqldump那些导出来的sql结构基本就这样。

暂不支持DML,如insert那些。

代码要点

整体逻辑

Statements statements = CCJSqlParserUtil.parseStatements(sqlContent);
for (Statement statement : statements.getStatements()) {
if (statement instanceof CreateTable) {
String sql = ProcessSingleCreateTable.process((CreateTable) statement);
totalSql.append(sql).append("\n");
} else if (statement instanceof Drop) {
String sql = ProcessSingleDropTable.process((Drop) statement);
totalSql.append(sql).append("\n");
} else {
throw new UnsupportedOperationException();
}
}

如上,CCJSqlParserUtil 是 JSqlParser 的工具类,将我们的sql转换为一个一个的statement(即sql语句),我这边利用instanceof检查属于哪种DDL,再调用对应的代码进行处理,设计模式也懒得弄,if else写起来多快。

数据准备:表注释

List<String> tableOptionsStrings = createTable.getTableOptionsStrings();
String tableCommentSql = null;
int commentIndex = tableOptionsStrings.indexOf("COMMENT");
if (commentIndex != -1) {
tableCommentSql = String.format("COMMENT ON TABLE %s IS %s;", tableFullyQualifiedName,tableOptionsStrings.get(commentIndex + 2));
}

解析出的表的相关属性,全都被放在一个list中,我们根据COMMENT关键字定位索引,然后找后两个,即是表注释具体值。

数据准备:列注释

由于我是直接在作者基础上改的,https://zhuanlan.zhihu.com/p/314069540,所以也是像他那样,复用了其代码,提取每一列的注释,逻辑也是根据COMMENT关键字找到index,然后index+1就是注释值。

提取出来后,格式化为pg语法:

String.format("COMMENT ON COLUMN %s.%s IS %s;", table, column, commentValue);

数据准备:提取主键

Index primaryKey = createTable.getIndexes().stream()
.filter((Index index) -> Objects.equals("PRIMARY KEY", index.getType()))
.findFirst().orElse(null);

组装sql:建表第一行

String createTableFirstLine = String.format("CREATE TABLE %s (", tableFullyQualifiedName);

组装sql:主键列

这里涉及数据类型转换,如mysql中的bigint,在pg中,使用bigserial即可:

String dataType = primaryKeyColumnDefinition.getColDataType().getDataType();
if (Objects.equals("bigint", dataType)) {
primaryKeyType = "bigserial";
} else if (Objects.equals("int", dataType)) {
primaryKeyType = "serial";
} else if (Objects.equals("varchar", dataType)){
primaryKeyType = primaryKeyColumnDefinition.getColDataType().toString();
} String sql = String.format("%s %s PRIMARY KEY", primaryKeyColumnName, primaryKeyType);

组装sql:其他列

这部分有几块:

  • 类型转换,mysql的类型,转换为pg的,我这边定义了一个map,大致如下:

​ 以上仅是部分,具体查看代码

  • 默认值处理

    这块也比较麻烦,比如mysql中的函数这种,如CURRENT_TIMESTAMP这种默认值,转换为pg中的对应函数,我大概定义了几个,满足当前需要:

    static {
    MYSQL_DEFAULT_TO_POSTGRE_DEFAULT.put("NULL", "NULL");
    MYSQL_DEFAULT_TO_POSTGRE_DEFAULT.put("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP");
    MYSQL_DEFAULT_TO_POSTGRE_DEFAULT.put("CURRENT_DATE", "CURRENT_DATE");
    MYSQL_DEFAULT_TO_POSTGRE_DEFAULT.put("CURRENT_TIME", "CURRENT_TIME");
    }
  • 删除pg不支持的mysql语法

    // postgre不支持unsigned
    sourceSpec = sourceSpec.replaceAll("unsigned", "");
    // postgre不支持ON UPDATE CURRENT_TIMESTAMP
    sourceSpec = sourceSpec.replaceAll("ON UPDATE CURRENT_TIMESTAMP", "");

打印完整的pg语法sql

这块就不说了,上面效果展示部分有。

生成出来的sql,会在项目根路径下的target.sql文件中

总结

生成的target.sql文件,在idea中打开,如果有语法错误会飘红,如果大家有java开发能力,直接debug改就行,不行就提issue,我看到了空了就改;

我之前拿着有语法错误的sql就去dbeaver执行了,报错也不详细,看得一脸懵,idea还是厉害。

参考资料

mysql官方的迁移指南,里面包含了pg的各种类型对应到mysql的什么类型
https://dev.mysql.com/doc/workbench/en/wb-migration-database-postgresql-typemapping.html
mysql中的各种类型查阅
https://dev.mysql.com/doc/refman/8.0/en/data-types.html
pg中的各种类型查阅,我看得低版本的,谁让我们的信创数据库是基于pg 9版本的呢
https://www.postgresql.org/docs/11/datatype-numeric.html#DATATYPE-INT

这边直接贴一下吧,方便大家看:

Pg Source Type Taret MySQL Type Comment
INT INT
SMALLINT SMALLINT
BIGINT BIGINT
SERIAL INT Sets AUTO_INCREMENT in its table definition.
SMALLSERIAL SMALLINT Sets AUTO_INCREMENT in its table definition.
BIGSERIAL BIGINT Sets AUTO_INCREMENT in its table definition.
BIT BIT
BOOLEAN TINYINT(1)
REAL FLOAT
DOUBLE PRECISION DOUBLE
NUMERIC DECIMAL
DECIMAL DECIMAL
MONEY DECIMAL(19,2)
CHAR CHAR/LONGTEXT
NATIONAL CHARACTER CHAR/LONGTEXT
VARCHAR VARCHAR/MEDIUMTEXT/LONGTEXT
NATIONAL CHARACTER VARYING VARCHAR/MEDIUMTEXT/LONGTEXT
DATE DATE
TIME TIME
TIMESTAMP DATETIME
INTERVAL TIME
BYTEA LONGBLOB
TEXT LONGTEXT
CIDR VARCHAR(43)
INET VARCHAR(43)
MACADDR VARCHAR(17)
UUID VARCHAR(36)
XML LONGTEXT
JSON LONGTEXT
TSVECTOR LONGTEXT
TSQUERY LONGTEXT
ARRAY LONGTEXT
POINT POINT
LINE LINESTRING
LSEG LINESTRING
BOX POLYGON
PATH LINESTRING
POLYGON POLYGON
CIRCLE POLYGON
TXID_SNAPSHOT VARCHAR

纯分享:将MySql的建表DDL转为PostgreSql的DDL的更多相关文章

  1. mysql workbench 建表时 PK,NN,UQ,BIN,UN,ZF,AI解释

    mysql workbench 建表时 - PK: primary key (column is part of a pk) 主键 - NN: not null (column is nullable ...

  2. 分享一个MySQL分库分表备份脚本(原)

    分享一个MySQL分库备份脚本(原) 开发思路: 1.路径:规定备份到什么位置,把路径(先判断是否存在,不存在创建一个目录)先定义好,我的路径:/mysql/backup,每个备份用压缩提升效率,带上 ...

  3. mysql怎样建表及mysql优化

    1.符合数据库三范式 2.字段选择合适的数据类型 3.注意表之间的联系,一对多,多对多,一对一 4.拆分表,把不常用的字段单独成表. 5.建立索引,哪些字段建立索引?建立索引的原则?最左前缀原则,wh ...

  4. MySQL常见建表选项以约束

    一.CREATE TABLE 选项 1.在定义列的时候,指定列选项 1)DEFAULT <literal>:定义列的默认值 当插入一个新行到表中并且没有给该列明确赋值时,如果定义了列的默认 ...

  5. mysql数据库建表的基本规范

    1.创建表的时候必须指定主键,并且主键建立后最好不要再有数据修改的需求 mysql从5.5版本开始默认使用innodb引擎,innodb表是聚簇索引表,也就是说数据通过主键聚集( 主键下存储该行的数据 ...

  6. MySQL常见建表选项及约束

    阅读目录---MySQL常见的建表选项及约束: 1.create table选项 1.指定列选项:default.comment 2.指定表选项:engine.auto_increment.comme ...

  7. Mysql的建表规范与注意事项

    一. 表设计规范 库名.表名.字段名必须使用小写字母,“_”分割. 库名.表名.字段名必须不超过12个字符. 库名.表名.字段名见名知意,建议使用名词而不是动词. 建议使用InnoDB存储引擎. 存储 ...

  8. Mysql 批量建表存储过程

    最近项目中用到了使用存储过程批量建表的功能,记录下来: USE db_test_3; drop procedure if EXISTS `createTablesWithIndex`; create ...

  9. 50个SQL语句(MySQL版) 建表 插入数据

    本学期正在学习数据库,前段时间老师让我们做一下50个经典SQL语句,当时做的比较快,有一些也是百度的,自我感觉理解的不是很透彻. 所以从本篇随笔开始,我将进行50个经典SQL语句的复盘,加深理解. 答 ...

  10. 【mysql】mysql 常用建表语句

    [1]建立员工档案表要求字段:员工员工编号,员工姓名,性别,工资,email,入职时间,部门.[2]合理选择数据类型及字段修饰符,要求有NOT NULL,auto_increment, primary ...

随机推荐

  1. PHP反序列化常用魔术方法

    PHP反序列化 php序列化(serialize):是将变量转换为可保存或传输的字符串的过程 php反序列化(unserialize):就是在适当的时候把这个字符串再转化成原来的变量使用 PHP反序列 ...

  2. 【最佳实践】如何设计 RESTful API ?

    良好的 API 设计是一个经常被提及的话题,特别是对于那些试图完善其 API 策略的团队来说.一个设计良好的 API 的好处包括:改进开发者体验.更快速地编写文档以及更高效地推广你的 API.但是,到 ...

  3. Python潮流周刊#4:Python 2023 语言峰会

    你好,我是猫哥.这里记录每周值得分享的 Python 及通用技术内容,本期是特别加更版,聚焦于 Python 官方 2023 年语言峰会的系列博客. 博客原文:https://pythoncat.to ...

  4. opencv图像显示问题

    opencv 的图像类型都是numpy array.dtype = uint8. 如果是默认的python的int类型的numpy array,即使每个整数都在范围0-255, 图像也不会显示,必须转 ...

  5. Kubernetes使用Harbor作为私有镜像仓库

    概述 Harbor使用了基于角色的访问控制策略,当从Harbor中拉去镜像的时候,首先要进行身份认证,认证通过后才可以拉取镜像.在命令行模式下,需要先执行docker login,登陆成功后,才可以d ...

  6. Python asyncio 库源码分析

    Python asyncio 库源码分析 前言 本着 「路漫漫其修远兮, 吾将上下而求索」 的精神.终于要开始深入研究 Python 中 asyncio 的源码实现啦. 本文章可能篇幅较长,因为是逐行 ...

  7. AI与健康管理:趋势与未来

    目录 引言 随着人工智能技术的不断发展,健康管理也逐渐成为了一个新的研究领域.AI技术可以为健康管理提供智能化.个性化.高效的支持,使得健康管理更加人性化和科学.本文将介绍AI与健康管理的技术原理.实 ...

  8. 重新搞懂Git,掌握日常命令和基本操作

    1.git Git 是一个免费的开源分布式版本控制系统,旨在快速高效地处理从小型到超大型项目的所有内容. Git 易于学习,占用空间很小,性能快如闪电.它超越了Subversion,CVS,Perfo ...

  9. C++面试八股文:如何避免死锁?

    某日二师兄参加XXX科技公司的C++工程师开发岗位第31面: 面试官:什么是锁?有什么作用? 二师兄:在C++中,锁(Lock)是一种同步工具,用于保护共享资源,防止多个线程同时访问,从而避免数据竞争 ...

  10. .NetCore3.1+微服务架构技术栈

    目标 目标系统架构演变,单体-分布式-微服务-中台 微服务架构核心解决,横向对比1.0.2.0.3.0 践行微服务架构,全组件解读! 也谈中台 单体架构Monolithic 单体应用时代:应用程序就是 ...