迁移脚本补一条查漏刷的语句
原来那条只查重复组,而重复组本来就该留着原样,漏刷的行它一条都报不出来。补的那条报的是所有还留在原写法的行。两条都写成用例,测试从脚本里把语句取出来在真库上跑一遍,坏掉会直接报错。
This commit is contained in:
1 parent
425210d89e
commit
92ecb4cf07
2 files changed
+116
-1
No files matched your search
@@ -100,6 +100,33 @@ WHERE cnt.`n` = 1
|
||||
-- WHERE `mac_address` REGEXP '^([0-9A-Fa-f]{2}[-:_]?){5}[0-9A-Fa-f]{2}$'
|
||||
-- GROUP BY `nmac` HAVING `c` > 1;
|
||||
--
|
||||
-- ── 漏刷检查:刷完之后还有哪些行是原写法 ──────────────────────────────────
|
||||
-- 上一条只找重复组,而重复组本来就该留着原样,所以它查不出漏刷:
|
||||
-- 判重之外的行要是压根没被刷到,它一条也报不出来。
|
||||
-- 下面这条报的是「还是原写法」的所有行。跑出结果不一定是漏刷——
|
||||
-- 重复组在上面那条名单里的那些是故意留的,剩下的是真漏刷。
|
||||
--
|
||||
-- 第 ① 段在有重复组的表上整段刷不动过一次:MySQL 26.7.0 实测,
|
||||
-- 该刷的行一行没动、ROW_COUNT() 报 0、退出码 0、不打日志、不报错。
|
||||
-- 没有报错可等,只能靠这条查。
|
||||
--
|
||||
-- SELECT `id`, `mac_address` AS `still_original`,
|
||||
-- LOWER(CONCAT(SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 1, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 3, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 5, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 7, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 9, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 11, 2))) AS `should_be`
|
||||
-- FROM `ai_device`
|
||||
-- WHERE `mac_address` REGEXP '^([0-9A-Fa-f]{2}[-:_]?){5}[0-9A-Fa-f]{2}$'
|
||||
-- AND BINARY `mac_address` <> BINARY LOWER(CONCAT(SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 1, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 3, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 5, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 7, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 9, 2), ':',
|
||||
-- SUBSTRING(REPLACE(REPLACE(REPLACE(`mac_address`,':',''),'-',''),'_',''), 11, 2)))
|
||||
-- ORDER BY `id`;
|
||||
--
|
||||
-- 三段合在一个 changeset 里,也就是一个事务(Liquibase 默认 runInTransaction 为真),
|
||||
-- 任何一段报错都会整体回滚、服务起不来。所以它们不该去撞,上面的 cnt 就是为这个。
|
||||
-- 值已经是对的三段都是空操作,重跑安全。
|
||||
+89
-1
@@ -1,6 +1,7 @@
|
||||
package xiaozhi.modules.device.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -211,6 +212,45 @@ class MacAddressMigrationSqlTest {
|
||||
"撞的是主键唯一性,没撞上说明这组数据造得不对:" + failure.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("排查重复的那条 SQL 报出被跳过的组")
|
||||
void duplicateDiagnosticListsTheSkippedGroup() throws SQLException {
|
||||
insertDevice("d1", "AA-BB-CC-DD-EE-01");
|
||||
insertDevice("d2", "aabbccddee01");
|
||||
insertDevice("x1", "11-22-33-44-55-66");
|
||||
runMigration();
|
||||
|
||||
assertEquals(List.of("aa:bb:cc:dd:ee:01"),
|
||||
queryColumn(diagnostic("③ 刷完之后跑这个"), 0),
|
||||
"只该报出被判重跳过的那个组,刷完了的行不该出现在名单里");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("漏刷检查报出还留在原写法的行")
|
||||
void leftoverCheckFindsRowsStillInOriginalSpelling() throws SQLException {
|
||||
insertDevice("d1", "AA-BB-CC-DD-EE-01");
|
||||
insertDevice("d2", "aabbccddee01");
|
||||
insertDevice("x1", "11-22-33-44-55-66");
|
||||
insertDevice("u1", "AA:BB:CC:DD:EE:99");
|
||||
runMigration();
|
||||
|
||||
assertEquals(List.of("AA-BB-CC-DD-EE-01", "aabbccddee01"),
|
||||
queryColumn(diagnostic("漏刷检查"), 1),
|
||||
"刷完只剩判重跳过的那两行是原写法,x1、u1 都该刷掉");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("漏刷检查能报出该刷没刷出来的行")
|
||||
void leftoverCheckFindsRowsTheMigrationNeverTouched() throws SQLException {
|
||||
insertDevice("d1", "AA-BB-CC-DD-EE-01");
|
||||
insertDevice("d2", "aabbccddee01");
|
||||
insertDevice("x1", "11-22-33-44-55-66");
|
||||
|
||||
assertEquals(List.of("d1", "d2", "x1"),
|
||||
queryColumn(diagnostic("漏刷检查"), 0),
|
||||
"整段迁移一行没动时漏刷检查要全报出来,不然迁移只做了一半没人知道");
|
||||
}
|
||||
|
||||
/** 按 Liquibase 的 sqlFile 那样读脚本:去注释,按分号切,在一个事务里逐句执行 */
|
||||
private void runMigration() throws SQLException {
|
||||
run(readChangelog("202610231200.sql"));
|
||||
@@ -298,4 +338,52 @@ class MacAddressMigrationSqlTest {
|
||||
throw new IllegalStateException("读不到 " + fileName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把脚本里注释掉的那段排查 SQL 整条取出来
|
||||
*
|
||||
* <p>
|
||||
* 脚本里那一段是给人手工跑的,跑不走就等于没写。取出来在真库上执行一遍,
|
||||
* 段里的 SQL 坏了、引用了不存在的列,这里会直接报错
|
||||
*/
|
||||
private static String diagnostic(String marker) {
|
||||
List<String> collected = new ArrayList<>();
|
||||
boolean inBlock = false;
|
||||
for (String line : readChangelog("202610231200.sql").split("\n")) {
|
||||
if (!line.stripLeading().startsWith("--")) {
|
||||
if (!collected.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!inBlock) {
|
||||
inBlock = line.contains(marker);
|
||||
if (!inBlock) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
String body = line.stripLeading().substring(2).strip();
|
||||
if (collected.isEmpty() && !body.startsWith("SELECT")) {
|
||||
// 说明文字,去掉行首的 -- 之后就不是注释了,不能拼进语句里
|
||||
continue;
|
||||
}
|
||||
collected.add(body);
|
||||
if (body.endsWith(";")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertFalse(collected.isEmpty(), "脚本里找不到 " + marker + " 那段排查 SQL");
|
||||
return String.join("\n", collected);
|
||||
}
|
||||
|
||||
private List<String> queryColumn(String sql, int column) throws SQLException {
|
||||
List<String> values = new ArrayList<>();
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet rows = statement.executeQuery(sql)) {
|
||||
while (rows.next()) {
|
||||
values.add(rows.getString(column + 1));
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user