20260729231854
This commit is contained in:
1 parent
1c839b707a
commit
84673b3d3d
39 files changed
+4683
-281
No files matched your search
@@ -0,0 +1,238 @@
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** Performs read-only checks after the business schema migrations have run. */
|
||||
public final class VerifyBusinessMigration {
|
||||
private static final List<String> TABLE_MIGRATIONS = List.of(
|
||||
"016_create_base_tables.sql",
|
||||
"017_create_contract_file_tables.sql",
|
||||
"018_create_business_tables.sql",
|
||||
"019_create_finance_tables.sql",
|
||||
"020_create_oa_tables.sql"
|
||||
);
|
||||
private static final Pattern CREATE_TABLE = Pattern.compile(
|
||||
"(?i)CREATE\\s+TABLE\\s+dbo\\.(?:\\[([^]]+)]|([A-Za-z0-9_]+))"
|
||||
);
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Path apiRoot = Path.of("").toAbsolutePath().normalize();
|
||||
Path migrationDirectory = apiRoot.resolve("config/migrations");
|
||||
Set<String> generatedTables = loadGeneratedTables(migrationDirectory);
|
||||
|
||||
Properties properties = new Properties();
|
||||
try (InputStream input = Files.newInputStream(apiRoot.resolve("config/dbconfigs/G3HD.properties"))) {
|
||||
properties.load(input);
|
||||
}
|
||||
|
||||
try (Connection connection = DriverManager.getConnection(
|
||||
properties.getProperty("url"),
|
||||
properties.getProperty("username"),
|
||||
properties.getProperty("password")
|
||||
)) {
|
||||
if (!"FMS".equalsIgnoreCase(connection.getCatalog())) {
|
||||
throw new IllegalStateException("Refusing to verify database: " + connection.getCatalog());
|
||||
}
|
||||
|
||||
verifyGeneratedTables(connection, generatedTables);
|
||||
verifyCoreTables(connection);
|
||||
verifyViews(connection);
|
||||
printCatalogCounts(connection, generatedTables.size());
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> loadGeneratedTables(Path migrationDirectory) throws Exception {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
for (String migration : TABLE_MIGRATIONS) {
|
||||
String sql = Files.readString(migrationDirectory.resolve(migration), StandardCharsets.UTF_8);
|
||||
Matcher matcher = CREATE_TABLE.matcher(sql);
|
||||
while (matcher.find()) {
|
||||
result.add(matcher.group(1) != null ? matcher.group(1) : matcher.group(2));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void verifyGeneratedTables(Connection connection, Set<String> tables) throws Exception {
|
||||
for (String table : tables) {
|
||||
if (!tableExists(connection, table)) {
|
||||
throw new IllegalStateException("Missing generated table: dbo." + table);
|
||||
}
|
||||
if (rowCount(connection, table) != 0) {
|
||||
throw new IllegalStateException("Generated table is not empty: dbo." + table);
|
||||
}
|
||||
|
||||
String primaryKey = columnExists(connection, table, "subid") ? "subid" : "b_id";
|
||||
if (!primaryKeyMatches(connection, table, primaryKey)) {
|
||||
throw new IllegalStateException("Unexpected primary key on dbo." + table + "; expected " + primaryKey);
|
||||
}
|
||||
verifyIdTypes(connection, table);
|
||||
verifyNoLegacyTypes(connection, table);
|
||||
verifyRemovedPluralIds(connection, table);
|
||||
}
|
||||
System.out.println("PASS|generated_tables=" + tables.size() + "|empty=true|keys=true|types=true");
|
||||
}
|
||||
|
||||
private static void verifyCoreTables(Connection connection) throws Exception {
|
||||
for (String table : List.of("b_othercompany", "b_contact", "b_othercompany_bmfl")) {
|
||||
if (!tableExists(connection, table)) {
|
||||
throw new IllegalStateException("Missing core business table: dbo." + table);
|
||||
}
|
||||
verifyIdTypes(connection, table);
|
||||
}
|
||||
|
||||
long users = scalar(connection, "SELECT COUNT_BIG(*) FROM dbo.b_user");
|
||||
long modules = scalar(connection, "SELECT COUNT_BIG(*) FROM dbo.s_module");
|
||||
long fields = scalar(connection, "SELECT COUNT_BIG(*) FROM dbo.s_module_field");
|
||||
long autoCodes = scalar(connection, "SELECT COUNT_BIG(*) FROM dbo.s_module_auto_code");
|
||||
if (users == 0 || modules == 0 || fields == 0) {
|
||||
throw new IllegalStateException("Existing user or module metadata is missing");
|
||||
}
|
||||
System.out.println("PASS|preserved|users=" + users + "|modules=" + modules
|
||||
+ "|fields=" + fields + "|auto_codes=" + autoCodes);
|
||||
}
|
||||
|
||||
private static void verifyViews(Connection connection) throws Exception {
|
||||
List<String> viewNames = new ArrayList<>();
|
||||
try (Statement statement = connection.createStatement(); ResultSet rows = statement.executeQuery(
|
||||
"SELECT QUOTENAME(SCHEMA_NAME(schema_id)) + N'.' + QUOTENAME(name) "
|
||||
+ "FROM sys.views WHERE is_ms_shipped = 0 ORDER BY name"
|
||||
)) {
|
||||
while (rows.next()) {
|
||||
viewNames.add(rows.getString(1));
|
||||
}
|
||||
}
|
||||
for (String viewName : viewNames) {
|
||||
try (Statement refresh = connection.createStatement()) {
|
||||
refresh.execute("EXEC sys.sp_refreshview N'" + viewName.replace("'", "''") + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("PASS|views_refreshed=" + viewNames.size());
|
||||
}
|
||||
|
||||
private static void printCatalogCounts(Connection connection, int generatedTableCount) throws Exception {
|
||||
try (Statement statement = connection.createStatement(); ResultSet rows = statement.executeQuery(
|
||||
"SELECT (SELECT COUNT(*) FROM sys.tables WHERE is_ms_shipped = 0), "
|
||||
+ "(SELECT COUNT(*) FROM sys.views WHERE is_ms_shipped = 0)"
|
||||
)) {
|
||||
rows.next();
|
||||
System.out.println("PASS|catalog|tables=" + rows.getInt(1) + "|views=" + rows.getInt(2)
|
||||
+ "|generated_tables=" + generatedTableCount);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean tableExists(Connection connection, String table) throws Exception {
|
||||
try (PreparedStatement statement = connection.prepareStatement(
|
||||
"SELECT COUNT(*) FROM sys.tables WHERE schema_id = SCHEMA_ID(N'dbo') AND name = ?"
|
||||
)) {
|
||||
statement.setString(1, table);
|
||||
try (ResultSet rows = statement.executeQuery()) {
|
||||
rows.next();
|
||||
return rows.getInt(1) == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean columnExists(Connection connection, String table, String column) throws Exception {
|
||||
try (PreparedStatement statement = connection.prepareStatement(
|
||||
"SELECT COUNT(*) FROM sys.columns WHERE object_id = OBJECT_ID(?) AND name = ?"
|
||||
)) {
|
||||
statement.setString(1, "dbo." + table);
|
||||
statement.setString(2, column);
|
||||
try (ResultSet rows = statement.executeQuery()) {
|
||||
rows.next();
|
||||
return rows.getInt(1) == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean primaryKeyMatches(Connection connection, String table, String expected) throws Exception {
|
||||
String sql = """
|
||||
SELECT c.name
|
||||
FROM sys.indexes AS i
|
||||
INNER JOIN sys.index_columns AS ic
|
||||
ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
||||
INNER JOIN sys.columns AS c
|
||||
ON c.object_id = ic.object_id AND c.column_id = ic.column_id
|
||||
WHERE i.object_id = OBJECT_ID(?) AND i.is_primary_key = 1
|
||||
ORDER BY ic.key_ordinal
|
||||
""";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, "dbo." + table);
|
||||
try (ResultSet rows = statement.executeQuery()) {
|
||||
return rows.next()
|
||||
&& rows.getString(1).equalsIgnoreCase(expected)
|
||||
&& !rows.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyIdTypes(Connection connection, String table) throws Exception {
|
||||
String sql = """
|
||||
SELECT c.name, TYPE_NAME(c.user_type_id)
|
||||
FROM sys.columns AS c
|
||||
WHERE c.object_id = OBJECT_ID(?)
|
||||
AND (LOWER(c.name) IN (N'b_id', N'subid', N'father') OR LOWER(c.name) LIKE N'%[_]id')
|
||||
AND TYPE_NAME(c.user_type_id) <> N'bigint'
|
||||
""";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, "dbo." + table);
|
||||
try (ResultSet rows = statement.executeQuery()) {
|
||||
if (rows.next()) {
|
||||
throw new IllegalStateException("Non-bigint ID column: dbo." + table + "."
|
||||
+ rows.getString(1) + " (" + rows.getString(2).toLowerCase(Locale.ROOT) + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyNoLegacyTypes(Connection connection, String table) throws Exception {
|
||||
String sql = """
|
||||
SELECT c.name, TYPE_NAME(c.user_type_id)
|
||||
FROM sys.columns AS c
|
||||
WHERE c.object_id = OBJECT_ID(?)
|
||||
AND TYPE_NAME(c.user_type_id) IN (N'text', N'ntext', N'image', N'float', N'real', N'uniqueidentifier')
|
||||
""";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, "dbo." + table);
|
||||
try (ResultSet rows = statement.executeQuery()) {
|
||||
if (rows.next()) {
|
||||
throw new IllegalStateException("Legacy type remains: dbo." + table + "." + rows.getString(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyRemovedPluralIds(Connection connection, String table) throws Exception {
|
||||
for (String column : List.of("b_yw_ids", "b_bill_ids", "b_inv_ids")) {
|
||||
if (columnExists(connection, table, column)) {
|
||||
throw new IllegalStateException("Legacy plural-ID column remains: dbo." + table + "." + column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long rowCount(Connection connection, String table) throws Exception {
|
||||
return scalar(connection, "SELECT COUNT_BIG(*) FROM dbo.[" + table.replace("]", "]]" ) + "]");
|
||||
}
|
||||
|
||||
private static long scalar(Connection connection, String sql) throws Exception {
|
||||
try (Statement statement = connection.createStatement(); ResultSet rows = statement.executeQuery(sql)) {
|
||||
rows.next();
|
||||
return rows.getLong(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user