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.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; /** * Reads the legacy SQL Server catalog and emits reviewed, schema-only FMS migrations. * The generated SQL never connects back to the legacy database and never contains data. */ public final class GenerateBusinessMigrations { private static final Set EXCLUDED_TABLES = lowerSet( "b_contact", "b_contact_type", "b_othercompany", "b_othercompany_bmfl", "b_user", "b_user_power", "b_user_qx", "b_user_record", "dtproperties", "log_datas" ); private static final Set TREE_TABLES = lowerSet( "b_other_bmfl", "bc_pricelist_bmfl", "oa_title_bmfl" ); private static final Set EXCLUDED_VIEWS = lowerSet( "v_autoidrule", "v_b_contact", "v_b_employee", "v_b_user_record" ); private static final Set BOOLEAN_COLUMNS = lowerSet( "b_black", "b_canuse", "b_visible", "b_used", "b_default", "b_isdefault", "b_finish", "b_sh", "b_bill", "b_zx", "b_toall", "b_reminder", "b_othermachinelogin", "b_salesman", "b_docman", "b_dealman", "b_visible_customer", "b_visible_supply", "b_levelvisible", "b_fromcustomer", "b_logintime", "b_checkxy", "b_loginfee", "mx_finish", "mx_sh" ); private static final Set TRUE_DEFAULT_COLUMNS = lowerSet( "b_canuse", "b_visible", "b_used" ); private static final Set UNICODE_HINTS = lowerSet( "name", "memo", "bz", "addr", "address", "subject", "content", "info", "desc", "caption", "department", "duty", "bank", "goods", "mark", "remark", "taste", "habit", "situation", "place", "body" ); private static final Map BUSINESS_CODE_FIELDS = Map.ofEntries( Map.entry("bc_khht", "b_htno"), Map.entry("bs_business", "b_ywno"), Map.entry("cw_account", "b_account"), Map.entry("cw_bill", "b_ywno"), Map.entry("cw_invoice", "b_ywno"), Map.entry("cw_sfd", "b_ywno") ); private static final Map CHILD_PARENTS = Map.ofEntries( Map.entry("b_othercompany_accountbank", "b_othercompany"), Map.entry("bc_pricelist_sub", "bc_pricelist"), Map.entry("bs_business_container", "bs_business"), Map.entry("bs_business_cw", "bs_business"), Map.entry("bs_business_hdbl", "bs_business"), Map.entry("bs_business_transport", "bs_business"), Map.entry("cw_billsub", "cw_bill"), Map.entry("cw_invoicesub", "cw_invoice"), Map.entry("cw_sfdsub", "cw_sfd"), Map.entry("oa_notices_attach", "oa_notices"), Map.entry("oa_noticessub", "oa_notices") ); private static final Map RENAMED_COLUMNS = Map.ofEntries( Map.entry("bs_business_cw.mx_type_id", "mx_direction"), Map.entry("bs_business_transport.mx_hstype_id", "mx_source_type"), Map.entry("cw_bill.b_module_id", "b_direction"), Map.entry("cw_bill.b_type_id", "b_document_type"), Map.entry("cw_invoice.b_module_id", "b_direction"), Map.entry("cw_invoice.b_type_id", "b_document_type"), Map.entry("cw_sfd.b_module_id", "b_direction"), Map.entry("cw_sfd.b_type_id", "b_document_type") ); private record Column( String name, String type, int maxLength, int precision, int scale, boolean nullable, int ordinal ) {} private record Table(String name, List columns) {} public static void main(String[] args) throws Exception { Path apiRoot = Path.of("").toAbsolutePath().normalize(); Path sourceConfig = apiRoot.resolve("config/dbconfigs/G3HY2025.properties"); Path migrations = apiRoot.resolve("config/migrations"); Properties properties = new Properties(); try (InputStream input = Files.newInputStream(sourceConfig)) { properties.load(input); } try (Connection connection = DriverManager.getConnection( properties.getProperty("url"), properties.getProperty("username"), properties.getProperty("password") )) { Map tables = loadTables(connection); writeDomain(migrations.resolve("016_create_base_tables.sql"), "基础资料", tables, "base"); writeDomain( migrations.resolve("017_create_contract_file_tables.sql"), "合同和文件资料", tables, "contract-file" ); writeDomain(migrations.resolve("018_create_business_tables.sql"), "业务单据", tables, "business"); writeDomain(migrations.resolve("019_create_finance_tables.sql"), "财务", tables, "finance"); writeDomain(migrations.resolve("020_create_oa_tables.sql"), "OA", tables, "oa"); writeViews(migrations.resolve("021_create_business_views.sql"), connection); writeVerification(migrations.resolve("verify_business_schema.sql"), tables, connection); } } private static Map loadTables(Connection connection) throws Exception { String sql = """ SELECT t.name AS table_name, c.name AS column_name, ty.name AS type_name, c.max_length, c.precision, c.scale, c.is_nullable, c.column_id FROM sys.tables AS t INNER JOIN sys.schemas AS s ON s.schema_id = t.schema_id INNER JOIN sys.columns AS c ON c.object_id = t.object_id INNER JOIN sys.types AS ty ON ty.user_type_id = c.user_type_id WHERE s.name = N'dbo' AND t.is_ms_shipped = 0 ORDER BY t.name, c.column_id """; Map> columnsByTable = new LinkedHashMap<>(); try (Statement statement = connection.createStatement(); ResultSet rows = statement.executeQuery(sql)) { while (rows.next()) { String tableName = rows.getString("table_name"); columnsByTable.computeIfAbsent(tableName, ignored -> new ArrayList<>()).add(new Column( rows.getString("column_name"), rows.getString("type_name"), rows.getInt("max_length"), rows.getInt("precision"), rows.getInt("scale"), rows.getBoolean("is_nullable"), rows.getInt("column_id") )); } } Map result = new LinkedHashMap<>(); for (Map.Entry> entry : columnsByTable.entrySet()) { if (!includeTable(entry.getKey())) continue; result.put(entry.getKey().toLowerCase(Locale.ROOT), new Table(entry.getKey(), entry.getValue())); } return result; } private static boolean includeTable(String tableName) { String name = tableName.toLowerCase(Locale.ROOT); if (EXCLUDED_TABLES.contains(name)) return false; return name.startsWith("b_") || name.startsWith("bc_") || name.startsWith("bf_") || name.startsWith("bs_") || name.startsWith("cw_") || name.startsWith("oa_"); } private static String domainOf(String tableName) { String name = tableName.toLowerCase(Locale.ROOT); if (name.startsWith("bc_") || name.startsWith("bf_")) return "contract-file"; if (name.startsWith("bs_")) return "business"; if (name.startsWith("cw_")) return "finance"; if (name.startsWith("oa_")) return "oa"; return "base"; } private static void writeDomain( Path target, String title, Map tables, String domain ) throws Exception { StringBuilder sql = new StringBuilder(header(title)); tables.values().stream() .filter(table -> domainOf(table.name()).equals(domain)) .sorted(Comparator.comparing(Table::name, String.CASE_INSENSITIVE_ORDER)) .forEach(table -> sql.append(createTable(table))); if (domain.equals("finance")) appendFinanceRelations(sql); sql.append("\nCOMMIT TRANSACTION;\n"); Files.writeString(target, sql.toString(), StandardCharsets.UTF_8); } private static String header(String title) { return """ /* Generated schema-only migration: %s. No legacy data is copied. */ SET NOCOUNT ON; SET XACT_ABORT ON; IF DB_NAME() <> N'FMS' THROW 51501, N'本迁移只能在 FMS 目标数据库执行。', 1; BEGIN TRANSACTION; """.formatted(title); } private static String createTable(Table table) { String lowerName = table.name().toLowerCase(Locale.ROOT); if (TREE_TABLES.contains(lowerName)) return createTreeTable(table.name()); boolean child = hasColumn(table, "subid"); String primaryKey = child ? originalColumnName(table, "subid") : "b_id"; boolean sourceHasStringId = !child && table.columns().stream().anyMatch(column -> column.name().equalsIgnoreCase("b_id") && isTextType(column.type()) ); String businessCode = businessCodeField(table, sourceHasStringId); List definitions = new ArrayList<>(); if (!child && !hasColumn(table, "b_id")) { definitions.add(" b_id bigint NOT NULL"); } for (Column column : table.columns()) { if (isPluralIdColumn(column.name())) continue; String targetName = targetColumnName(table, column.name()); String definition = " " + quote(targetName) + " " + targetType(table, column); boolean requiredCode = businessCode != null && column.name().equalsIgnoreCase(businessCode); boolean requiredKey = column.name().equalsIgnoreCase(primaryKey) || (!child && column.name().equalsIgnoreCase("b_id")); if (BOOLEAN_COLUMNS.contains(column.name().toLowerCase(Locale.ROOT))) { int defaultValue = TRUE_DEFAULT_COLUMNS.contains(column.name().toLowerCase(Locale.ROOT)) ? 1 : 0; definition += " NOT NULL CONSTRAINT " + quote("DF_" + safeName(table.name()) + "_" + safeName(column.name())) + " DEFAULT (" + defaultValue + ")"; } else { definition += requiredCode || requiredKey ? " NOT NULL" : column.nullable() ? " NULL" : " NOT NULL"; } definitions.add(definition); } if (sourceHasStringId && businessCode != null && !hasColumn(table, businessCode)) { definitions.add(" " + quote(businessCode) + " varchar(50) NOT NULL"); } definitions.add(" CONSTRAINT " + quote("PK_" + safeName(table.name())) + " PRIMARY KEY (" + quote(primaryKey) + ")"); if (businessCode != null) { definitions.add(" CONSTRAINT " + quote("UQ_" + safeName(table.name()) + "_" + businessCode) + " UNIQUE (" + quote(businessCode) + ")"); } String parentTable = CHILD_PARENTS.get(lowerName); if (child && parentTable != null && hasColumn(table, "father")) { definitions.add(" CONSTRAINT " + quote("FK_" + safeName(table.name()) + "_father") + " FOREIGN KEY (" + quote(originalColumnName(table, "father")) + ") REFERENCES dbo." + quote(parentTable) + " (b_id)"); } StringBuilder result = new StringBuilder(); result.append(" IF OBJECT_ID(N'dbo.").append(escape(table.name())).append("', N'U') IS NULL\n"); result.append(" BEGIN\n"); result.append(" CREATE TABLE dbo.").append(quote(table.name())).append(" (\n"); result.append(String.join(",\n", definitions)).append("\n );\n"); appendIndexes(result, table, primaryKey, businessCode); result.append(" END;\n\n"); return result.toString(); } private static String createTreeTable(String tableName) { String safe = safeName(tableName); return """ IF OBJECT_ID(N'dbo.%s', N'U') IS NULL BEGIN CREATE TABLE dbo.%s ( b_id bigint NOT NULL, b_parent_id bigint NULL, b_name nvarchar(150) NOT NULL, b_level int NOT NULL CONSTRAINT %s DEFAULT (1), b_path varchar(1000) NOT NULL, b_xh int NOT NULL CONSTRAINT %s DEFAULT (0), b_canuse int NOT NULL CONSTRAINT %s DEFAULT (1), b_inputuser_id bigint NULL, b_inputdatetime datetime2(0) NOT NULL CONSTRAINT %s DEFAULT (SYSDATETIME()), b_updateuser_id bigint NULL, b_updatedatetime datetime2(0) NULL, CONSTRAINT %s PRIMARY KEY (b_id), CONSTRAINT %s CHECK (b_parent_id IS NULL OR b_parent_id <> b_id), CONSTRAINT %s FOREIGN KEY (b_parent_id) REFERENCES dbo.%s (b_id) ); CREATE INDEX %s ON dbo.%s (b_parent_id, b_xh, b_id); CREATE INDEX %s ON dbo.%s (b_path, b_xh, b_id); END; """.formatted( escape(tableName), quote(tableName), quote("DF_" + safe + "_level"), quote("DF_" + safe + "_xh"), quote("DF_" + safe + "_canuse"), quote("DF_" + safe + "_inputdatetime"), quote("PK_" + safe), quote("CK_" + safe + "_parent"), quote("FK_" + safe + "_parent"), quote(tableName), quote("IX_" + safe + "_parent"), quote(tableName), quote("IX_" + safe + "_path"), quote(tableName) ); } private static void appendIndexes( StringBuilder sql, Table table, String primaryKey, String businessCode ) { Set indexed = new LinkedHashSet<>(); for (Column column : table.columns()) { String targetName = targetColumnName(table, column.name()); String lower = targetName.toLowerCase(Locale.ROOT); if (lower.equals(primaryKey.toLowerCase(Locale.ROOT))) continue; if (businessCode != null && lower.equals(businessCode.toLowerCase(Locale.ROOT))) continue; if (lower.equals("father") || (lower.endsWith("_id") && !isAuditUserColumn(lower))) { if (!indexed.add(lower)) continue; String indexName = "IX_" + safeName(table.name()) + "_" + safeName(column.name()); sql.append(" CREATE INDEX ").append(quote(indexName)).append(" ON dbo.") .append(quote(table.name())).append(" (").append(quote(targetName)) .append(", ").append(quote(primaryKey)).append(");\n"); } } } private static String businessCodeField(Table table, boolean sourceHasStringId) { String lowerName = table.name().toLowerCase(Locale.ROOT); String configured = BUSINESS_CODE_FIELDS.get(lowerName); if (configured != null) return configured; if (hasColumn(table, "b_code")) return originalColumnName(table, "b_code"); return sourceHasStringId ? "b_code" : null; } private static String targetType(Table table, Column column) { String name = column.name().toLowerCase(Locale.ROOT); String sourceType = column.type().toLowerCase(Locale.ROOT); String renameKey = table.name().toLowerCase(Locale.ROOT) + "." + name; if (renameKey.equals("bs_business_cw.mx_type_id") || renameKey.equals("cw_bill.b_module_id") || renameKey.equals("cw_invoice.b_module_id") || renameKey.equals("cw_sfd.b_module_id")) { return "varchar(20)"; } if (renameKey.equals("bs_business_transport.mx_hstype_id") || renameKey.endsWith(".b_type_id")) { return "varchar(50)"; } if (name.equals("b_id") || name.equals("subid") || name.equals("father") || isReferenceId(name)) { return "bigint"; } if (BOOLEAN_COLUMNS.contains(name) || sourceType.equals("bit")) return "int"; if (name.endsWith("datetime") || name.endsWith("_datetime") || name.equals("b_inputdate")) { return "datetime2(0)"; } if (name.equals("b_inputtime")) return "time(0)"; if (name.equals("b_birthday") || name.endsWith("begindate") || name.endsWith("enddate") || name.endsWith("expiredate")) { return "date"; } if (sourceType.equals("text") || sourceType.equals("ntext")) return "nvarchar(max)"; if (sourceType.equals("image")) return "varbinary(max)"; if (sourceType.equals("datetime") || sourceType.equals("smalldatetime")) return "datetime2(0)"; if (sourceType.equals("float") || sourceType.equals("real")) return "decimal(18,6)"; if (sourceType.equals("money") || sourceType.equals("smallmoney")) return "decimal(19,4)"; if (sourceType.equals("decimal") || sourceType.equals("numeric")) { return sourceType + "(" + column.precision() + "," + column.scale() + ")"; } if (sourceType.equals("varchar") || sourceType.equals("char") || sourceType.equals("nvarchar") || sourceType.equals("nchar")) { boolean unicode = sourceType.startsWith("n") || hasUnicodeHint(name); String base = unicode ? "nvarchar" : "varchar"; int bytesPerChar = sourceType.startsWith("n") ? 2 : 1; String length = column.maxLength() < 0 ? "max" : String.valueOf(column.maxLength() / bytesPerChar); return base + "(" + length + ")"; } if (sourceType.equals("binary") || sourceType.equals("varbinary")) { String length = column.maxLength() < 0 ? "max" : String.valueOf(column.maxLength()); return sourceType + "(" + length + ")"; } if (sourceType.equals("timestamp") || sourceType.equals("rowversion")) return "binary(8)"; if (sourceType.equals("uniqueidentifier")) return "bigint"; return sourceType; } private static boolean isReferenceId(String name) { return name.endsWith("_id") || name.equals("userid") || name.equals("moduleid") || name.endsWith("userid") || name.endsWith("moduleid"); } private static boolean isAuditUserColumn(String name) { return name.equals("b_inputuser_id") || name.equals("b_updateuser_id") || name.equals("mx_inputuser_id") || name.equals("mx_updateuser_id"); } private static boolean isPluralIdColumn(String name) { String lower = name.toLowerCase(Locale.ROOT); return lower.endsWith("_ids") || lower.equals("b_yw_ids") || lower.equals("b_bill_ids") || lower.equals("b_inv_ids"); } private static boolean hasUnicodeHint(String name) { for (String hint : UNICODE_HINTS) { if (name.contains(hint)) return true; } return false; } private static boolean isTextType(String type) { String lower = type.toLowerCase(Locale.ROOT); return lower.equals("varchar") || lower.equals("nvarchar") || lower.equals("char") || lower.equals("nchar") || lower.equals("text") || lower.equals("ntext"); } private static boolean hasColumn(Table table, String name) { return table.columns().stream().anyMatch(column -> column.name().equalsIgnoreCase(name)); } private static String originalColumnName(Table table, String name) { return table.columns().stream() .filter(column -> column.name().equalsIgnoreCase(name)) .findFirst() .map(Column::name) .orElse(name); } private static void appendFinanceRelations(StringBuilder sql) { appendRelation(sql, "cw_bill_business", "b_bill_id", "cw_bill", "b_business_id", "bs_business"); appendRelation(sql, "cw_invoice_bill", "b_invoice_id", "cw_invoice", "b_bill_id", "cw_bill"); appendRelation(sql, "cw_invoice_business", "b_invoice_id", "cw_invoice", "b_business_id", "bs_business"); appendRelation(sql, "cw_sfd_business", "b_sfd_id", "cw_sfd", "b_business_id", "bs_business"); appendRelation(sql, "cw_sfd_invoice", "b_sfd_id", "cw_sfd", "b_invoice_id", "cw_invoice"); appendRelation(sql, "cw_sfd_bill", "b_sfd_id", "cw_sfd", "b_bill_id", "cw_bill"); } private static void appendRelation( StringBuilder sql, String table, String leftColumn, String leftTable, String rightColumn, String rightTable ) { String safe = safeName(table); sql.append(" IF OBJECT_ID(N'dbo.").append(table).append("', N'U') IS NULL\n") .append(" BEGIN\n") .append(" CREATE TABLE dbo.").append(quote(table)).append(" (\n") .append(" subid bigint NOT NULL,\n") .append(" ").append(quote(leftColumn)).append(" bigint NOT NULL,\n") .append(" ").append(quote(rightColumn)).append(" bigint NOT NULL,\n") .append(" b_xh int NOT NULL CONSTRAINT ").append(quote("DF_" + safe + "_xh")) .append(" DEFAULT (0),\n") .append(" CONSTRAINT ").append(quote("PK_" + safe)).append(" PRIMARY KEY (subid),\n") .append(" CONSTRAINT ").append(quote("UQ_" + safe)).append(" UNIQUE (") .append(quote(leftColumn)).append(", ").append(quote(rightColumn)).append("),\n") .append(" CONSTRAINT ").append(quote("FK_" + safe + "_left")).append(" FOREIGN KEY (") .append(quote(leftColumn)).append(") REFERENCES dbo.").append(quote(leftTable)).append(" (b_id),\n") .append(" CONSTRAINT ").append(quote("FK_" + safe + "_right")).append(" FOREIGN KEY (") .append(quote(rightColumn)).append(") REFERENCES dbo.").append(quote(rightTable)).append(" (b_id)\n") .append(" );\n") .append(" END;\n\n"); } private static void writeViews(Path target, Connection connection) throws Exception { String query = """ SELECT v.name, sm.definition FROM sys.views AS v INNER JOIN sys.schemas AS s ON s.schema_id = v.schema_id INNER JOIN sys.sql_modules AS sm ON sm.object_id = v.object_id WHERE s.name = N'dbo' ORDER BY v.name """; StringBuilder sql = new StringBuilder(header("业务视图")); try (Statement statement = connection.createStatement(); ResultSet rows = statement.executeQuery(query)) { while (rows.next()) { String name = rows.getString("name"); if (!includeView(name)) continue; String definition = rows.getString("definition"); if (name.equalsIgnoreCase("v_b_othercompany_bmfl")) { definition = "CREATE VIEW dbo.v_b_othercompany_bmfl AS " + "SELECT b_id, b_name FROM dbo.b_othercompany_bmfl"; } else if (name.equalsIgnoreCase("v_b_feetype")) { definition = "CREATE VIEW dbo.v_b_feetype AS SELECT " + "fee.b_id, fee.b_name, fee.b_name_e, fee.b_bz, fee.b_default, " + "fee.b_amount, fee.b_tax, fee.b_money_id, fee.b_inputuser_id, " + "fee.b_inputdatetime, fee.b_updateuser_id, fee.b_updatedatetime, money.b_hl " + "FROM dbo.b_feetype AS fee " + "LEFT JOIN dbo.b_money AS money ON money.b_id = fee.b_money_id"; } definition = removeLegacyPluralIds(definition); definition = renameLegacySemanticIds(name, definition); sql.append(" IF OBJECT_ID(N'dbo.").append(escape(name)).append("', N'V') IS NOT NULL\n") .append(" DROP VIEW dbo.").append(quote(name)).append(";\n") .append(" EXEC sys.sp_executesql N'") .append(escape(definition)) .append("';\n\n"); } } sql.append("COMMIT TRANSACTION;\n"); Files.writeString(target, sql.toString(), StandardCharsets.UTF_8); } private static boolean includeView(String viewName) { String name = viewName.toLowerCase(Locale.ROOT); if (EXCLUDED_VIEWS.contains(name)) return false; return name.startsWith("v_b_") || name.startsWith("v_bc_") || name.startsWith("v_bs_") || name.startsWith("v_cw_") || name.equals("v_feetype"); } private static String removeLegacyPluralIds(String definition) { String result = definition; for (String field : List.of("b_yw_ids", "b_bill_ids", "b_inv_ids")) { result = result.replaceAll( "(?i)(?:dbo\\.)?[A-Za-z0-9_]+\\." + field + "\\s*,\\s*", "" ); result = result.replaceAll( "(?i),\\s*(?:dbo\\.)?[A-Za-z0-9_]+\\." + field + "(?=\\s+FROM)", "" ); } return result; } private static String renameLegacySemanticIds(String viewName, String definition) { String result = definition; result = replaceIgnoreCase(result, "dbo.bs_business_cw.mx_type_id", "dbo.bs_business_cw.mx_direction"); result = replaceIgnoreCase(result, "dbo.bs_business_transport.mx_hstype_id", "dbo.bs_business_transport.mx_source_type"); result = replaceIgnoreCase(result, "dbo.cw_bill.b_module_id", "dbo.cw_bill.b_direction"); result = replaceIgnoreCase(result, "dbo.cw_bill.b_type_id", "dbo.cw_bill.b_document_type"); result = replaceIgnoreCase(result, "dbo.cw_invoice.b_module_id", "dbo.cw_invoice.b_direction"); result = replaceIgnoreCase(result, "dbo.cw_invoice.b_type_id", "dbo.cw_invoice.b_document_type"); result = replaceIgnoreCase(result, "dbo.cw_sfd.b_module_id", "dbo.cw_sfd.b_direction"); result = replaceIgnoreCase(result, "dbo.cw_sfd.b_type_id", "dbo.cw_sfd.b_document_type"); if (viewName.equalsIgnoreCase("v_bs_business_cw") || viewName.equalsIgnoreCase("v_bs_business_cw_yf") || viewName.equalsIgnoreCase("v_bs_business_cw_ys")) { result = replaceIgnoreCase(result, "mx_type_id", "mx_direction"); } if (viewName.equalsIgnoreCase("v_cw_account")) { result = replaceIgnoreCase(result, "b_type_id", "b_direction"); result = replaceIgnoreCase( result, "a.b_account_id = dbo.cw_account.b_account", "a.b_account_id = dbo.cw_account.b_id" ); } return result; } private static String replaceIgnoreCase(String value, String source, String replacement) { return value.replaceAll("(?i)" + java.util.regex.Pattern.quote(source), replacement); } private static String targetColumnName(Table table, String sourceName) { String key = table.name().toLowerCase(Locale.ROOT) + "." + sourceName.toLowerCase(Locale.ROOT); return RENAMED_COLUMNS.getOrDefault(key, sourceName); } private static void writeVerification( Path target, Map tables, Connection connection ) throws Exception { int expectedTables = tables.size() + 6 + 5; int expectedViews = 0; try (Statement statement = connection.createStatement(); ResultSet rows = statement.executeQuery( "SELECT name FROM sys.views WHERE is_ms_shipped = 0" )) { while (rows.next()) if (includeView(rows.getString(1))) expectedViews++; } String sql = """ SET NOCOUNT ON; IF DB_NAME() <> N'FMS' THROW 51510, N'验证脚本只能在 FMS 目标数据库执行。', 1; SELECT (SELECT COUNT(*) FROM sys.tables WHERE is_ms_shipped = 0) AS table_count, (SELECT COUNT(*) FROM sys.views WHERE is_ms_shipped = 0) AS view_count, %d AS generated_business_table_minimum, %d AS generated_business_view_count; SELECT OBJECT_SCHEMA_NAME(c.object_id) AS schema_name, OBJECT_NAME(c.object_id) AS object_name, c.name AS column_name, TYPE_NAME(c.user_type_id) AS type_name FROM sys.columns AS c INNER JOIN sys.tables AS t ON t.object_id = c.object_id WHERE t.is_ms_shipped = 0 AND (c.name = N'b_id' OR c.name = N'subid' OR c.name = N'father' OR c.name LIKE N'%%[_]id') AND TYPE_NAME(c.user_type_id) <> N'bigint' ORDER BY schema_name, object_name, c.column_id; SELECT s.name AS schema_name, v.name AS view_name FROM sys.views AS v INNER JOIN sys.schemas AS s ON s.schema_id = v.schema_id WHERE v.is_ms_shipped = 0 AND OBJECTPROPERTY(v.object_id, 'IsSchemaBound') IN (0, 1) AND EXISTS ( SELECT 1 FROM sys.sql_expression_dependencies AS d WHERE d.referencing_id = v.object_id AND d.referenced_id IS NULL AND d.referenced_entity_name IS NOT NULL ) ORDER BY s.name, v.name; """.formatted(expectedTables, expectedViews); Files.writeString(target, sql, StandardCharsets.UTF_8); } private static String quote(String identifier) { return "[" + identifier.replace("]", "]]" ) + "]"; } private static String escape(String value) { return value.replace("'", "''"); } private static String safeName(String value) { return value.replaceAll("[^A-Za-z0-9_]", "_"); } private static Set lowerSet(String... values) { Set result = new HashSet<>(); for (String value : values) result.add(value.toLowerCase(Locale.ROOT)); return result; } }