258 lines
13 KiB
Java
258 lines
13 KiB
Java
package com.baiyue.lastleg.schema;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
class SchemaChineseCommentContractTest {
|
|
|
|
private static final Set<String> MODULE_BASELINE_ONLY_TABLES = Set.of(
|
|
"ll_us_truck_order_operation", "ll_local_looseboard_operation",
|
|
"ll_receiving_warehouse_operation");
|
|
private static final Set<String> MODULE_BASELINE_ONLY_COLUMNS = Set.of(
|
|
"ll_order.overseas_warehouse_code", "ll_order.receiving_warehouse_version",
|
|
"ll_receiving_container_session.overseas_warehouse_code");
|
|
|
|
private static final Pattern CHINESE = Pattern.compile("[\\u4e00-\\u9fff]");
|
|
private static final Pattern TABLE = Pattern.compile(
|
|
"(?is)CREATE\\s+TABLE\\s+IF\\s+NOT\\s+EXISTS\\s+(ll_[a-z0-9_]+)"
|
|
+ "\\s*\\((.*?)\\)\\s*ENGINE\\s*=\\s*InnoDB\\s+COMMENT\\s*=\\s*'([^']*)'\\s*;");
|
|
|
|
@Test
|
|
void allLastlegTablesAndColumnsHaveChineseComments() throws Exception {
|
|
String baseline = Files.readString(
|
|
Path.of("src", "main", "resources", "db", "lastleg.sql"), StandardCharsets.UTF_8);
|
|
String incremental = Files.readString(
|
|
Path.of("..", "scripts", "db", "incremental.sql"), StandardCharsets.UTF_8);
|
|
String overseasUnified = Files.readString(
|
|
Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), StandardCharsets.UTF_8);
|
|
|
|
List<TableDefinition> baselineTables = assertAllChineseComments(baseline, "基线 SQL", 55);
|
|
assertAllChineseComments(incremental, "增量 SQL", 49);
|
|
assertAllChineseComments(overseasUnified, "海外统一增量 SQL", 10);
|
|
assertCommentMigrationCovers(incremental, overseasUnified, baselineTables);
|
|
}
|
|
|
|
@Test
|
|
void newlyAddedColumnsCarryChineseCommentsInBaselineAndIncrementalSql() throws Exception {
|
|
String baseline = Files.readString(
|
|
Path.of("src", "main", "resources", "db", "lastleg.sql"), StandardCharsets.UTF_8);
|
|
String incremental = Files.readString(
|
|
Path.of("..", "scripts", "db", "incremental.sql"), StandardCharsets.UTF_8);
|
|
|
|
List<String> columns = List.of(
|
|
"allocation_idempotency_key", "allocation_request_hash", "outbound_idempotency_key",
|
|
"outbound_request_hash", "outbound_at", "pallet_count", "received_pallet_count",
|
|
"allocated_pallet_count"
|
|
);
|
|
for (String column : columns) {
|
|
assertColumnHasChineseComment(baseline, column, "基线 SQL");
|
|
assertColumnHasChineseComment(incremental, column, "增量 SQL");
|
|
assertIncrementalAddColumnHasChineseComment(incremental, column);
|
|
}
|
|
for (String column : List.of("review_idempotency_key", "review_request_hash", "result_version")) {
|
|
assertColumnHasChineseComment(baseline, column, "基线 SQL");
|
|
}
|
|
}
|
|
|
|
@Test
|
|
void inboundPlanMigrationsPreserveChineseCommentsForEveryAlteredColumn() throws Exception {
|
|
String baseline = Files.readString(
|
|
Path.of("src", "main", "resources", "db", "lastleg.sql"), StandardCharsets.UTF_8);
|
|
String overseasUnified = Files.readString(
|
|
Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"), StandardCharsets.UTF_8);
|
|
|
|
List<MigrationColumn> required = List.of(
|
|
new MigrationColumn("ll_inbound_plan", "status"),
|
|
new MigrationColumn("ll_inbound_plan_line", "required_package_count"),
|
|
new MigrationColumn("ll_inbound_plan_pallet", "actual_package_count"),
|
|
new MigrationColumn("ll_inbound_plan_pallet", "actual_volume_cbm"),
|
|
new MigrationColumn("ll_inbound_plan_pallet", "assignment_status"),
|
|
new MigrationColumn("ll_inbound_plan_pallet", "locked_at"),
|
|
new MigrationColumn("ll_inbound_plan_pallet", "warehouse_location_id"),
|
|
new MigrationColumn("ll_inbound_plan_pallet", "warehouse_pallet_id"),
|
|
new MigrationColumn("ll_inbound_plan_pallet", "warehouse_resource_lock_id"),
|
|
new MigrationColumn("ll_inbound_plan_pallet_allocation", "actual_volume_cbm"),
|
|
new MigrationColumn("ll_inbound_plan_pallet_allocation", "counted_package_count"),
|
|
new MigrationColumn("ll_inbound_plan_pallet_allocation", "picked_package_count"),
|
|
new MigrationColumn("ll_inbound_plan_pallet_allocation", "planned_package_count"),
|
|
new MigrationColumn("ll_inbound_plan_pallet_allocation", "status"),
|
|
new MigrationColumn("ll_inbound_plan_pallet_allocation", "version")
|
|
);
|
|
for (MigrationColumn column : required) {
|
|
assertSpecificAlterationHasChineseComment(baseline, column);
|
|
}
|
|
assertAllAlteredColumnsHaveChineseComments(overseasUnified, "海外统一增量 SQL");
|
|
}
|
|
|
|
private static List<TableDefinition> assertAllChineseComments(String sql, String source, int expectedCount) {
|
|
List<TableDefinition> tables = new ArrayList<>();
|
|
Matcher matcher = TABLE.matcher(sql);
|
|
while (matcher.find()) {
|
|
String table = matcher.group(1);
|
|
assertTrue(CHINESE.matcher(matcher.group(3)).find(), source + " 表备注必须包含中文: " + table);
|
|
List<String> columns = splitTopLevel(matcher.group(2));
|
|
assertFalse(columns.isEmpty(), source + " 表定义没有字段: " + table);
|
|
List<String> columnNames = new ArrayList<>();
|
|
for (String definition : columns) {
|
|
if (isConstraint(definition)) {
|
|
continue;
|
|
}
|
|
String column = definition.stripLeading().split("\\s+", 2)[0].replace("`", "");
|
|
columnNames.add(column);
|
|
assertTrue(Pattern.compile("(?is)\\bCOMMENT\\s+'[^']*[\\u4e00-\\u9fff][^']*'")
|
|
.matcher(definition).find(),
|
|
source + " 字段备注必须包含中文: " + table + "." + column);
|
|
}
|
|
tables.add(new TableDefinition(table, columnNames));
|
|
}
|
|
assertTrue(tables.size() >= expectedCount,
|
|
source + " 解析到的尾程表数量不足,实际=" + tables.size() + ",期望至少=" + expectedCount);
|
|
return tables;
|
|
}
|
|
|
|
private static void assertCommentMigrationCovers(
|
|
String incremental,
|
|
String overseasUnified,
|
|
List<TableDefinition> tables
|
|
) {
|
|
int marker = incremental.indexOf("2026-08-18 / 025 Chinese table and column comments");
|
|
assertTrue(marker >= 0, "增量 SQL 缺少 025 中文备注迁移");
|
|
String migration = incremental.substring(marker);
|
|
for (TableDefinition table : tables) {
|
|
if (MODULE_BASELINE_ONLY_TABLES.contains(table.name())) {
|
|
continue;
|
|
}
|
|
boolean createdInLegacy = hasCommentedCreate(migration, table.name());
|
|
boolean createdInUnified = hasCommentedCreate(overseasUnified, table.name());
|
|
assertTrue(createdInLegacy || createdInUnified
|
|
|| migration.contains("table_name='" + table.name() + "'"),
|
|
"025 备注迁移缺少表: " + table.name());
|
|
if (createdInLegacy || createdInUnified) {
|
|
continue;
|
|
}
|
|
for (String column : table.columns()) {
|
|
if (MODULE_BASELINE_ONLY_COLUMNS.contains(table.name() + "." + column)) {
|
|
continue;
|
|
}
|
|
boolean repaired = migration.contains("table_name='" + table.name()
|
|
+ "' AND column_name='" + column + "'");
|
|
boolean addedWithComment = hasCommentedAdd(migration, column)
|
|
|| hasCommentedAdd(overseasUnified, column);
|
|
assertTrue(repaired || addedWithComment,
|
|
"025 备注迁移缺少字段: " + table.name() + "." + column);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean hasCommentedCreate(String sql, String table) {
|
|
return Pattern.compile(
|
|
"(?is)CREATE\\s+TABLE\\s+IF\\s+NOT\\s+EXISTS\\s+" + Pattern.quote(table)
|
|
+ "\\s*\\(.*?\\)\\s*ENGINE\\s*=\\s*InnoDB\\s+COMMENT\\s*=\\s*'[^']*[\\u4e00-\\u9fff][^']*'\\s*;")
|
|
.matcher(sql).find();
|
|
}
|
|
|
|
private static boolean hasCommentedAdd(String sql, String column) {
|
|
return Pattern.compile(
|
|
"(?is)ADD\\s+COLUMN\\s+" + Pattern.quote(column)
|
|
+ "\\b[^;]*?COMMENT\\s+''?[^;]*[\\u4e00-\\u9fff][^;]*''?")
|
|
.matcher(sql).find();
|
|
}
|
|
|
|
private static boolean isConstraint(String definition) {
|
|
String normalized = definition.stripLeading().toUpperCase();
|
|
return normalized.startsWith("PRIMARY ") || normalized.startsWith("UNIQUE ")
|
|
|| normalized.startsWith("KEY ") || normalized.startsWith("CONSTRAINT ")
|
|
|| normalized.startsWith("FOREIGN ") || normalized.startsWith("CHECK ");
|
|
}
|
|
|
|
private static void assertColumnHasChineseComment(String sql, String column, String source) {
|
|
Pattern columnPattern = Pattern.compile(
|
|
"(?is)\\b" + Pattern.quote(column) + "\\b[^,;]*?COMMENT\\s+''?[^',;]*[\\u4e00-\\u9fff][^',;]*''?");
|
|
assertTrue(columnPattern.matcher(sql).find(), source + " 新增字段缺少中文备注: " + column);
|
|
}
|
|
|
|
private static void assertIncrementalAddColumnHasChineseComment(String sql, String column) {
|
|
Pattern addColumnPattern = Pattern.compile(
|
|
"(?is)ADD\\s+COLUMN\\s+" + Pattern.quote(column)
|
|
+ "\\b[^;]*?COMMENT\\s+''[^';]*[\\u4e00-\\u9fff][^';]*''");
|
|
assertTrue(addColumnPattern.matcher(sql).find(), "增量 SQL 的 ADD COLUMN 缺少中文备注: " + column);
|
|
}
|
|
|
|
private static void assertSpecificAlterationHasChineseComment(String sql, MigrationColumn target) {
|
|
Pattern pattern = Pattern.compile(
|
|
"(?is)ALTER\\s+TABLE\\s+" + Pattern.quote(target.table())
|
|
+ "\\s+(?:ADD|MODIFY)\\s+COLUMN\\s+" + Pattern.quote(target.column()) + "\\b");
|
|
Matcher matcher = pattern.matcher(sql);
|
|
assertTrue(matcher.find(), "缺少字段增量定义: " + target.table() + "." + target.column());
|
|
assertAlterationSegmentHasChineseComment(sql, matcher.start(), matcher.end(),
|
|
"字段增量缺少中文备注: " + target.table() + "." + target.column());
|
|
}
|
|
|
|
private static void assertAllAlteredColumnsHaveChineseComments(String sql, String source) {
|
|
Pattern pattern = Pattern.compile("(?i)\\b(?:ADD|MODIFY)\\s+COLUMN\\s+([a-z0-9_]+)\\b");
|
|
Matcher matcher = pattern.matcher(sql);
|
|
int count = 0;
|
|
while (matcher.find()) {
|
|
count++;
|
|
assertAlterationSegmentHasChineseComment(sql, matcher.start(), matcher.end(),
|
|
source + " 字段增量缺少中文备注: " + matcher.group(1));
|
|
}
|
|
assertTrue(count > 0, source + " 未解析到 ADD/MODIFY COLUMN");
|
|
}
|
|
|
|
private static void assertAlterationSegmentHasChineseComment(
|
|
String sql,
|
|
int start,
|
|
int tokenEnd,
|
|
String message
|
|
) {
|
|
Matcher next = Pattern.compile("(?i)\\b(?:ADD|MODIFY)\\s+COLUMN\\s+[a-z0-9_]+\\b")
|
|
.matcher(sql);
|
|
int end = sql.indexOf(';', tokenEnd);
|
|
if (end < 0) end = sql.length();
|
|
if (next.find(tokenEnd) && next.start() < end) end = next.start();
|
|
String definition = sql.substring(start, end);
|
|
assertTrue(Pattern.compile("(?is)\\bCOMMENT\\s+''?[^;]*[\\u4e00-\\u9fff][^;]*''?")
|
|
.matcher(definition).find(), message);
|
|
}
|
|
|
|
private static List<String> splitTopLevel(String body) {
|
|
List<String> definitions = new ArrayList<>();
|
|
int start = 0;
|
|
int parentheses = 0;
|
|
boolean quoted = false;
|
|
for (int i = 0; i < body.length(); i++) {
|
|
char current = body.charAt(i);
|
|
if (current == '\'' && (i == 0 || body.charAt(i - 1) != '\\')) {
|
|
quoted = !quoted;
|
|
} else if (!quoted && current == '(') {
|
|
parentheses++;
|
|
} else if (!quoted && current == ')') {
|
|
parentheses--;
|
|
} else if (!quoted && parentheses == 0 && current == ',') {
|
|
definitions.add(body.substring(start, i).trim());
|
|
start = i + 1;
|
|
}
|
|
}
|
|
definitions.add(body.substring(start).trim());
|
|
return definitions.stream().filter(value -> !value.isEmpty()).toList();
|
|
}
|
|
|
|
private record TableDefinition(String name, List<String> columns) {
|
|
}
|
|
|
|
private record MigrationColumn(String table, String column) {
|
|
}
|
|
}
|