239 lines
11 KiB
Java
239 lines
11 KiB
Java
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_category")) {
|
|
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);
|
|
}
|
|
}
|
|
}
|