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 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 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 loadGeneratedTables(Path migrationDirectory) throws Exception { Set 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 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 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); } } }