diff --git a/code/fms/.tmp-fkcheck/BUserRebuild.class b/code/fms/.tmp-fkcheck/BUserRebuild.class new file mode 100644 index 00000000..d2e829de Binary files /dev/null and b/code/fms/.tmp-fkcheck/BUserRebuild.class differ diff --git a/code/fms/.tmp-fkcheck/BUserRebuild.java b/code/fms/.tmp-fkcheck/BUserRebuild.java new file mode 100644 index 00000000..d2b5ecf8 --- /dev/null +++ b/code/fms/.tmp-fkcheck/BUserRebuild.java @@ -0,0 +1,95 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +import java.util.stream.Collectors; + +public class BUserRebuild { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + conn.setAutoCommit(false); + + // 1. stage 001/002 into temp table + run(conn, "IF OBJECT_ID('dbo.b_user_keep') IS NOT NULL DROP TABLE dbo.b_user_keep"); + run(conn, "SELECT * INTO dbo.b_user_keep FROM dbo.b_user WHERE b_account IN ('001','002')"); + + // 2. drop + recreate with b_id first + run(conn, "DROP TABLE dbo.b_user"); + String create = "CREATE TABLE dbo.b_user (\n" + + " b_id bigint identity(1,1) NOT NULL,\n" + + " b_account nvarchar(50) NOT NULL,\n" + + " b_name nvarchar(50) NULL,\n" + + " b_department nvarchar(50) NULL,\n" + + " b_subcode nvarchar(50) NULL,\n" + + " b_machineid nvarchar(50) NULL,\n" + + " b_bz nvarchar(200) NULL,\n" + + " b_password nvarchar(255) NOT NULL,\n" + + " b_canuse tinyint NOT NULL DEFAULT ((1)),\n" + + " b_othermachinelogin tinyint NOT NULL DEFAULT ((0)),\n" + + " b_salesman tinyint NOT NULL DEFAULT ((0)),\n" + + " b_docman tinyint NOT NULL DEFAULT ((0)),\n" + + " b_dealman tinyint NOT NULL DEFAULT ((0)),\n" + + " b_receivelevel tinyint NULL,\n" + + " b_paylevel tinyint NULL,\n" + + " b_visible_customer tinyint NOT NULL DEFAULT ((0)),\n" + + " b_visible_supply tinyint NOT NULL DEFAULT ((0)),\n" + + " b_levelvisible tinyint NOT NULL DEFAULT ((0)),\n" + + " b_fromcustomer tinyint NOT NULL DEFAULT ((0)),\n" + + " b_logintime tinyint NOT NULL DEFAULT ((0)),\n" + + " b_checkxy tinyint NOT NULL DEFAULT ((0)),\n" + + " b_loginfee tinyint NOT NULL DEFAULT ((0)),\n" + + " b_phone nvarchar(50) NULL,\n" + + " CONSTRAINT [PK_b_user] PRIMARY KEY (b_id),\n" + + " CONSTRAINT [UQ_b_user_account] UNIQUE (b_account)\n" + + ");"; + run(conn, create); + + // 3. copy back 001/002 (without b_id, identity assigns 1,2) + String cols = getColsExcept(conn, "b_user_keep", "b_id"); + run(conn, "INSERT INTO dbo.b_user (" + cols + ") SELECT " + cols + " FROM dbo.b_user_keep ORDER BY b_id"); + + // 4. drop temp + run(conn, "DROP TABLE dbo.b_user_keep"); + + conn.commit(); + System.out.println("COMMITTED."); + + // verify + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT c.column_id, c.name, c.is_identity FROM sys.columns c WHERE c.object_id=OBJECT_ID('b_user') ORDER BY c.column_id")) { + System.out.println("== column order =="); + while (rs.next()) System.out.printf(" [%d] %-22s identity=%s%n", rs.getInt("column_id"), rs.getString("name"), rs.getBoolean("is_identity")); + } + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT b_id, b_account, b_name, b_password, b_canuse FROM b_user ORDER BY b_id")) { + System.out.println("== data =="); + while (rs.next()) System.out.printf(" b_id=%s b_account=%s b_name=%s b_password=%s b_canuse=%s%n", + rs.getString("b_id"), rs.getString("b_account"), rs.getString("b_name"), rs.getString("b_password"), rs.getString("b_canuse")); + } + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT name FROM sys.key_constraints WHERE parent_object_id=OBJECT_ID('b_user') ORDER BY name")) { + System.out.println("== constraints =="); + while (rs.next()) System.out.println(" " + rs.getString("name")); + } + } catch (SQLException e) { + System.err.println("ERROR: " + e.getMessage()); + throw e; + } + } + + static void run(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement()) { st.execute(sql); } + System.out.println("OK: " + sql.replaceAll("\\s+", " ").trim().substring(0, Math.min(90, sql.replaceAll("\\s+", " ").trim().length()))); + } + + static String getColsExcept(Connection conn, String table, String exclude) throws SQLException { + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT name FROM sys.columns WHERE object_id=OBJECT_ID('" + table + "') AND name <> '" + exclude + "' ORDER BY column_id")) { + java.util.List cols = new java.util.ArrayList<>(); + while (rs.next()) cols.add("[" + rs.getString("name") + "]"); + return String.join(", ", cols); + } + } +} diff --git a/code/fms/.tmp-fkcheck/BUserStruct.class b/code/fms/.tmp-fkcheck/BUserStruct.class new file mode 100644 index 00000000..aa1ddd21 Binary files /dev/null and b/code/fms/.tmp-fkcheck/BUserStruct.class differ diff --git a/code/fms/.tmp-fkcheck/BUserStruct.java b/code/fms/.tmp-fkcheck/BUserStruct.java new file mode 100644 index 00000000..0ccef22f --- /dev/null +++ b/code/fms/.tmp-fkcheck/BUserStruct.java @@ -0,0 +1,47 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class BUserStruct { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + System.out.println("== b_user column definitions =="); + String sql = + "SELECT c.name AS col, t.name AS type, c.max_length, c.precision, c.scale, c.is_nullable, " + + " c.is_identity, dc.definition AS default_def, c.column_id " + + "FROM sys.columns c " + + "JOIN sys.types t ON t.user_type_id = c.user_type_id " + + "LEFT JOIN sys.default_constraints dc ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id " + + "WHERE c.object_id = OBJECT_ID('b_user') ORDER BY c.column_id"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + String type = rs.getString("type"); + int maxLen = rs.getInt("max_length"); + String typeSql; + if (type.equals("nvarchar") || type.equals("nchar") || type.equals("varchar") || type.equals("char")) { + typeSql = type + (maxLen == -1 ? "(max)" : "(" + (maxLen / (type.startsWith("n") ? 2 : 1)) + ")"); + } else if (type.equals("decimal") || type.equals("numeric")) { + typeSql = type + "(" + rs.getInt("precision") + "," + rs.getInt("scale") + ")"; + } else { typeSql = type; } + System.out.printf(" col=%-22s %-16s null=%s identity=%s default=%s%n", + rs.getString("col"), typeSql, rs.getBoolean("is_nullable"), + rs.getBoolean("is_identity"), rs.getString("default_def")); + } + } + System.out.println("== b_user 001/002 data =="); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT * FROM b_user WHERE b_account IN ('001','002')")) { + ResultSetMetaData meta = rs.getMetaData(); + while (rs.next()) { + StringBuilder sb = new StringBuilder(); + for (int i = 1; i <= meta.getColumnCount(); i++) { + if (i > 1) sb.append(", "); + sb.append(meta.getColumnName(i)).append("=").append(rs.getObject(i)); + } + System.out.println(" " + sb); + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/DbState.class b/code/fms/.tmp-fkcheck/DbState.class new file mode 100644 index 00000000..a1bba244 Binary files /dev/null and b/code/fms/.tmp-fkcheck/DbState.class differ diff --git a/code/fms/.tmp-fkcheck/DbState.java b/code/fms/.tmp-fkcheck/DbState.java new file mode 100644 index 00000000..3e6d9ea9 --- /dev/null +++ b/code/fms/.tmp-fkcheck/DbState.java @@ -0,0 +1,36 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class DbState { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM sys.foreign_keys")) { + rs.next(); System.out.println("physical_fk_count=" + rs.getInt(1)); + } + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM sys.tables")) { + rs.next(); System.out.println("table_count=" + rs.getInt(1)); + } + // 元数据表 b_id 是否仍是 identity + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT OBJECT_NAME(ic.object_id) AS tbl, ic.name AS col, ic.seed_value, ic.increment_value " + + "FROM sys.identity_columns ic " + + "WHERE ic.name = 'b_id' ORDER BY tbl")) { + System.out.println("identity_b_id_columns (sample):"); + int n = 0; + while (rs.next() && n < 25) { n++; System.out.printf(" %-22s b_id identity(%s,%s)%n", rs.getString("tbl"), rs.getString("seed_value"), rs.getString("increment_value")); } + System.out.println(" ... total shown=" + n); + } + // 最近 30 天内是否新建/修改过表 + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT COUNT(*) FROM sys.tables t JOIN sys.objects o ON o.object_id = t.object_id " + + "WHERE o.modify_date > DATEADD(day, -30, GETUTCDATE())")) { + rs.next(); System.out.println("tables_modified_last_30d=" + rs.getInt(1)); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/DropFks$Fk.class b/code/fms/.tmp-fkcheck/DropFks$Fk.class new file mode 100644 index 00000000..88bc2548 Binary files /dev/null and b/code/fms/.tmp-fkcheck/DropFks$Fk.class differ diff --git a/code/fms/.tmp-fkcheck/DropFks.class b/code/fms/.tmp-fkcheck/DropFks.class new file mode 100644 index 00000000..48cee300 Binary files /dev/null and b/code/fms/.tmp-fkcheck/DropFks.class differ diff --git a/code/fms/.tmp-fkcheck/DropFks.java b/code/fms/.tmp-fkcheck/DropFks.java new file mode 100644 index 00000000..dc349529 --- /dev/null +++ b/code/fms/.tmp-fkcheck/DropFks.java @@ -0,0 +1,112 @@ +import java.io.FileInputStream; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class DropFks { + static class Fk { + String name, schema, parentTable, refTable, delAction, updAction; + List cols = new ArrayList<>(); // {parentCol, refCol} + } + + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + String url = props.getProperty("url"); + String user = props.getProperty("username"); + String pass = props.getProperty("password"); + + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + try (Connection conn = DriverManager.getConnection(url, user, pass)) { + conn.setAutoCommit(false); + List fks = new ArrayList<>(); + + String sql = + "SELECT fk.name AS fk_name, " + + "OBJECT_SCHEMA_NAME(fk.parent_object_id) AS sch, " + + "OBJECT_NAME(fk.parent_object_id) AS parent_tbl, " + + "OBJECT_NAME(fk.referenced_object_id) AS ref_tbl, " + + "fk.delete_referential_action_desc AS del, " + + "fk.update_referential_action_desc AS upd " + + "FROM sys.foreign_keys fk ORDER BY fk.name"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + Fk f = new Fk(); + f.name = rs.getString("fk_name"); + f.schema = rs.getString("sch"); + f.parentTable = rs.getString("parent_tbl"); + f.refTable = rs.getString("ref_tbl"); + f.delAction = rs.getString("del"); + f.updAction = rs.getString("upd"); + fks.add(f); + } + } + String colSql = + "SELECT fk.name AS fk_name, " + + "COL_NAME(fkc.parent_object_id, fkc.parent_column_id) AS pc, " + + "COL_NAME(fkc.referenced_object_id, fkc.referenced_column_id) AS rc " + + "FROM sys.foreign_keys fk " + + "JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id " + + "ORDER BY fk.name, fkc.constraint_column_id"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(colSql)) { + while (rs.next()) { + String n = rs.getString("fk_name"); + for (Fk f : fks) { + if (f.name.equals(n)) { + f.cols.add(new String[]{rs.getString("pc"), rs.getString("rc")}); + break; + } + } + } + } + + // 1. Write restore script + String restorePath = "restore_fks.sql"; + try (PrintWriter w = new PrintWriter(restorePath, StandardCharsets.UTF_8)) { + w.println("-- Restore script for G3HD (FMS-NEW) physical foreign keys"); + w.println("-- Generated: " + new Timestamp(System.currentTimeMillis())); + w.println("USE [FMS-NEW];"); + w.println("GO"); + for (Fk f : fks) { + StringBuilder pcols = new StringBuilder(), rcols = new StringBuilder(); + for (int i = 0; i < f.cols.size(); i++) { + if (i > 0) { pcols.append(", "); rcols.append(", "); } + pcols.append("[").append(f.cols.get(i)[0]).append("]"); + rcols.append("[").append(f.cols.get(i)[1]).append("]"); + } + w.printf("ALTER TABLE [%s].[%s] WITH CHECK ADD CONSTRAINT [%s] FOREIGN KEY(%s)%n", + f.schema, f.parentTable, f.name, pcols); + w.printf("REFERENCES [%s].[%s] (%s) ON DELETE %s ON UPDATE %s;%n", + f.schema, f.refTable, rcols, f.delAction, f.updAction); + w.println("GO"); + } + w.println("-- End of restore script"); + } + System.out.println("Restore script written to: " + restorePath + " (" + fks.size() + " FKs)"); + + // 2. Drop all FKs in one transaction + int dropped = 0; + for (Fk f : fks) { + String ddl = String.format("ALTER TABLE [%s].[%s] DROP CONSTRAINT [%s]", + f.schema, f.parentTable, f.name); + try (Statement st = conn.createStatement()) { + st.executeUpdate(ddl); + dropped++; + System.out.println("DROPPED: " + f.name + " (on " + f.parentTable + ")"); + } + } + conn.commit(); + System.out.println("\nCommitted. Dropped " + dropped + " foreign keys."); + + // 3. Verify + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM sys.foreign_keys")) { + rs.next(); + System.out.println("REMAINING FK COUNT = " + rs.getInt(1)); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/DropOrgId.class b/code/fms/.tmp-fkcheck/DropOrgId.class new file mode 100644 index 00000000..1f5e92c0 Binary files /dev/null and b/code/fms/.tmp-fkcheck/DropOrgId.class differ diff --git a/code/fms/.tmp-fkcheck/DropOrgId.java b/code/fms/.tmp-fkcheck/DropOrgId.java new file mode 100644 index 00000000..d7ecba00 --- /dev/null +++ b/code/fms/.tmp-fkcheck/DropOrgId.java @@ -0,0 +1,47 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class DropOrgId { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + conn.setAutoCommit(false); + // drop indexes referencing b_org_id on s_log_technical + String idxSql = "SELECT i.name FROM sys.indexes i " + + "JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id " + + "JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id " + + "WHERE i.object_id = OBJECT_ID('s_log_technical') AND c.name = 'b_org_id'"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(idxSql)) { + while (rs.next()) { + String idx = rs.getString(1); + st.execute("DROP INDEX [" + idx + "] ON dbo.s_log_technical"); + System.out.println("OK: DROP INDEX " + idx); + } + } + // drop columns + for (String tbl : new String[]{"s_log_technical", "s_log_login"}) { + try (Statement st = conn.createStatement()) { + st.execute("ALTER TABLE dbo." + tbl + " DROP COLUMN b_org_id"); + System.out.println("OK: ALTER TABLE " + tbl + " DROP COLUMN b_org_id"); + } + } + conn.commit(); + System.out.println("COMMITTED."); + // verify + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT t.name, c.name FROM sys.tables t JOIN sys.columns c ON c.object_id = t.object_id " + + "WHERE c.name = 'b_org_id' ORDER BY t.name")) { + boolean any = false; + while (rs.next()) { any = true; System.out.println("STILL EXISTS: " + rs.getString(1) + "." + rs.getString(2)); } + if (!any) System.out.println("VERIFY: no b_org_id column remains in any table"); + } + // verify views still valid + for (String v : new String[]{"v_s_log_login", "v_s_log_technical"}) { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT TOP 1 1 FROM " + v)) { + System.out.println("VIEW OK: " + v + " (queryable)"); + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/DropOrgId2.class b/code/fms/.tmp-fkcheck/DropOrgId2.class new file mode 100644 index 00000000..aace8946 Binary files /dev/null and b/code/fms/.tmp-fkcheck/DropOrgId2.class differ diff --git a/code/fms/.tmp-fkcheck/DropOrgId2.java b/code/fms/.tmp-fkcheck/DropOrgId2.java new file mode 100644 index 00000000..ecf1a5f2 --- /dev/null +++ b/code/fms/.tmp-fkcheck/DropOrgId2.java @@ -0,0 +1,49 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +public class DropOrgId2 { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + conn.setAutoCommit(false); + // collect index names first + List idxs = new ArrayList<>(); + String idxSql = "SELECT i.name FROM sys.indexes i " + + "JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id " + + "JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id " + + "WHERE i.object_id = OBJECT_ID('s_log_technical') AND c.name = 'b_org_id'"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(idxSql)) { + while (rs.next()) idxs.add(rs.getString(1)); + } + for (String idx : idxs) { + try (Statement st = conn.createStatement()) { + st.execute("DROP INDEX [" + idx + "] ON dbo.s_log_technical"); + System.out.println("OK: DROP INDEX " + idx); + } + } + for (String tbl : new String[]{"s_log_technical", "s_log_login"}) { + try (Statement st = conn.createStatement()) { + st.execute("ALTER TABLE dbo." + tbl + " DROP COLUMN b_org_id"); + System.out.println("OK: ALTER TABLE " + tbl + " DROP COLUMN b_org_id"); + } + } + conn.commit(); + System.out.println("COMMITTED."); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT t.name, c.name FROM sys.tables t JOIN sys.columns c ON c.object_id = t.object_id " + + "WHERE c.name = 'b_org_id' ORDER BY t.name")) { + boolean any = false; + while (rs.next()) { any = true; System.out.println("STILL EXISTS: " + rs.getString(1) + "." + rs.getString(2)); } + if (!any) System.out.println("VERIFY: no b_org_id column remains in any table"); + } + for (String v : new String[]{"v_s_log_login", "v_s_log_technical"}) { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT TOP 1 1 FROM " + v)) { + System.out.println("VIEW OK: " + v + " (queryable)"); + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/FkCheck.class b/code/fms/.tmp-fkcheck/FkCheck.class new file mode 100644 index 00000000..d1c5702c Binary files /dev/null and b/code/fms/.tmp-fkcheck/FkCheck.class differ diff --git a/code/fms/.tmp-fkcheck/FkCheck.java b/code/fms/.tmp-fkcheck/FkCheck.java new file mode 100644 index 00000000..ed67e955 --- /dev/null +++ b/code/fms/.tmp-fkcheck/FkCheck.java @@ -0,0 +1,85 @@ +import java.io.FileInputStream; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Properties; + +public class FkCheck { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { + props.load(in); + } + String url = props.getProperty("url"); + String user = props.getProperty("username"); + String pass = props.getProperty("password"); + + System.out.println("Connecting to: " + url); + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + try (Connection conn = DriverManager.getConnection(url, user, pass)) { + System.out.println("CONNECTED: " + conn.getMetaData().getDatabaseProductName() + + " " + conn.getMetaData().getDatabaseProductVersion()); + + // 1. Total FK count + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM sys.foreign_keys")) { + rs.next(); + System.out.println("TOTAL_FK_COUNT=" + rs.getInt(1)); + } + + // 2. FK status summary (disabled / not trusted / not for replication) + System.out.println("--- FK STATUS ---"); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT name, is_disabled, is_not_trusted, is_not_for_replication, create_date " + + "FROM sys.foreign_keys ORDER BY name")) { + while (rs.next()) { + System.out.printf("FK=%s disabled=%s not_trusted=%s not_for_repl=%s created=%s%n", + rs.getString("name"), rs.getBoolean("is_disabled"), + rs.getBoolean("is_not_trusted"), rs.getBoolean("is_not_for_replication"), + rs.getTimestamp("create_date")); + } + } + + // 3. FK delete/update actions + System.out.println("--- FK ACTIONS ---"); + String actSql = + "SELECT fk.name, OBJECT_NAME(fk.parent_object_id) AS parent_table, " + + " fk.delete_referential_action_desc AS del_action, " + + " fk.update_referential_action_desc AS upd_action " + + "FROM sys.foreign_keys fk ORDER BY parent_table, fk.name"; + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery(actSql)) { + while (rs.next()) { + System.out.printf("%s (%s): ON DELETE %s, ON UPDATE %s%n", + rs.getString("name"), rs.getString("parent_table"), + rs.getString("del_action"), rs.getString("upd_action")); + } + } + + // 4. FK details: parent/child columns + System.out.println("--- FK DETAILS ---"); + String sql = + "SELECT fk.name AS fk_name, " + + " OBJECT_NAME(fk.parent_object_id) AS parent_table, " + + " OBJECT_NAME(fk.referenced_object_id) AS ref_table, " + + " pc.name AS parent_col, rc.name AS ref_col, fkc.constraint_column_id AS ord " + + "FROM sys.foreign_keys fk " + + "JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id " + + "JOIN sys.columns pc ON pc.object_id = fk.parent_object_id AND pc.column_id = fkc.parent_column_id " + + "JOIN sys.columns rc ON rc.object_id = fk.referenced_object_id AND rc.column_id = fkc.referenced_column_id " + + "ORDER BY parent_table, fk.name, ord"; + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + System.out.printf("%s: %s(%s) -> %s(%s)%n", + rs.getString("fk_name"), rs.getString("parent_table"), + rs.getString("parent_col"), rs.getString("ref_table"), + rs.getString("ref_col")); + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/FkCount.class b/code/fms/.tmp-fkcheck/FkCount.class new file mode 100644 index 00000000..323fd5f2 Binary files /dev/null and b/code/fms/.tmp-fkcheck/FkCount.class differ diff --git a/code/fms/.tmp-fkcheck/FkCount.java b/code/fms/.tmp-fkcheck/FkCount.java new file mode 100644 index 00000000..ba2b4943 --- /dev/null +++ b/code/fms/.tmp-fkcheck/FkCount.java @@ -0,0 +1,14 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class FkCount { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password")); + Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM sys.foreign_keys")) { + rs.next(); System.out.println("physical_fk_count=" + rs.getInt(1)); + } + } +} diff --git a/code/fms/.tmp-fkcheck/FkPairs.class b/code/fms/.tmp-fkcheck/FkPairs.class new file mode 100644 index 00000000..57c56f92 Binary files /dev/null and b/code/fms/.tmp-fkcheck/FkPairs.class differ diff --git a/code/fms/.tmp-fkcheck/FkPairs.java b/code/fms/.tmp-fkcheck/FkPairs.java new file mode 100644 index 00000000..4e35c3a4 --- /dev/null +++ b/code/fms/.tmp-fkcheck/FkPairs.java @@ -0,0 +1,39 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class FkPairs { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + String sql = + "SELECT OBJECT_NAME(fk.parent_object_id) AS parent_table, " + + " OBJECT_NAME(fk.referenced_object_id) AS ref_table, " + + " fk.name AS fk_name, " + + " STRING_AGG(pc.name + ' -> ' + rc.name, ', ') WITHIN GROUP (ORDER BY fkc.constraint_column_id) AS cols " + + "FROM sys.foreign_keys fk " + + "JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id " + + "JOIN sys.columns pc ON pc.object_id = fk.parent_object_id AND pc.column_id = fkc.parent_column_id " + + "JOIN sys.columns rc ON rc.object_id = fk.referenced_object_id AND rc.column_id = fkc.referenced_column_id " + + "WHERE fk.parent_object_id <> fk.referenced_object_id " + + "GROUP BY fk.name, fk.parent_object_id, fk.referenced_object_id " + + "ORDER BY parent_table, fk_name"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + System.out.println("=== CROSS-TABLE FK ASSOCIATIONS (two tables linked) ==="); + String curPair = ""; + int n = 0; + while (rs.next()) { + String pair = rs.getString("parent_table") + " -> " + rs.getString("ref_table"); + if (!pair.equals(curPair)) { + curPair = pair; + n++; + System.out.println(); + System.out.println("[" + n + "] " + pair); + } + System.out.println(" FK " + rs.getString("fk_name") + " ( " + rs.getString("cols") + " )"); + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/FkSummary.class b/code/fms/.tmp-fkcheck/FkSummary.class new file mode 100644 index 00000000..f329f3c9 Binary files /dev/null and b/code/fms/.tmp-fkcheck/FkSummary.class differ diff --git a/code/fms/.tmp-fkcheck/FkSummary.java b/code/fms/.tmp-fkcheck/FkSummary.java new file mode 100644 index 00000000..e4192472 --- /dev/null +++ b/code/fms/.tmp-fkcheck/FkSummary.java @@ -0,0 +1,44 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class FkSummary { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + System.out.println("== FK count by parent table =="); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT OBJECT_NAME(parent_object_id) AS tbl, COUNT(*) AS fk_cnt " + + "FROM sys.foreign_keys GROUP BY parent_object_id ORDER BY tbl")) { + while (rs.next()) System.out.printf("%-22s %d%n", rs.getString("tbl"), rs.getInt("fk_cnt")); + } + System.out.println("== tables WITH FKs: " + "see above" + " =="); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT COUNT(DISTINCT parent_object_id) FROM sys.foreign_keys")) { + rs.next(); System.out.println("tables_with_fk=" + rs.getInt(1)); + } + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM sys.tables")) { + rs.next(); System.out.println("total_tables=" + rs.getInt(1)); + } + // self-referencing FKs + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT name FROM sys.foreign_keys WHERE parent_object_id = referenced_object_id")) { + System.out.println("== self-referencing FKs =="); + while (rs.next()) System.out.println(" " + rs.getString("name")); + } + // composite FKs (more than one column) + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT fk.name, COUNT(*) AS cols FROM sys.foreign_keys fk " + + "JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id " + + "GROUP BY fk.name HAVING COUNT(*) > 1")) { + System.out.println("== composite FKs (multi-column) =="); + while (rs.next()) System.out.printf(" %s (%d cols)%n", rs.getString("name"), rs.getInt("cols")); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/G3SoftCheck.class b/code/fms/.tmp-fkcheck/G3SoftCheck.class new file mode 100644 index 00000000..03005705 Binary files /dev/null and b/code/fms/.tmp-fkcheck/G3SoftCheck.class differ diff --git a/code/fms/.tmp-fkcheck/G3SoftCheck.java b/code/fms/.tmp-fkcheck/G3SoftCheck.java new file mode 100644 index 00000000..5c38d853 --- /dev/null +++ b/code/fms/.tmp-fkcheck/G3SoftCheck.java @@ -0,0 +1,30 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class G3SoftCheck { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + System.out.println("== objects mentioning g3soft / session_context =="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT o.type_desc, OBJECT_SCHEMA_NAME(o.object_id) AS sch, o.name, m.definition " + + "FROM sys.sql_modules m JOIN sys.objects o ON o.object_id = m.object_id " + + "WHERE m.definition LIKE '%g3soft%' OR m.definition LIKE '%session_context%' OR m.definition LIKE '%allow_g3soft%'")) { + boolean any = false; + while (rs.next()) { any = true; System.out.printf(" %-20s %s.%s%n def: %s%n%n", rs.getString("type_desc"), rs.getString("sch"), rs.getString("name"), rs.getString("definition")); } + if (!any) System.out.println(" (none)"); + } + System.out.println("== views / security policies on b_user =="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT o.type_desc, OBJECT_SCHEMA_NAME(o.object_id) AS sch, o.name FROM sys.objects o " + + "WHERE o.name LIKE '%b_user%' OR o.name LIKE '%g3soft%' OR o.type = 'S' ORDER BY o.type, o.name")) { + while (rs.next()) System.out.printf(" %-20s %s.%s%n", rs.getString("type_desc"), rs.getString("sch"), rs.getString("name")); + } + System.out.println("== b_user current data =="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT b_id, b_account, b_canuse FROM b_user")) { + while (rs.next()) System.out.printf(" b_id=%s b_account=%s b_canuse=%s%n", rs.getString("b_id"), rs.getString("b_account"), rs.getString("b_canuse")); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/G3SoftSetup.class b/code/fms/.tmp-fkcheck/G3SoftSetup.class new file mode 100644 index 00000000..da2b92ce Binary files /dev/null and b/code/fms/.tmp-fkcheck/G3SoftSetup.class differ diff --git a/code/fms/.tmp-fkcheck/G3SoftSetup.java b/code/fms/.tmp-fkcheck/G3SoftSetup.java new file mode 100644 index 00000000..21c55aee --- /dev/null +++ b/code/fms/.tmp-fkcheck/G3SoftSetup.java @@ -0,0 +1,81 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class G3SoftSetup { + static final String ALLOW = "ISNULL(CONVERT(int, SESSION_CONTEXT(N'allow_g3soft')), 0) = 1"; + + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + conn.setAutoCommit(false); + + // 1. add b_is_system marker + run(conn, "IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=OBJECT_ID('b_user') AND name='b_is_system') " + + "ALTER TABLE dbo.b_user ADD b_is_system int NOT NULL DEFAULT 0"); + + // 2. insert g3soft account (idempotent) + run(conn, "IF NOT EXISTS (SELECT 1 FROM b_user WHERE b_account = N'g3soft') " + + "INSERT INTO dbo.b_user (b_account, b_name, b_password, b_canuse, b_is_system) " + + "VALUES (N'g3soft', N'超级管理员', N'g3soft@2026', 1, 1)"); + + // 3. user filter view + String vUser = "CREATE VIEW dbo.v_b_user AS SELECT * FROM dbo.b_user WHERE b_is_system = 0 OR " + ALLOW; + run(conn, "IF OBJECT_ID('dbo.v_b_user') IS NOT NULL DROP VIEW dbo.v_b_user"); + run(conn, vUser); + + // 4. log filter views + String vLogin = "CREATE VIEW dbo.v_s_log_login AS SELECT * FROM dbo.s_log_login WHERE b_account <> N'g3soft' OR " + ALLOW; + run(conn, "IF OBJECT_ID('dbo.v_s_log_login') IS NOT NULL DROP VIEW dbo.v_s_log_login"); + run(conn, vLogin); + + String vTech = "CREATE VIEW dbo.v_s_log_technical AS SELECT * FROM dbo.s_log_technical WHERE b_account <> N'g3soft' OR " + ALLOW; + run(conn, "IF OBJECT_ID('dbo.v_s_log_technical') IS NOT NULL DROP VIEW dbo.v_s_log_technical"); + run(conn, vTech); + + String vAudit = "CREATE VIEW dbo.v_s_log_audit AS SELECT * FROM dbo.s_log_audit WHERE b_operator_id IN (SELECT b_id FROM dbo.v_b_user) OR " + ALLOW; + run(conn, "IF OBJECT_ID('dbo.v_s_log_audit') IS NOT NULL DROP VIEW dbo.v_s_log_audit"); + run(conn, vAudit); + + conn.commit(); + System.out.println("COMMITTED."); + + // verify + System.out.println("\n== b_user data =="); + dump(conn, "SELECT b_id, b_account, b_name, b_canuse, b_is_system FROM b_user ORDER BY b_id"); + System.out.println("\n== v_b_user WITHOUT allow_g3soft (should hide g3soft) =="); + dump(conn, "SELECT b_id, b_account, b_is_system FROM v_b_user ORDER BY b_id"); + System.out.println("\n== v_b_user WITH allow_g3soft=1 (should show all) =="); + try (Statement st = conn.createStatement()) { + st.execute("EXEC sys.sp_set_session_context @key = N'allow_g3soft', @value = 1"); + } + dump(conn, "SELECT b_id, b_account, b_is_system FROM v_b_user ORDER BY b_id"); + try (Statement st = conn.createStatement()) { + st.execute("EXEC sys.sp_set_session_context @key = N'allow_g3soft', @value = NULL"); + } + System.out.println("\n== views created =="); + dump(conn, "SELECT name FROM sys.views WHERE name LIKE 'v_%' ORDER BY name"); + } + } + + static void run(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement()) { st.execute(sql); } + System.out.println("OK: " + sql.replaceAll("\\s+", " ").trim().substring(0, Math.min(100, sql.replaceAll("\\s+", " ").trim().length()))); + } + + static void dump(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + ResultSetMetaData m = rs.getMetaData(); + while (rs.next()) { + StringBuilder sb = new StringBuilder(" "); + for (int i = 1; i <= m.getColumnCount(); i++) { + if (i > 1) sb.append(" | "); + sb.append(m.getColumnName(i)).append("=").append(rs.getObject(i)); + } + System.out.println(sb); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/IndexSurvey.class b/code/fms/.tmp-fkcheck/IndexSurvey.class new file mode 100644 index 00000000..79485163 Binary files /dev/null and b/code/fms/.tmp-fkcheck/IndexSurvey.class differ diff --git a/code/fms/.tmp-fkcheck/IndexSurvey.java b/code/fms/.tmp-fkcheck/IndexSurvey.java new file mode 100644 index 00000000..15d8a87d --- /dev/null +++ b/code/fms/.tmp-fkcheck/IndexSurvey.java @@ -0,0 +1,29 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class IndexSurvey { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + String sql = + "SELECT OBJECT_NAME(i.object_id) AS tbl, i.name AS idx, i.type_desc AS itype, " + + " i.is_unique, i.is_primary_key, " + + " STUFF((SELECT ',' + c.name FROM sys.index_columns ic " + + " JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id " + + " WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id " + + " ORDER BY ic.key_ordinal FOR XML PATH('')), 1, 1, '') AS cols " + + "FROM sys.indexes i " + + "WHERE i.object_id IN (SELECT object_id FROM sys.tables WHERE name LIKE 's' + char(95) + '%') " + + " AND i.type IN (1,2) AND i.is_disabled = 0 " + + "ORDER BY tbl, i.index_id"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + System.out.printf("%-22s %-32s %-12s unique=%s pk=%s cols=%s%n", + rs.getString("tbl"), rs.getString("idx"), rs.getString("itype"), + rs.getBoolean("is_unique"), rs.getBoolean("is_primary_key"), rs.getString("cols")); + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/LogCols.class b/code/fms/.tmp-fkcheck/LogCols.class new file mode 100644 index 00000000..a99fc5aa Binary files /dev/null and b/code/fms/.tmp-fkcheck/LogCols.class differ diff --git a/code/fms/.tmp-fkcheck/LogCols.java b/code/fms/.tmp-fkcheck/LogCols.java new file mode 100644 index 00000000..2c3b454f --- /dev/null +++ b/code/fms/.tmp-fkcheck/LogCols.java @@ -0,0 +1,20 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class LogCols { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + for (String t : new String[]{"s_log_login", "s_log_technical", "s_log_audit"}) { + System.out.println("== " + t + " =="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT c.name FROM sys.columns c WHERE c.object_id=OBJECT_ID('" + t + "') ORDER BY c.column_id")) { + StringBuilder sb = new StringBuilder(" "); + while (rs.next()) sb.append(rs.getString(1)).append(" "); + System.out.println(sb); + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/LogFilterTest.class b/code/fms/.tmp-fkcheck/LogFilterTest.class new file mode 100644 index 00000000..4cb0df02 Binary files /dev/null and b/code/fms/.tmp-fkcheck/LogFilterTest.class differ diff --git a/code/fms/.tmp-fkcheck/LogFilterTest.java b/code/fms/.tmp-fkcheck/LogFilterTest.java new file mode 100644 index 00000000..e8167a3b --- /dev/null +++ b/code/fms/.tmp-fkcheck/LogFilterTest.java @@ -0,0 +1,43 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class LogFilterTest { + static final String ALLOW = "ISNULL(CONVERT(int, SESSION_CONTEXT(N'allow_g3soft')), 0) = 1"; + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + // insert two test login log rows: one g3soft, one 001 + try (Statement st = conn.createStatement()) { + st.execute("INSERT INTO s_log_login (b_id, b_org_id, b_user_id, b_account, b_login_type, b_login_result) VALUES (900000000000000001, N'G3HD', 3, N'g3soft', N'password', 1)"); + st.execute("INSERT INTO s_log_login (b_id, b_org_id, b_user_id, b_account, b_login_type, b_login_result) VALUES (900000000000000002, N'G3HD', 1, N'001', N'password', 1)"); + } + System.out.println("== v_s_log_login WITHOUT ctx (expect only 001) =="); + dump(conn, "SELECT b_id, b_account FROM v_s_log_login ORDER BY b_id"); + try (Statement st = conn.createStatement()) { st.execute("EXEC sys.sp_set_session_context @key = N'allow_g3soft', @value = 1"); } + System.out.println("== v_s_log_login WITH ctx=1 (expect both) =="); + dump(conn, "SELECT b_id, b_account FROM v_s_log_login ORDER BY b_id"); + try (Statement st = conn.createStatement()) { st.execute("EXEC sys.sp_set_session_context @key = N'allow_g3soft', @value = NULL"); } + // cleanup + try (Statement st = conn.createStatement()) { + st.execute("DELETE FROM s_log_login WHERE b_id IN (900000000000000001, 900000000000000002)"); + } + System.out.println("== cleaned =="); + dump(conn, "SELECT COUNT(*) AS cnt FROM s_log_login"); + } + } + static void dump(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + ResultSetMetaData m = rs.getMetaData(); + while (rs.next()) { + StringBuilder sb = new StringBuilder(" "); + for (int i = 1; i <= m.getColumnCount(); i++) { + if (i > 1) sb.append(" | "); + sb.append(m.getColumnName(i)).append("=").append(rs.getObject(i)); + } + System.out.println(sb); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/LogPkShow.class b/code/fms/.tmp-fkcheck/LogPkShow.class new file mode 100644 index 00000000..71c1d3f1 Binary files /dev/null and b/code/fms/.tmp-fkcheck/LogPkShow.class differ diff --git a/code/fms/.tmp-fkcheck/LogPkShow.java b/code/fms/.tmp-fkcheck/LogPkShow.java new file mode 100644 index 00000000..60cc2ca7 --- /dev/null +++ b/code/fms/.tmp-fkcheck/LogPkShow.java @@ -0,0 +1,30 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class LogPkShow { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + for (String t : new String[]{"s_log_login", "s_log_technical", "s_log_audit", "s_log_audit_field"}) { + System.out.println("== " + t + " =="); + // PK columns + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT kc.name AS pk, c.name AS col FROM sys.key_constraints kc " + + "JOIN sys.index_columns ic ON ic.object_id = kc.parent_object_id AND ic.index_id = kc.unique_index_id " + + "JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id " + + "WHERE kc.type = 'PK' AND kc.parent_object_id = OBJECT_ID('" + t + "') ORDER BY ic.key_ordinal")) { + if (rs.next()) System.out.printf(" PK: %s -> column: %s%n", rs.getString("pk"), rs.getString("col")); + else System.out.println(" PK: NONE !!"); + } + // all columns + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT c.name, c.is_identity FROM sys.columns c WHERE c.object_id=OBJECT_ID('" + t + "') ORDER BY c.column_id")) { + StringBuilder sb = new StringBuilder(" cols: "); + while (rs.next()) sb.append(rs.getString(1)).append(rs.getBoolean("is_identity") ? "(identity) " : " "); + System.out.println(sb); + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/LogRebuild.class b/code/fms/.tmp-fkcheck/LogRebuild.class new file mode 100644 index 00000000..7a85ec85 Binary files /dev/null and b/code/fms/.tmp-fkcheck/LogRebuild.class differ diff --git a/code/fms/.tmp-fkcheck/LogRebuild.java b/code/fms/.tmp-fkcheck/LogRebuild.java new file mode 100644 index 00000000..911db629 --- /dev/null +++ b/code/fms/.tmp-fkcheck/LogRebuild.java @@ -0,0 +1,150 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class LogRebuild { + static final String ALLOW = "ISNULL(CONVERT(int, SESSION_CONTEXT(N'allow_g3soft')), 0) = 1"; + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + conn.setAutoCommit(false); + + // 1. drop views referencing log tables + run(conn, "IF OBJECT_ID('dbo.v_s_log_audit') IS NOT NULL DROP VIEW dbo.v_s_log_audit"); + run(conn, "IF OBJECT_ID('dbo.v_s_log_technical') IS NOT NULL DROP VIEW dbo.v_s_log_technical"); + run(conn, "IF OBJECT_ID('dbo.v_s_log_login') IS NOT NULL DROP VIEW dbo.v_s_log_login"); + + // 2. drop tables + run(conn, "DROP TABLE dbo.s_log_audit_field"); + run(conn, "DROP TABLE dbo.s_log_audit"); + run(conn, "DROP TABLE dbo.s_log_technical"); + run(conn, "DROP TABLE dbo.s_log_login"); + + // 3. recreate with b_id first + run(conn, + "CREATE TABLE dbo.s_log_login (\n" + + " b_id bigint NOT NULL,\n" + + " b_user_id bigint NULL,\n" + + " b_account varchar(50) NULL,\n" + + " b_login_type varchar(20) NOT NULL,\n" + + " b_login_result int NOT NULL CONSTRAINT DF_s_log_login_result DEFAULT (0),\n" + + " b_fail_reason nvarchar(200) NULL,\n" + + " b_ip varchar(50) NULL,\n" + + " b_ip_location nvarchar(200) NULL,\n" + + " b_browser varchar(100) NULL,\n" + + " b_browser_version varchar(50) NULL,\n" + + " b_os varchar(100) NULL,\n" + + " b_os_version varchar(50) NULL,\n" + + " b_device_type varchar(20) NULL,\n" + + " b_user_agent nvarchar(max) NULL,\n" + + " b_session_id varchar(50) NULL,\n" + + " b_request_id varchar(64) NULL,\n" + + " b_trace_id varchar(64) NULL,\n" + + " b_occurdatetime datetime2 NULL CONSTRAINT DF_s_log_login_occur DEFAULT (sysutcdatetime()),\n" + + " CONSTRAINT PK_s_log_login PRIMARY KEY (b_id)\n" + + ");"); + run(conn, "CREATE INDEX IX_s_log_login_user ON dbo.s_log_login (b_user_id, b_occurdatetime DESC)"); + run(conn, "CREATE INDEX IX_s_log_login_account ON dbo.s_log_login (b_account, b_occurdatetime DESC)"); + run(conn, "CREATE INDEX IX_s_log_login_result ON dbo.s_log_login (b_login_result, b_occurdatetime DESC)"); + + run(conn, + "CREATE TABLE dbo.s_log_technical (\n" + + " b_id bigint NOT NULL,\n" + + " b_user_id bigint NULL,\n" + + " b_account varchar(50) NULL,\n" + + " b_sourcetype varchar(20) NULL,\n" + + " b_loglevel varchar(16) NOT NULL,\n" + + " b_logcategory varchar(64) NULL,\n" + + " b_operation varchar(100) NULL,\n" + + " b_module_code varchar(50) NULL,\n" + + " b_request_id varchar(64) NULL,\n" + + " b_trace_id varchar(64) NULL,\n" + + " b_server_node varchar(128) NULL,\n" + + " b_duration_ms bigint NULL,\n" + + " b_result_code varchar(50) NULL,\n" + + " b_error_code varchar(50) NULL,\n" + + " b_error_message nvarchar(2000) NULL,\n" + + " b_exception_text nvarchar(max) NULL,\n" + + " b_context_text nvarchar(max) NULL,\n" + + " b_occurdatetime datetime2 NULL CONSTRAINT DF_s_log_technical_occur DEFAULT (sysutcdatetime()),\n" + + " CONSTRAINT PK_s_log_technical PRIMARY KEY (b_id)\n" + + ");"); + run(conn, "CREATE INDEX IX_s_log_technical_trace ON dbo.s_log_technical (b_trace_id, b_occurdatetime DESC)"); + run(conn, "CREATE INDEX IX_s_log_technical_level ON dbo.s_log_technical (b_loglevel, b_occurdatetime DESC)"); + + run(conn, + "CREATE TABLE dbo.s_log_audit (\n" + + " b_id bigint NOT NULL,\n" + + " b_event_code varchar(50) NOT NULL,\n" + + " b_module_code varchar(50) NULL,\n" + + " b_data_id varchar(50) NULL,\n" + + " b_business_no varchar(50) NULL,\n" + + " b_operation varchar(100) NULL,\n" + + " b_operator_id bigint NULL,\n" + + " b_operator_name nvarchar(100) NULL,\n" + + " b_source_type varchar(20) NOT NULL CONSTRAINT DF_s_log_audit_source DEFAULT ('user'),\n" + + " b_visibility varchar(20) NOT NULL CONSTRAINT DF_s_log_audit_visibility DEFAULT ('internal'),\n" + + " b_request_id varchar(64) NULL,\n" + + " b_trace_id varchar(64) NULL,\n" + + " b_payload_text nvarchar(max) NULL,\n" + + " b_occurdatetime datetime2 NULL CONSTRAINT DF_s_log_audit_occur DEFAULT (sysutcdatetime()),\n" + + " CONSTRAINT PK_s_log_audit PRIMARY KEY (b_id)\n" + + ");"); + run(conn, "CREATE INDEX IX_s_log_audit_module ON dbo.s_log_audit (b_module_code, b_data_id, b_occurdatetime DESC)"); + run(conn, "CREATE INDEX IX_s_log_audit_trace ON dbo.s_log_audit (b_trace_id)"); + + run(conn, + "CREATE TABLE dbo.s_log_audit_field (\n" + + " b_id bigint NOT NULL,\n" + + " b_event_id bigint NOT NULL,\n" + + " b_module_code varchar(50) NULL,\n" + + " b_data_id varchar(50) NULL,\n" + + " b_field varchar(50) NOT NULL,\n" + + " b_field_label nvarchar(100) NULL,\n" + + " b_before_value nvarchar(max) NULL,\n" + + " b_after_value nvarchar(max) NULL,\n" + + " b_before_display nvarchar(500) NULL,\n" + + " b_after_display nvarchar(500) NULL,\n" + + " b_visibility varchar(20) NOT NULL CONSTRAINT DF_s_log_audit_field_visibility DEFAULT ('internal'),\n" + + " CONSTRAINT PK_s_log_audit_field PRIMARY KEY (b_id)\n" + + ");"); + run(conn, "CREATE INDEX IX_s_log_audit_field_event ON dbo.s_log_audit_field (b_event_id)"); + run(conn, "CREATE INDEX IX_s_log_audit_field_customer ON dbo.s_log_audit_field (b_module_code, b_data_id, b_visibility, b_event_id)"); + + // 4. recreate views + run(conn, "CREATE VIEW dbo.v_s_log_login AS SELECT * FROM dbo.s_log_login WHERE b_account <> N'g3soft' OR " + ALLOW); + run(conn, "CREATE VIEW dbo.v_s_log_technical AS SELECT * FROM dbo.s_log_technical WHERE b_account <> N'g3soft' OR " + ALLOW); + run(conn, "CREATE VIEW dbo.v_s_log_audit AS SELECT * FROM dbo.s_log_audit WHERE b_operator_id IN (SELECT b_id FROM dbo.v_b_user) OR " + ALLOW); + + conn.commit(); + System.out.println("COMMITTED."); + + // verify + for (String t : new String[]{"s_log_login", "s_log_technical", "s_log_audit", "s_log_audit_field"}) { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT c.column_id, c.name FROM sys.columns c WHERE c.object_id=OBJECT_ID('" + t + "') ORDER BY c.column_id")) { + rs.next(); + System.out.printf(" %-22s col1=[%d]%s PK=%s%n", t, rs.getInt(1), rs.getString(2), + hasPk(conn, t)); + } + } + for (String v : new String[]{"v_s_log_login", "v_s_log_technical", "v_s_log_audit"}) { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT TOP 1 1 FROM " + v)) { + System.out.println("VIEW OK: " + v); + } + } + } + } + static boolean hasPk(Connection conn, String tbl) throws SQLException { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT COUNT(*) FROM sys.key_constraints WHERE parent_object_id=OBJECT_ID('" + tbl + "') AND type='PK'")) { + rs.next(); return rs.getInt(1) > 0; + } + } + static void run(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement()) { st.execute(sql); } + System.out.println("OK: " + sql.replaceAll("\\s+", " ").trim().substring(0, Math.min(80, sql.replaceAll("\\s+", " ").trim().length()))); + } +} diff --git a/code/fms/.tmp-fkcheck/LogStruct.class b/code/fms/.tmp-fkcheck/LogStruct.class new file mode 100644 index 00000000..365e8c2e Binary files /dev/null and b/code/fms/.tmp-fkcheck/LogStruct.class differ diff --git a/code/fms/.tmp-fkcheck/LogStruct.java b/code/fms/.tmp-fkcheck/LogStruct.java new file mode 100644 index 00000000..b184eff5 --- /dev/null +++ b/code/fms/.tmp-fkcheck/LogStruct.java @@ -0,0 +1,40 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class LogStruct { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + for (String t : new String[]{"s_log_login", "s_log_technical", "s_log_audit", "s_log_audit_field"}) { + System.out.println("==== " + t + " ===="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT c.name, ty.name AS type, c.max_length, c.precision, c.scale, c.is_nullable, c.is_identity, dc.definition AS def " + + "FROM sys.columns c JOIN sys.types ty ON ty.user_type_id = c.user_type_id " + + "LEFT JOIN sys.default_constraints dc ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id " + + "WHERE c.object_id = OBJECT_ID('" + t + "') ORDER BY c.column_id")) { + while (rs.next()) { + String type = rs.getString("type"); int ml = rs.getInt("max_length"); + String ts; + if (type.equals("nvarchar")||type.equals("varchar")||type.equals("nchar")||type.equals("char")) + ts = type + (ml == -1 ? "(max)" : "(" + (ml / (type.startsWith("n")?2:1)) + ")"); + else if (type.equals("decimal")||type.equals("numeric")) + ts = type + "(" + rs.getInt("precision") + "," + rs.getInt("scale") + ")"; + else ts = type; + System.out.printf(" %-24s %-15s null=%-5s id=%-5s def=%s%n", + rs.getString("name"), ts, rs.getBoolean("is_nullable"), rs.getBoolean("is_identity"), rs.getString("def")); + } + } + System.out.println(" -- indexes --"); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT i.name, i.is_unique, i.is_primary_key, " + + "STUFF((SELECT ',' + c2.name FROM sys.index_columns ic2 JOIN sys.columns c2 ON c2.object_id=ic2.object_id AND c2.column_id=ic2.column_id " + + "WHERE ic2.object_id=i.object_id AND ic2.index_id=i.index_id ORDER BY ic2.key_ordinal FOR XML PATH('')),1,1,'') AS cols " + + "FROM sys.indexes i WHERE i.object_id=OBJECT_ID('" + t + "') AND i.name IS NOT NULL")) { + while (rs.next()) System.out.printf(" idx: %-28s unique=%-5s pk=%-5s cols=%s%n", + rs.getString("name"), rs.getBoolean("is_unique"), rs.getBoolean("is_primary_key"), rs.getString("cols")); + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/LoginLogWriteTest.class b/code/fms/.tmp-fkcheck/LoginLogWriteTest.class new file mode 100644 index 00000000..c6ce0c0c Binary files /dev/null and b/code/fms/.tmp-fkcheck/LoginLogWriteTest.class differ diff --git a/code/fms/.tmp-fkcheck/LoginLogWriteTest.java b/code/fms/.tmp-fkcheck/LoginLogWriteTest.java new file mode 100644 index 00000000..c6e8eabe --- /dev/null +++ b/code/fms/.tmp-fkcheck/LoginLogWriteTest.java @@ -0,0 +1,55 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class LoginLogWriteTest { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + long id = 900000000000000099L; // test snowflake-like id + // exact same column set as LoginLogService.recordLogin + String sql = "INSERT INTO dbo.s_log_login (" + + "b_id, b_org_id, b_user_id, b_account, b_login_type, b_occurdatetime, b_ip, b_ip_location, " + + "b_browser, b_browser_version, b_os, b_os_version, b_device_type, b_user_agent, " + + "b_login_result, b_fail_reason, b_session_id" + + ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setLong(1, id); + ps.setString(2, "G3HD"); + ps.setLong(3, 3L); // g3soft's b_id + ps.setString(4, "g3soft"); + ps.setString(5, "password"); + ps.setTimestamp(6, new Timestamp(System.currentTimeMillis())); // UTC instant + ps.setString(7, "127.0.0.1"); + ps.setString(8, "内网"); + ps.setString(9, "Chrome"); + ps.setString(10, "120"); + ps.setString(11, "Windows"); + ps.setString(12, "10"); + ps.setString(13, "pc"); + ps.setString(14, "Mozilla/5.0 test"); + ps.setInt(15, 1); // success + ps.setNull(16, Types.NVARCHAR); + ps.setString(17, "test-session"); + int n = ps.executeUpdate(); + System.out.println("INSERT OK rows=" + n); + } + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT b_id, b_org_id, b_user_id, b_account, b_login_type, b_occurdatetime, b_login_result, b_session_id FROM s_log_login WHERE b_id = " + id)) { + ResultSetMetaData m = rs.getMetaData(); + rs.next(); + StringBuilder sb = new StringBuilder(); + for (int i = 1; i <= m.getColumnCount(); i++) { + if (i > 1) sb.append(" | "); + sb.append(m.getColumnName(i)).append("=").append(rs.getObject(i)); + } + System.out.println("READBACK: " + sb); + } + try (Statement st = conn.createStatement()) { + int d = st.executeUpdate("DELETE FROM s_log_login WHERE b_id = " + id); + System.out.println("CLEANUP deleted=" + d); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/MigrateGen$Chk.class b/code/fms/.tmp-fkcheck/MigrateGen$Chk.class new file mode 100644 index 00000000..d90ab39f Binary files /dev/null and b/code/fms/.tmp-fkcheck/MigrateGen$Chk.class differ diff --git a/code/fms/.tmp-fkcheck/MigrateGen$Idx.class b/code/fms/.tmp-fkcheck/MigrateGen$Idx.class new file mode 100644 index 00000000..b0130dc1 Binary files /dev/null and b/code/fms/.tmp-fkcheck/MigrateGen$Idx.class differ diff --git a/code/fms/.tmp-fkcheck/MigrateGen.class b/code/fms/.tmp-fkcheck/MigrateGen.class new file mode 100644 index 00000000..667688eb Binary files /dev/null and b/code/fms/.tmp-fkcheck/MigrateGen.class differ diff --git a/code/fms/.tmp-fkcheck/MigrateGen.java b/code/fms/.tmp-fkcheck/MigrateGen.java new file mode 100644 index 00000000..d124be2f --- /dev/null +++ b/code/fms/.tmp-fkcheck/MigrateGen.java @@ -0,0 +1,93 @@ +import java.io.FileInputStream; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.sql.*; +import java.util.*; + +public class MigrateGen { + record Idx(String tbl, String name, String cols, boolean isPk, boolean isUnique) {} + record Chk(String tbl, String name, String definition) {} + + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + List tables = new ArrayList<>(List.of( + "s_autocode","s_field","s_field_edit","s_field_group","s_field_query","s_field_view", + "s_i18n","s_log_audit","s_log_audit_field","s_log_login","s_log_technical", + "s_menu","s_module","s_power","s_relation","s_role","s_role_power","s_role_scope","s_user_role")); + + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + // 1. indexes + key columns + Map> idxByTbl = new HashMap<>(); + String idxSql = + "SELECT OBJECT_NAME(i.object_id) AS tbl, i.name AS idx, i.is_unique, i.is_primary_key, " + + " STUFF((SELECT ',' + c.name FROM sys.index_columns ic " + + " JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id " + + " WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id " + + " ORDER BY ic.key_ordinal FOR XML PATH('')), 1, 1, '') AS cols " + + "FROM sys.indexes i WHERE i.object_id IN (SELECT object_id FROM sys.tables WHERE name LIKE 's' + char(95) + '%') AND i.type IN (1,2) AND i.is_disabled = 0"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(idxSql)) { + while (rs.next()) { + String tbl = rs.getString("tbl"); + idxByTbl.computeIfAbsent(tbl, k -> new ArrayList<>()).add( + new Idx(tbl, rs.getString("idx"), rs.getString("cols"), + rs.getBoolean("is_primary_key"), rs.getBoolean("is_unique"))); + } + } + // 2. check constraints + Map> chkByTbl = new HashMap<>(); + String chkSql = "SELECT OBJECT_NAME(parent_object_id) AS tbl, name, definition FROM sys.check_constraints WHERE parent_object_id IN (SELECT object_id FROM sys.tables)"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(chkSql)) { + while (rs.next()) { + String tbl = rs.getString("tbl"); + chkByTbl.computeIfAbsent(tbl, k -> new ArrayList<>()).add( + new Chk(tbl, rs.getString("name"), rs.getString("definition"))); + } + } + // 3. generate script + String out = "migrate_identity_to_snowflake.sql"; + try (PrintWriter w = new PrintWriter(out, StandardCharsets.UTF_8)) { + w.println("-- Migrate s_* tables: b_id identity(1,1) -> plain bigint (snowflake by app layer)"); + w.println("-- Generated: " + new Timestamp(System.currentTimeMillis())); + for (String tbl : tables) { + List idxs = idxByTbl.getOrDefault(tbl, List.of()); + List chks = chkByTbl.getOrDefault(tbl, List.of()); + List pk = idxs.stream().filter(i -> i.isPk()).toList(); + List bIdIdx = idxs.stream().filter(i -> !i.isPk() && i.cols().contains("b_id")).toList(); + List bIdChk = chks.stream().filter(c -> c.definition().contains("b_id")).toList(); + List bIdUq = bIdIdx.stream().filter(i -> i.isUnique()).toList(); + List bIdNc = bIdIdx.stream().filter(i -> !i.isUnique()).toList(); + + w.println(); + w.println("-- ===== " + tbl + " ====="); + if (pk.isEmpty()) { w.println("-- [SKIP] no PK found"); continue; } + w.println("ALTER TABLE [dbo].[" + tbl + "] DROP CONSTRAINT [" + pk.get(0).name() + "];"); + for (Chk c : bIdChk) w.println("ALTER TABLE [dbo].[" + tbl + "] DROP CONSTRAINT [" + c.name() + "];"); + for (Idx i : bIdUq) w.println("ALTER TABLE [dbo].[" + tbl + "] DROP CONSTRAINT [" + i.name() + "];"); + for (Idx i : bIdNc) w.println("DROP INDEX [" + i.name() + "] ON [dbo].[" + tbl + "];"); + w.println("ALTER TABLE [dbo].[" + tbl + "] ADD [b_id_new] bigint NULL;"); + w.println("UPDATE [dbo].[" + tbl + "] SET [b_id_new] = [b_id];"); + w.println("ALTER TABLE [dbo].[" + tbl + "] ALTER COLUMN [b_id_new] bigint NOT NULL;"); + w.println("ALTER TABLE [dbo].[" + tbl + "] DROP COLUMN [b_id];"); + w.println("GO"); + w.println("EXEC sp_rename 'dbo." + tbl + ".b_id_new', 'b_id', 'COLUMN';"); + w.println("GO"); + w.println("ALTER TABLE [dbo].[" + tbl + "] ADD CONSTRAINT [" + pk.get(0).name() + "] PRIMARY KEY ([b_id]);"); + for (Chk c : bIdChk) w.println("ALTER TABLE [dbo].[" + tbl + "] ADD CONSTRAINT [" + c.name() + "] CHECK " + c.definition() + ";"); + for (Idx i : bIdUq) w.println("ALTER TABLE [dbo].[" + tbl + "] ADD CONSTRAINT [" + i.name() + "] UNIQUE (" + i.cols() + ");"); + for (Idx i : bIdNc) w.println("CREATE INDEX [" + i.name() + "] ON [dbo].[" + tbl + "] (" + i.cols() + ");"); + w.println("GO"); + } + w.println("-- End of migration"); + } + System.out.println("Script written: " + out); + // print preview: first 60 lines + try (java.io.BufferedReader r = new java.io.BufferedReader(new java.io.InputStreamReader( + new FileInputStream(out), StandardCharsets.UTF_8))) { + String line; int n = 0; + while ((line = r.readLine()) != null && n < 60) { System.out.println(line); n++; } + System.out.println("... (" + n + " lines shown of total)"); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/MigrateRun.class b/code/fms/.tmp-fkcheck/MigrateRun.class new file mode 100644 index 00000000..2ff0d09e Binary files /dev/null and b/code/fms/.tmp-fkcheck/MigrateRun.class differ diff --git a/code/fms/.tmp-fkcheck/MigrateRun.java b/code/fms/.tmp-fkcheck/MigrateRun.java new file mode 100644 index 00000000..84513867 --- /dev/null +++ b/code/fms/.tmp-fkcheck/MigrateRun.java @@ -0,0 +1,74 @@ +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class MigrateRun { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + String script = new String(Files.readAllBytes(Paths.get(args[1])), StandardCharsets.UTF_8); + + // split into per-table blocks by "-- ===== " headers + String[] blocks = script.split("(?m)^-- ===== "); + List tableBatches = new ArrayList<>(); + List tableNames = new ArrayList<>(); + for (String b : blocks) { + if (b.isBlank()) continue; + String[] lines = b.split("\\R", 2); + String name = lines[0].trim(); + String body = lines.length > 1 ? lines[1] : ""; + // skip comment-only tail + if (!body.contains("ALTER TABLE") && !body.contains("EXEC sp_rename")) continue; + // split body by GO lines + List batches = new ArrayList<>(); + StringBuilder cur = new StringBuilder(); + for (String line : body.split("\\R")) { + if (line.trim().equalsIgnoreCase("GO")) { + if (!cur.toString().isBlank()) batches.add(cur.toString()); + cur.setLength(0); + } else { + cur.append(line).append("\n"); + } + } + if (!cur.toString().isBlank()) batches.add(cur.toString()); + tableBatches.add(batches.toArray(new String[0])); + tableNames.add(name); + } + + System.out.println("Tables to migrate: " + tableNames.size()); + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + int ok = 0; + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + for (int t = 0; t < tableNames.size(); t++) { + String tbl = tableNames.get(t); + String[] batches = tableBatches.get(t); + conn.setAutoCommit(false); + boolean failed = false; + for (String batch : batches) { + try (Statement st = conn.createStatement()) { + st.execute(batch); + } catch (SQLException e) { + System.err.println("FAILED on " + tbl + " batch: " + e.getMessage()); + System.err.println(" SQL: " + batch.replaceAll("\\s+", " ").trim()); + failed = true; + break; + } + } + if (failed) { + conn.rollback(); + System.err.println(" -> ROLLED BACK " + tbl); + } else { + conn.commit(); + System.out.println("OK: " + tbl + " (" + batches.length + " batches)"); + ok++; + } + } + } + System.out.println("\nMigrated OK: " + ok + "/" + tableNames.size()); + } +} diff --git a/code/fms/.tmp-fkcheck/MigrateRun2.class b/code/fms/.tmp-fkcheck/MigrateRun2.class new file mode 100644 index 00000000..851d7196 Binary files /dev/null and b/code/fms/.tmp-fkcheck/MigrateRun2.class differ diff --git a/code/fms/.tmp-fkcheck/MigrateRun2.java b/code/fms/.tmp-fkcheck/MigrateRun2.java new file mode 100644 index 00000000..46687268 --- /dev/null +++ b/code/fms/.tmp-fkcheck/MigrateRun2.java @@ -0,0 +1,64 @@ +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class MigrateRun2 { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + String script = new String(Files.readAllBytes(Paths.get(args[1])), StandardCharsets.UTF_8); + + // split into per-table blocks by "-- ===== " headers + String[] blocks = script.split("(?m)^-- ===== "); + List tableNames = new ArrayList<>(); + List> tableStmts = new ArrayList<>(); + for (String b : blocks) { + if (b.isBlank()) continue; + String[] lines = b.split("\\R", 2); + String name = lines[0].trim(); + String body = lines.length > 1 ? lines[1] : ""; + if (!body.contains("ALTER TABLE") && !body.contains("EXEC sp_rename")) continue; + // drop GO lines, split statements by ';' + StringBuilder sb = new StringBuilder(); + for (String line : body.split("\\R")) { + if (line.trim().equalsIgnoreCase("GO")) continue; + sb.append(line).append("\n"); + } + List stmts = new ArrayList<>(); + for (String s : sb.toString().split(";")) { + String t = s.trim(); + if (!t.isEmpty()) stmts.add(t); + } + tableNames.add(name); + tableStmts.add(stmts); + } + + System.out.println("Tables to migrate: " + tableNames.size()); + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + int ok = 0; + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + for (int t = 0; t < tableNames.size(); t++) { + String tbl = tableNames.get(t); + List stmts = tableStmts.get(t); + conn.setAutoCommit(false); + boolean failed = false; + for (String s : stmts) { + try (Statement st = conn.createStatement()) { st.execute(s); } + catch (SQLException e) { + System.err.println("FAILED on " + tbl + ": " + e.getMessage()); + System.err.println(" SQL: " + s.replaceAll("\\s+", " ").trim()); + failed = true; break; + } + } + if (failed) { conn.rollback(); System.err.println(" -> ROLLED BACK " + tbl); } + else { conn.commit(); System.out.println("OK: " + tbl + " (" + stmts.size() + " stmts)"); ok++; } + } + } + System.out.println("\nMigrated OK: " + ok + "/" + tableNames.size()); + } +} diff --git a/code/fms/.tmp-fkcheck/OrgIdSurvey.class b/code/fms/.tmp-fkcheck/OrgIdSurvey.class new file mode 100644 index 00000000..5032982a Binary files /dev/null and b/code/fms/.tmp-fkcheck/OrgIdSurvey.class differ diff --git a/code/fms/.tmp-fkcheck/OrgIdSurvey.java b/code/fms/.tmp-fkcheck/OrgIdSurvey.java new file mode 100644 index 00000000..2bff3540 --- /dev/null +++ b/code/fms/.tmp-fkcheck/OrgIdSurvey.java @@ -0,0 +1,50 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class OrgIdSurvey { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + System.out.println("== tables with b_org_id column =="); + String sql = "SELECT t.name AS tbl, c.name AS col, c.is_nullable, c.max_length, dc.definition AS def, t2.name AS dtype " + + "FROM sys.tables t " + + "JOIN sys.columns c ON c.object_id = t.object_id " + + "JOIN sys.types t2 ON t2.user_type_id = c.user_type_id " + + "LEFT JOIN sys.default_constraints dc ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id " + + "WHERE c.name = 'b_org_id' ORDER BY t.name"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + boolean any = false; + while (rs.next()) { + any = true; + System.out.printf(" %-22s %-10s null=%-5s type=%-10s default=%s%n", + rs.getString("tbl"), rs.getString("col"), rs.getBoolean("is_nullable"), + rs.getString("dtype"), rs.getString("def")); + } + if (!any) System.out.println(" (none)"); + } + System.out.println("\n== all user tables + row counts =="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT t.name, p.rows FROM sys.tables t " + + "JOIN sys.partitions p ON p.object_id = t.object_id AND p.index_id IN (0,1) " + + "WHERE t.is_ms_shipped = 0 ORDER BY t.name")) { + while (rs.next()) System.out.printf(" %-22s rows=%s%n", rs.getString("name"), rs.getLong("rows")); + } + System.out.println("\n== sample distinct b_org_id values per table =="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT t.name AS tbl FROM sys.tables t JOIN sys.columns c ON c.object_id = t.object_id " + + "WHERE c.name = 'b_org_id' AND t.is_ms_shipped = 0")) { + while (rs.next()) { + String tbl = rs.getString("tbl"); + try (Statement st2 = conn.createStatement(); ResultSet rs2 = st2.executeQuery( + "SELECT COUNT(*) AS total, COUNT(DISTINCT b_org_id) AS distinct_orgs, MIN(b_org_id) AS min_org, MAX(b_org_id) AS max_org FROM [" + tbl + "]")) { + rs2.next(); + System.out.printf(" %-22s rows=%-6s distinct_org=%-3s min=%s max=%s%n", + tbl, rs2.getLong("total"), rs2.getLong("distinct_orgs"), + rs2.getString("min_org"), rs2.getString("max_org")); + } + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/PkSurvey.class b/code/fms/.tmp-fkcheck/PkSurvey.class new file mode 100644 index 00000000..2cc8762f Binary files /dev/null and b/code/fms/.tmp-fkcheck/PkSurvey.class differ diff --git a/code/fms/.tmp-fkcheck/PkSurvey.java b/code/fms/.tmp-fkcheck/PkSurvey.java new file mode 100644 index 00000000..263bf367 --- /dev/null +++ b/code/fms/.tmp-fkcheck/PkSurvey.java @@ -0,0 +1,33 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class PkSurvey { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + System.out.println("== PK + identity status per table =="); + String sql = "SELECT t.name AS tbl, c.name AS col, c.is_identity, " + + "(SELECT COUNT(*) FROM sys.key_constraints kc WHERE kc.parent_object_id = t.object_id AND kc.type = 'PK') AS pk_count " + + "FROM sys.tables t " + + "LEFT JOIN sys.columns c ON c.object_id = t.object_id AND c.name = 'b_id' " + + "WHERE t.is_ms_shipped = 0 ORDER BY t.name"; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + System.out.printf(" %-22s b_id=%-8s identity=%-5s pk_count=%s%n", + rs.getString("tbl"), rs.getString("col"), rs.getBoolean("is_identity"), rs.getInt("pk_count")); + } + } + System.out.println("\n== PK columns for log tables =="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT kc.name AS pk, OBJECT_NAME(kc.parent_object_id) AS tbl, c.name AS col " + + "FROM sys.key_constraints kc " + + "JOIN sys.index_columns ic ON ic.object_id = kc.parent_object_id AND ic.index_id = kc.unique_index_id " + + "JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id " + + "WHERE kc.type = 'PK' AND OBJECT_NAME(kc.parent_object_id) IN ('s_log_login','s_log_technical') " + + "ORDER BY tbl, ic.key_ordinal")) { + while (rs.next()) System.out.printf(" %s.%s (%s)%n", rs.getString("tbl"), rs.getString("col"), rs.getString("pk")); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/RefreshViews.class b/code/fms/.tmp-fkcheck/RefreshViews.class new file mode 100644 index 00000000..85635d2f Binary files /dev/null and b/code/fms/.tmp-fkcheck/RefreshViews.class differ diff --git a/code/fms/.tmp-fkcheck/RefreshViews.java b/code/fms/.tmp-fkcheck/RefreshViews.java new file mode 100644 index 00000000..8c3951d8 --- /dev/null +++ b/code/fms/.tmp-fkcheck/RefreshViews.java @@ -0,0 +1,32 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class RefreshViews { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + for (String v : new String[]{"v_s_log_login", "v_s_log_technical", "v_s_log_audit", "v_b_user"}) { + try (Statement st = conn.createStatement()) { + st.execute("EXEC sp_refreshview 'dbo." + v + "'"); + System.out.println("REFRESHED: " + v); + } catch (SQLException e) { + System.out.println("WARN: " + v + " -> " + e.getMessage()); + } + } + // verify all views queryable + for (String v : new String[]{"v_s_log_login", "v_s_log_technical", "v_s_log_audit", "v_b_user"}) { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT TOP 1 1 FROM " + v)) { + System.out.println("VIEW OK: " + v); + } + } + // verify s_log_login current columns + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT c.name FROM sys.columns c WHERE c.object_id=OBJECT_ID('s_log_login') ORDER BY c.column_id")) { + StringBuilder sb = new StringBuilder("s_log_login cols: "); + while (rs.next()) sb.append(rs.getString(1)).append(" "); + System.out.println(sb); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/SimplifyG3Soft.class b/code/fms/.tmp-fkcheck/SimplifyG3Soft.class new file mode 100644 index 00000000..be45278e Binary files /dev/null and b/code/fms/.tmp-fkcheck/SimplifyG3Soft.class differ diff --git a/code/fms/.tmp-fkcheck/SimplifyG3Soft.java b/code/fms/.tmp-fkcheck/SimplifyG3Soft.java new file mode 100644 index 00000000..2e9434a3 --- /dev/null +++ b/code/fms/.tmp-fkcheck/SimplifyG3Soft.java @@ -0,0 +1,53 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class SimplifyG3Soft { + static final String ALLOW = "ISNULL(CONVERT(int, SESSION_CONTEXT(N'allow_g3soft')), 0) = 1"; + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + conn.setAutoCommit(false); + // 1. drop views that reference b_user (v_b_user, and v_s_log_audit depends on v_b_user) + run(conn, "IF OBJECT_ID('dbo.v_s_log_audit') IS NOT NULL DROP VIEW dbo.v_s_log_audit"); + run(conn, "IF OBJECT_ID('dbo.v_b_user') IS NOT NULL DROP VIEW dbo.v_b_user"); + // 2. drop b_is_system column + run(conn, "ALTER TABLE dbo.b_user DROP COLUMN b_is_system"); + // 3. recreate v_b_user filtering by account name only + run(conn, "CREATE VIEW dbo.v_b_user AS SELECT * FROM dbo.b_user WHERE b_account <> N'g3soft' OR " + ALLOW); + // 4. recreate v_s_log_audit (depends on v_b_user) + run(conn, "CREATE VIEW dbo.v_s_log_audit AS SELECT * FROM dbo.s_log_audit WHERE b_operator_id IN (SELECT b_id FROM dbo.v_b_user) OR " + ALLOW); + conn.commit(); + System.out.println("COMMITTED."); + + System.out.println("\n== b_user columns =="); + dump(conn, "SELECT name FROM sys.columns WHERE object_id=OBJECT_ID('b_user') ORDER BY column_id"); + System.out.println("\n== v_b_user WITHOUT ctx (expect 001,002 only) =="); + dump(conn, "SELECT b_id, b_account FROM v_b_user ORDER BY b_id"); + try (Statement st = conn.createStatement()) { st.execute("EXEC sys.sp_set_session_context @key=N'allow_g3soft', @value=1"); } + System.out.println("== v_b_user WITH ctx=1 (expect all 3) =="); + dump(conn, "SELECT b_id, b_account FROM v_b_user ORDER BY b_id"); + try (Statement st = conn.createStatement()) { st.execute("EXEC sys.sp_set_session_context @key=N'allow_g3soft', @value=NULL"); } + System.out.println("\n== views =="); + dump(conn, "SELECT name FROM sys.views WHERE name LIKE 'v_%' ORDER BY name"); + } + } + static void run(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement()) { st.execute(sql); } + System.out.println("OK: " + sql.replaceAll("\\s+", " ").trim().substring(0, Math.min(95, sql.replaceAll("\\s+", " ").trim().length()))); + } + static void dump(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + ResultSetMetaData m = rs.getMetaData(); + while (rs.next()) { + StringBuilder sb = new StringBuilder(" "); + for (int i = 1; i <= m.getColumnCount(); i++) { + if (i > 1) sb.append(" | "); + sb.append(m.getColumnName(i)).append("=").append(rs.getObject(i)); + } + System.out.println(sb); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/SimplifyG3Soft2.class b/code/fms/.tmp-fkcheck/SimplifyG3Soft2.class new file mode 100644 index 00000000..a8b4fe03 Binary files /dev/null and b/code/fms/.tmp-fkcheck/SimplifyG3Soft2.class differ diff --git a/code/fms/.tmp-fkcheck/SimplifyG3Soft2.java b/code/fms/.tmp-fkcheck/SimplifyG3Soft2.java new file mode 100644 index 00000000..d38b10ce --- /dev/null +++ b/code/fms/.tmp-fkcheck/SimplifyG3Soft2.java @@ -0,0 +1,55 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class SimplifyG3Soft2 { + static final String ALLOW = "ISNULL(CONVERT(int, SESSION_CONTEXT(N'allow_g3soft')), 0) = 1"; + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + conn.setAutoCommit(false); + run(conn, "IF OBJECT_ID('dbo.v_s_log_audit') IS NOT NULL DROP VIEW dbo.v_s_log_audit"); + run(conn, "IF OBJECT_ID('dbo.v_b_user') IS NOT NULL DROP VIEW dbo.v_b_user"); + // drop default constraint on b_is_system if exists, then drop column + run(conn, "DECLARE @d nvarchar(128) = (SELECT dc.name FROM sys.default_constraints dc " + + "WHERE dc.parent_object_id = OBJECT_ID('b_user') AND dc.parent_column_id = " + + "(SELECT column_id FROM sys.columns WHERE object_id=OBJECT_ID('b_user') AND name='b_is_system')); " + + "IF @d IS NOT NULL EXEC('ALTER TABLE dbo.b_user DROP CONSTRAINT ' + QUOTENAME(@d))"); + run(conn, "IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=OBJECT_ID('b_user') AND name='b_is_system') " + + "ALTER TABLE dbo.b_user DROP COLUMN b_is_system"); + run(conn, "CREATE VIEW dbo.v_b_user AS SELECT * FROM dbo.b_user WHERE b_account <> N'g3soft' OR " + ALLOW); + run(conn, "CREATE VIEW dbo.v_s_log_audit AS SELECT * FROM dbo.s_log_audit WHERE b_operator_id IN (SELECT b_id FROM dbo.v_b_user) OR " + ALLOW); + conn.commit(); + System.out.println("COMMITTED."); + + System.out.println("\n== b_user columns (no b_is_system expected) =="); + dump(conn, "SELECT name FROM sys.columns WHERE object_id=OBJECT_ID('b_user') ORDER BY column_id"); + System.out.println("\n== v_b_user WITHOUT ctx =="); + dump(conn, "SELECT b_id, b_account FROM v_b_user ORDER BY b_id"); + try (Statement st = conn.createStatement()) { st.execute("EXEC sys.sp_set_session_context @key=N'allow_g3soft', @value=1"); } + System.out.println("== v_b_user WITH ctx=1 =="); + dump(conn, "SELECT b_id, b_account FROM v_b_user ORDER BY b_id"); + try (Statement st = conn.createStatement()) { st.execute("EXEC sys.sp_set_session_context @key=N'allow_g3soft', @value=NULL"); } + System.out.println("\n== views =="); + dump(conn, "SELECT name FROM sys.views WHERE name LIKE 'v_%' ORDER BY name"); + } + } + static void run(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement()) { st.execute(sql); } + System.out.println("OK: " + sql.replaceAll("\\s+", " ").trim().substring(0, Math.min(95, sql.replaceAll("\\s+", " ").trim().length()))); + } + static void dump(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + ResultSetMetaData m = rs.getMetaData(); + while (rs.next()) { + StringBuilder sb = new StringBuilder(" "); + for (int i = 1; i <= m.getColumnCount(); i++) { + if (i > 1) sb.append(" | "); + sb.append(m.getColumnName(i)).append("=").append(rs.getObject(i)); + } + System.out.println(sb); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/SimplifyG3Soft3.class b/code/fms/.tmp-fkcheck/SimplifyG3Soft3.class new file mode 100644 index 00000000..f5ae0652 Binary files /dev/null and b/code/fms/.tmp-fkcheck/SimplifyG3Soft3.class differ diff --git a/code/fms/.tmp-fkcheck/SimplifyG3Soft3.java b/code/fms/.tmp-fkcheck/SimplifyG3Soft3.java new file mode 100644 index 00000000..c5c11c81 --- /dev/null +++ b/code/fms/.tmp-fkcheck/SimplifyG3Soft3.java @@ -0,0 +1,61 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class SimplifyG3Soft3 { + static final String ALLOW = "ISNULL(CONVERT(int, SESSION_CONTEXT(N'allow_g3soft')), 0) = 1"; + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + conn.setAutoCommit(false); + run(conn, "IF OBJECT_ID('dbo.v_s_log_audit') IS NOT NULL DROP VIEW dbo.v_s_log_audit"); + run(conn, "IF OBJECT_ID('dbo.v_b_user') IS NOT NULL DROP VIEW dbo.v_b_user"); + + // find & drop default constraint on b_is_system + String defCons = null; + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT dc.name FROM sys.default_constraints dc " + + "WHERE dc.parent_object_id = OBJECT_ID('b_user') AND dc.parent_column_id = " + + "(SELECT column_id FROM sys.columns WHERE object_id=OBJECT_ID('b_user') AND name='b_is_system')")) { + if (rs.next()) defCons = rs.getString(1); + } + if (defCons != null) run(conn, "ALTER TABLE dbo.b_user DROP CONSTRAINT [" + defCons + "]"); + + run(conn, "IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=OBJECT_ID('b_user') AND name='b_is_system') " + + "ALTER TABLE dbo.b_user DROP COLUMN b_is_system"); + run(conn, "CREATE VIEW dbo.v_b_user AS SELECT * FROM dbo.b_user WHERE b_account <> N'g3soft' OR " + ALLOW); + run(conn, "CREATE VIEW dbo.v_s_log_audit AS SELECT * FROM dbo.s_log_audit WHERE b_operator_id IN (SELECT b_id FROM dbo.v_b_user) OR " + ALLOW); + conn.commit(); + System.out.println("COMMITTED."); + + System.out.println("\n== b_user columns =="); + dump(conn, "SELECT name FROM sys.columns WHERE object_id=OBJECT_ID('b_user') ORDER BY column_id"); + System.out.println("\n== v_b_user WITHOUT ctx =="); + dump(conn, "SELECT b_id, b_account FROM v_b_user ORDER BY b_id"); + try (Statement st = conn.createStatement()) { st.execute("EXEC sys.sp_set_session_context @key=N'allow_g3soft', @value=1"); } + System.out.println("== v_b_user WITH ctx=1 =="); + dump(conn, "SELECT b_id, b_account FROM v_b_user ORDER BY b_id"); + try (Statement st = conn.createStatement()) { st.execute("EXEC sys.sp_set_session_context @key=N'allow_g3soft', @value=NULL"); } + System.out.println("\n== views =="); + dump(conn, "SELECT name FROM sys.views WHERE name LIKE 'v_%' ORDER BY name"); + } + } + static void run(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement()) { st.execute(sql); } + System.out.println("OK: " + sql.replaceAll("\\s+", " ").trim().substring(0, Math.min(95, sql.replaceAll("\\s+", " ").trim().length()))); + } + static void dump(Connection conn, String sql) throws SQLException { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + ResultSetMetaData m = rs.getMetaData(); + while (rs.next()) { + StringBuilder sb = new StringBuilder(" "); + for (int i = 1; i <= m.getColumnCount(); i++) { + if (i > 1) sb.append(" | "); + sb.append(m.getColumnName(i)).append("=").append(rs.getObject(i)); + } + System.out.println(sb); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/TableSurvey.class b/code/fms/.tmp-fkcheck/TableSurvey.class new file mode 100644 index 00000000..26d42962 Binary files /dev/null and b/code/fms/.tmp-fkcheck/TableSurvey.class differ diff --git a/code/fms/.tmp-fkcheck/TableSurvey.java b/code/fms/.tmp-fkcheck/TableSurvey.java new file mode 100644 index 00000000..cfef3d55 --- /dev/null +++ b/code/fms/.tmp-fkcheck/TableSurvey.java @@ -0,0 +1,44 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; + +public class TableSurvey { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + System.out.println("== ALL TABLES + ROW COUNTS =="); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT t.name AS tbl, SUM(p.rows) AS rowcnt " + + "FROM sys.tables t JOIN sys.partitions p ON p.object_id = t.object_id AND p.index_id IN (0,1) " + + "GROUP BY t.name ORDER BY t.name")) { + while (rs.next()) System.out.printf(" %-24s rows=%s%n", rs.getString("tbl"), rs.getString("rowcnt")); + } + System.out.println("== TABLES WHOSE b_id IS IDENTITY =="); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT OBJECT_NAME(ic.object_id) AS tbl FROM sys.identity_columns ic WHERE ic.name='b_id' ORDER BY 1")) { + int n = 0; + while (rs.next()) { System.out.println(" " + rs.getString("tbl")); n++; } + System.out.println(" count=" + n); + } + System.out.println("== PK / UNIQUE / CHECK CONSTRAINTS REFERENCING TABLES =="); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT OBJECT_NAME(kc.parent_object_id) AS tbl, kc.name AS cons, kc.type_desc " + + "FROM sys.key_constraints kc WHERE kc.parent_object_id IN (SELECT object_id FROM sys.tables) " + + "ORDER BY tbl, cons")) { + while (rs.next()) System.out.printf(" %-24s %-30s %s%n", rs.getString("tbl"), rs.getString("cons"), rs.getString("type_desc")); + } + System.out.println("== NON-CLUSTERED INDEXES =="); + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery( + "SELECT OBJECT_NAME(i.object_id) AS tbl, i.name AS idx, i.is_unique " + + "FROM sys.indexes i WHERE i.object_id IN (SELECT object_id FROM sys.tables) AND i.type = 2 " + + "ORDER BY tbl, idx")) { + while (rs.next()) System.out.printf(" %-24s %-32s unique=%s%n", rs.getString("tbl"), rs.getString("idx"), rs.getBoolean("is_unique")); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/TblCheck.class b/code/fms/.tmp-fkcheck/TblCheck.class new file mode 100644 index 00000000..a4adefe7 Binary files /dev/null and b/code/fms/.tmp-fkcheck/TblCheck.class differ diff --git a/code/fms/.tmp-fkcheck/TblCheck.java b/code/fms/.tmp-fkcheck/TblCheck.java new file mode 100644 index 00000000..f8676c87 --- /dev/null +++ b/code/fms/.tmp-fkcheck/TblCheck.java @@ -0,0 +1,34 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class TblCheck { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + System.out.println("== tables matching log =="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT name FROM sys.tables WHERE name LIKE '%log%' ORDER BY name")) { + while (rs.next()) System.out.println(" " + rs.getString(1)); + } + System.out.println("== s_login_log exists? rows? =="); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT COUNT(*) FROM sys.tables WHERE name = 's_login_log'")) { + rs.next(); + boolean exists = rs.getInt(1) > 0; + System.out.println(" exists=" + exists); + if (exists) { + try (Statement st2 = conn.createStatement(); ResultSet rs2 = st2.executeQuery("SELECT COUNT(*) AS c FROM s_login_log")) { + rs2.next(); System.out.println(" rows=" + rs2.getInt("c")); + } + try (Statement st2 = conn.createStatement(); ResultSet rs2 = st2.executeQuery( + "SELECT name FROM sys.columns WHERE object_id=OBJECT_ID('s_login_log') ORDER BY column_id")) { + StringBuilder sb = new StringBuilder(" cols: "); + while (rs2.next()) sb.append(rs2.getString(1)).append(" "); + System.out.println(sb); + } + } + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/UserCheck.class b/code/fms/.tmp-fkcheck/UserCheck.class new file mode 100644 index 00000000..b172f858 Binary files /dev/null and b/code/fms/.tmp-fkcheck/UserCheck.class differ diff --git a/code/fms/.tmp-fkcheck/UserCheck.java b/code/fms/.tmp-fkcheck/UserCheck.java new file mode 100644 index 00000000..d8765e51 --- /dev/null +++ b/code/fms/.tmp-fkcheck/UserCheck.java @@ -0,0 +1,25 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class UserCheck { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT TOP 0 * FROM b_user")) { + ResultSetMetaData meta = rs.getMetaData(); + System.out.println("== b_user columns =="); + for (int i = 1; i <= meta.getColumnCount(); i++) { + System.out.printf(" %-20s type=%s autoinc=%s%n", meta.getColumnName(i), meta.getColumnTypeName(i), meta.isAutoIncrement(i)); + } + } + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT b_id, b_account, b_name FROM b_user")) { + System.out.println("== b_user data =="); + while (rs.next()) System.out.printf(" b_id=%s b_account=%s b_name=%s%n", rs.getString("b_id"), rs.getString("b_account"), rs.getString("b_name")); + } + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT is_identity FROM sys.columns WHERE object_id=OBJECT_ID('b_user') AND name='b_id'")) { + rs.next(); System.out.println("b_user.b_id is_identity=" + rs.getBoolean("is_identity")); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/UserMigrate.class b/code/fms/.tmp-fkcheck/UserMigrate.class new file mode 100644 index 00000000..508737a0 Binary files /dev/null and b/code/fms/.tmp-fkcheck/UserMigrate.class differ diff --git a/code/fms/.tmp-fkcheck/UserMigrate.java b/code/fms/.tmp-fkcheck/UserMigrate.java new file mode 100644 index 00000000..9f3cc6e2 --- /dev/null +++ b/code/fms/.tmp-fkcheck/UserMigrate.java @@ -0,0 +1,71 @@ +import java.io.FileInputStream; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.sql.*; +import java.util.Properties; + +public class UserMigrate { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + + // 1. backup b_user data to file (INSERT statements) + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT * FROM b_user"); + PrintWriter w = new PrintWriter("backup_b_user.sql", StandardCharsets.UTF_8)) { + ResultSetMetaData meta = rs.getMetaData(); + int cols = meta.getColumnCount(); + w.println("-- b_user backup: " + new Timestamp(System.currentTimeMillis())); + while (rs.next()) { + StringBuilder colsSql = new StringBuilder(); + StringBuilder vals = new StringBuilder(); + for (int i = 1; i <= cols; i++) { + if (i > 1) { colsSql.append(", "); vals.append(", "); } + colsSql.append("[").append(meta.getColumnName(i)).append("]"); + Object v = rs.getObject(i); + if (v == null) vals.append("NULL"); + else if (v instanceof Number || v instanceof Boolean) vals.append(v); + else vals.append("N'").append(String.valueOf(v).replace("'", "''")).append("'"); + } + w.println("INSERT INTO [dbo].[b_user] (" + colsSql + ") VALUES (" + vals + ");"); + } + System.out.println("Backup written: backup_b_user.sql (" + cols + " cols)"); + } + + // 2. migrate: PK drop -> add identity col -> drop old -> rename -> rebuild PK + conn.setAutoCommit(false); + String[] stmts = { + "ALTER TABLE [dbo].[b_user] DROP CONSTRAINT [PK_b_user]", + "ALTER TABLE [dbo].[b_user] ADD [b_id_new] bigint identity(1,1) NOT NULL", + "ALTER TABLE [dbo].[b_user] DROP COLUMN [b_id]", + "EXEC sp_rename 'dbo.b_user.b_id_new', 'b_id', 'COLUMN'", + "ALTER TABLE [dbo].[b_user] ADD CONSTRAINT [PK_b_user] PRIMARY KEY ([b_id])" + }; + for (String s : stmts) { + try (Statement st = conn.createStatement()) { st.execute(s); System.out.println("OK: " + s); } + catch (SQLException e) { conn.rollback(); System.err.println("FAILED: " + e.getMessage() + "\n SQL: " + s); return; } + } + conn.commit(); + System.out.println("COMMITTED."); + + // 3. verify + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT b_id, b_account, b_name FROM b_user ORDER BY b_id")) { + System.out.println("== b_user after migration =="); + while (rs.next()) System.out.printf(" b_id=%s b_account=%s b_name=%s%n", + rs.getString("b_id"), rs.getString("b_account"), rs.getString("b_name")); + } + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT is_identity FROM sys.columns WHERE object_id=OBJECT_ID('b_user') AND name='b_id'")) { + rs.next(); System.out.println("b_user.b_id is_identity=" + rs.getBoolean("is_identity")); + } + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT name FROM sys.key_constraints WHERE parent_object_id=OBJECT_ID('b_user') ORDER BY name")) { + System.out.println("== b_user constraints =="); + while (rs.next()) System.out.println(" " + rs.getString("name")); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/Verify.class b/code/fms/.tmp-fkcheck/Verify.class new file mode 100644 index 00000000..e672e999 Binary files /dev/null and b/code/fms/.tmp-fkcheck/Verify.class differ diff --git a/code/fms/.tmp-fkcheck/Verify.java b/code/fms/.tmp-fkcheck/Verify.java new file mode 100644 index 00000000..6f876138 --- /dev/null +++ b/code/fms/.tmp-fkcheck/Verify.java @@ -0,0 +1,40 @@ +import java.io.FileInputStream; +import java.sql.*; +import java.util.Properties; +public class Verify { + public static void main(String[] args) throws Exception { + Properties props = new Properties(); + try (FileInputStream in = new FileInputStream(args[0])) { props.load(in); } + try (Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"))) { + // 1. identity columns + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT OBJECT_NAME(ic.object_id) AS tbl, ic.name, ic.is_identity FROM sys.identity_columns ic WHERE ic.name='b_id'")) { + System.out.println("== identity columns on b_id =="); + if (!rs.next()) System.out.println(" NONE (all s_* tables b_id no longer identity)"); + else { do { System.out.println(" " + rs.getString("tbl")); } while (rs.next()); } + } + // 2. all PKs still exist + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery( + "SELECT OBJECT_NAME(kc.parent_object_id) AS tbl, kc.name FROM sys.key_constraints kc WHERE kc.type='PK' AND kc.parent_object_id IN (SELECT object_id FROM sys.tables WHERE name LIKE 's' + char(95) + '%') ORDER BY 1")) { + System.out.println("== PKs =="); + while (rs.next()) System.out.println(" " + rs.getString("tbl") + " " + rs.getString("name")); + } + // 3. table count + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM sys.tables")) { + rs.next(); System.out.println("total_tables=" + rs.getInt(1)); + } + // 4. FK count (should still be 0) + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM sys.foreign_keys")) { + rs.next(); System.out.println("physical_fk_count=" + rs.getInt(1)); + } + // 5. b_user unchanged + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT is_identity FROM sys.columns WHERE object_id=OBJECT_ID('b_user') AND name='b_id'")) { + rs.next(); System.out.println("b_user.b_id is_identity=" + rs.getBoolean("is_identity")); + } + // 6. select from b_user to confirm it's still readable + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM b_user")) { + rs.next(); System.out.println("b_user row_count=" + rs.getInt(1)); + } + } + } +} diff --git a/code/fms/.tmp-fkcheck/backup_b_user.sql b/code/fms/.tmp-fkcheck/backup_b_user.sql new file mode 100644 index 00000000..85a8498f --- /dev/null +++ b/code/fms/.tmp-fkcheck/backup_b_user.sql @@ -0,0 +1,10 @@ +-- b_user backup: 2026-08-21 20:45:16.558 +INSERT INTO [dbo].[b_user] ([b_id], [b_account], [b_name], [b_department], [b_subcode], [b_machineid], [b_bz], [b_password], [b_canuse], [b_othermachinelogin], [b_salesman], [b_docman], [b_dealman], [b_receivelevel], [b_paylevel], [b_visible_customer], [b_visible_supply], [b_levelvisible], [b_fromcustomer], [b_logintime], [b_checkxy], [b_loginfee], [b_phone]) VALUES (828079462711360, N'001', N'总经理', N'经理室', NULL, NULL, NULL, N'001', 1, 0, 1, 1, 1, 6, 6, 0, 0, 1, 0, 1, 1, 1, NULL); +INSERT INTO [dbo].[b_user] ([b_id], [b_account], [b_name], [b_department], [b_subcode], [b_machineid], [b_bz], [b_password], [b_canuse], [b_othermachinelogin], [b_salesman], [b_docman], [b_dealman], [b_receivelevel], [b_paylevel], [b_visible_customer], [b_visible_supply], [b_levelvisible], [b_fromcustomer], [b_logintime], [b_checkxy], [b_loginfee], [b_phone]) VALUES (829363337502725, N'002', N'002', NULL, NULL, NULL, NULL, N'002', 1, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL); +INSERT INTO [dbo].[b_user] ([b_id], [b_account], [b_name], [b_department], [b_subcode], [b_machineid], [b_bz], [b_password], [b_canuse], [b_othermachinelogin], [b_salesman], [b_docman], [b_dealman], [b_receivelevel], [b_paylevel], [b_visible_customer], [b_visible_supply], [b_levelvisible], [b_fromcustomer], [b_logintime], [b_checkxy], [b_loginfee], [b_phone]) VALUES (829363338100741, N'003', N'003', NULL, NULL, NULL, NULL, N'003', 1, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL); +INSERT INTO [dbo].[b_user] ([b_id], [b_account], [b_name], [b_department], [b_subcode], [b_machineid], [b_bz], [b_password], [b_canuse], [b_othermachinelogin], [b_salesman], [b_docman], [b_dealman], [b_receivelevel], [b_paylevel], [b_visible_customer], [b_visible_supply], [b_levelvisible], [b_fromcustomer], [b_logintime], [b_checkxy], [b_loginfee], [b_phone]) VALUES (829363338174469, N'004', N'004', NULL, NULL, NULL, NULL, N'004', 1, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL); +INSERT INTO [dbo].[b_user] ([b_id], [b_account], [b_name], [b_department], [b_subcode], [b_machineid], [b_bz], [b_password], [b_canuse], [b_othermachinelogin], [b_salesman], [b_docman], [b_dealman], [b_receivelevel], [b_paylevel], [b_visible_customer], [b_visible_supply], [b_levelvisible], [b_fromcustomer], [b_logintime], [b_checkxy], [b_loginfee], [b_phone]) VALUES (829363338235909, N'005', N'005', NULL, NULL, NULL, NULL, N'005', 1, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL); +INSERT INTO [dbo].[b_user] ([b_id], [b_account], [b_name], [b_department], [b_subcode], [b_machineid], [b_bz], [b_password], [b_canuse], [b_othermachinelogin], [b_salesman], [b_docman], [b_dealman], [b_receivelevel], [b_paylevel], [b_visible_customer], [b_visible_supply], [b_levelvisible], [b_fromcustomer], [b_logintime], [b_checkxy], [b_loginfee], [b_phone]) VALUES (829363338301445, N'006', N'006', NULL, NULL, NULL, NULL, N'006', 1, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL); +INSERT INTO [dbo].[b_user] ([b_id], [b_account], [b_name], [b_department], [b_subcode], [b_machineid], [b_bz], [b_password], [b_canuse], [b_othermachinelogin], [b_salesman], [b_docman], [b_dealman], [b_receivelevel], [b_paylevel], [b_visible_customer], [b_visible_supply], [b_levelvisible], [b_fromcustomer], [b_logintime], [b_checkxy], [b_loginfee], [b_phone]) VALUES (829363338362885, N'007', N'007', NULL, NULL, NULL, NULL, N'007', 1, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL); +INSERT INTO [dbo].[b_user] ([b_id], [b_account], [b_name], [b_department], [b_subcode], [b_machineid], [b_bz], [b_password], [b_canuse], [b_othermachinelogin], [b_salesman], [b_docman], [b_dealman], [b_receivelevel], [b_paylevel], [b_visible_customer], [b_visible_supply], [b_levelvisible], [b_fromcustomer], [b_logintime], [b_checkxy], [b_loginfee], [b_phone]) VALUES (829363338432517, N'008', N'008', NULL, NULL, NULL, NULL, N'008', 1, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL); +INSERT INTO [dbo].[b_user] ([b_id], [b_account], [b_name], [b_department], [b_subcode], [b_machineid], [b_bz], [b_password], [b_canuse], [b_othermachinelogin], [b_salesman], [b_docman], [b_dealman], [b_receivelevel], [b_paylevel], [b_visible_customer], [b_visible_supply], [b_levelvisible], [b_fromcustomer], [b_logintime], [b_checkxy], [b_loginfee], [b_phone]) VALUES (829363338502149, N'009', N'009', NULL, NULL, NULL, NULL, N'009', 1, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL); diff --git a/code/fms/.tmp-fkcheck/migrate_identity_to_snowflake.sql b/code/fms/.tmp-fkcheck/migrate_identity_to_snowflake.sql new file mode 100644 index 00000000..8ff0e251 --- /dev/null +++ b/code/fms/.tmp-fkcheck/migrate_identity_to_snowflake.sql @@ -0,0 +1,251 @@ +-- Migrate s_* tables: b_id identity(1,1) -> plain bigint (snowflake by app layer) +-- Generated: 2026-08-21 20:27:23.939 + +-- ===== s_autocode ===== +ALTER TABLE [dbo].[s_autocode] DROP CONSTRAINT [PK_s_autocode]; +ALTER TABLE [dbo].[s_autocode] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_autocode] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_autocode] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_autocode] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_autocode.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_autocode] ADD CONSTRAINT [PK_s_autocode] PRIMARY KEY ([b_id]); +GO + +-- ===== s_field ===== +ALTER TABLE [dbo].[s_field] DROP CONSTRAINT [PK_s_field]; +ALTER TABLE [dbo].[s_field] DROP CONSTRAINT [UQ_s_field_module_id]; +DROP INDEX [IX_s_field_module] ON [dbo].[s_field]; +ALTER TABLE [dbo].[s_field] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_field] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_field] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_field] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_field.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_field] ADD CONSTRAINT [PK_s_field] PRIMARY KEY ([b_id]); +ALTER TABLE [dbo].[s_field] ADD CONSTRAINT [UQ_s_field_module_id] UNIQUE (b_module_id,b_id); +CREATE INDEX [IX_s_field_module] ON [dbo].[s_field] (b_module_id,b_canuse,b_id); +GO + +-- ===== s_field_edit ===== +ALTER TABLE [dbo].[s_field_edit] DROP CONSTRAINT [PK_s_field_edit]; +DROP INDEX [IX_s_field_edit_order] ON [dbo].[s_field_edit]; +ALTER TABLE [dbo].[s_field_edit] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_field_edit] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_field_edit] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_field_edit] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_field_edit.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_field_edit] ADD CONSTRAINT [PK_s_field_edit] PRIMARY KEY ([b_id]); +CREATE INDEX [IX_s_field_edit_order] ON [dbo].[s_field_edit] (b_xh,b_id); +GO + +-- ===== s_field_group ===== +ALTER TABLE [dbo].[s_field_group] DROP CONSTRAINT [PK_s_field_group]; +ALTER TABLE [dbo].[s_field_group] DROP CONSTRAINT [CK_s_field_group_parent]; +ALTER TABLE [dbo].[s_field_group] DROP CONSTRAINT [UQ_s_field_group_module_id]; +ALTER TABLE [dbo].[s_field_group] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_field_group] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_field_group] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_field_group] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_field_group.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_field_group] ADD CONSTRAINT [PK_s_field_group] PRIMARY KEY ([b_id]); +ALTER TABLE [dbo].[s_field_group] ADD CONSTRAINT [CK_s_field_group_parent] CHECK ([b_parent_id] IS NULL OR [b_parent_id]<>[b_id]); +ALTER TABLE [dbo].[s_field_group] ADD CONSTRAINT [UQ_s_field_group_module_id] UNIQUE (b_module_id,b_id); +GO + +-- ===== s_field_query ===== +ALTER TABLE [dbo].[s_field_query] DROP CONSTRAINT [PK_s_field_query]; +DROP INDEX [IX_s_field_query_order] ON [dbo].[s_field_query]; +ALTER TABLE [dbo].[s_field_query] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_field_query] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_field_query] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_field_query] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_field_query.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_field_query] ADD CONSTRAINT [PK_s_field_query] PRIMARY KEY ([b_id]); +CREATE INDEX [IX_s_field_query_order] ON [dbo].[s_field_query] (b_canuse,b_xh,b_id); +GO + +-- ===== s_field_view ===== +ALTER TABLE [dbo].[s_field_view] DROP CONSTRAINT [PK_s_field_view]; +DROP INDEX [IX_s_field_view_order] ON [dbo].[s_field_view]; +ALTER TABLE [dbo].[s_field_view] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_field_view] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_field_view] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_field_view] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_field_view.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_field_view] ADD CONSTRAINT [PK_s_field_view] PRIMARY KEY ([b_id]); +CREATE INDEX [IX_s_field_view_order] ON [dbo].[s_field_view] (b_xh,b_id); +GO + +-- ===== s_i18n ===== +ALTER TABLE [dbo].[s_i18n] DROP CONSTRAINT [PK_s_i18n]; +ALTER TABLE [dbo].[s_i18n] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_i18n] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_i18n] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_i18n] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_i18n.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_i18n] ADD CONSTRAINT [PK_s_i18n] PRIMARY KEY ([b_id]); +GO + +-- ===== s_log_audit ===== +ALTER TABLE [dbo].[s_log_audit] DROP CONSTRAINT [PK_s_log_audit]; +ALTER TABLE [dbo].[s_log_audit] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_log_audit] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_log_audit] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_log_audit] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_log_audit.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_log_audit] ADD CONSTRAINT [PK_s_log_audit] PRIMARY KEY ([b_id]); +GO + +-- ===== s_log_audit_field ===== +ALTER TABLE [dbo].[s_log_audit_field] DROP CONSTRAINT [PK_s_log_audit_field]; +ALTER TABLE [dbo].[s_log_audit_field] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_log_audit_field] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_log_audit_field] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_log_audit_field] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_log_audit_field.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_log_audit_field] ADD CONSTRAINT [PK_s_log_audit_field] PRIMARY KEY ([b_id]); +GO + +-- ===== s_log_login ===== +ALTER TABLE [dbo].[s_log_login] DROP CONSTRAINT [PK_s_log_login]; +ALTER TABLE [dbo].[s_log_login] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_log_login] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_log_login] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_log_login] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_log_login.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_log_login] ADD CONSTRAINT [PK_s_log_login] PRIMARY KEY ([b_id]); +GO + +-- ===== s_log_technical ===== +ALTER TABLE [dbo].[s_log_technical] DROP CONSTRAINT [PK_s_log_technical]; +ALTER TABLE [dbo].[s_log_technical] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_log_technical] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_log_technical] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_log_technical] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_log_technical.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_log_technical] ADD CONSTRAINT [PK_s_log_technical] PRIMARY KEY ([b_id]); +GO + +-- ===== s_menu ===== +ALTER TABLE [dbo].[s_menu] DROP CONSTRAINT [PK_s_menu]; +ALTER TABLE [dbo].[s_menu] DROP CONSTRAINT [CK_s_menu_parent]; +ALTER TABLE [dbo].[s_menu] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_menu] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_menu] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_menu] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_menu.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_menu] ADD CONSTRAINT [PK_s_menu] PRIMARY KEY ([b_id]); +ALTER TABLE [dbo].[s_menu] ADD CONSTRAINT [CK_s_menu_parent] CHECK ([b_parent_id] IS NULL OR [b_parent_id]<>[b_id]); +GO + +-- ===== s_module ===== +ALTER TABLE [dbo].[s_module] DROP CONSTRAINT [PK_s_module]; +ALTER TABLE [dbo].[s_module] DROP CONSTRAINT [CK_s_module_parent]; +DROP INDEX [IX_s_module_parent] ON [dbo].[s_module]; +ALTER TABLE [dbo].[s_module] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_module] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_module] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_module] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_module.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_module] ADD CONSTRAINT [PK_s_module] PRIMARY KEY ([b_id]); +ALTER TABLE [dbo].[s_module] ADD CONSTRAINT [CK_s_module_parent] CHECK ([b_parent_id] IS NULL OR [b_parent_id]<>[b_id]); +CREATE INDEX [IX_s_module_parent] ON [dbo].[s_module] (b_parent_id,b_xh,b_canuse,b_id); +GO + +-- ===== s_power ===== +ALTER TABLE [dbo].[s_power] DROP CONSTRAINT [PK_s_power]; +ALTER TABLE [dbo].[s_power] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_power] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_power] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_power] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_power.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_power] ADD CONSTRAINT [PK_s_power] PRIMARY KEY ([b_id]); +GO + +-- ===== s_relation ===== +ALTER TABLE [dbo].[s_relation] DROP CONSTRAINT [PK_s_relation]; +ALTER TABLE [dbo].[s_relation] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_relation] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_relation] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_relation] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_relation.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_relation] ADD CONSTRAINT [PK_s_relation] PRIMARY KEY ([b_id]); +GO + +-- ===== s_role ===== +ALTER TABLE [dbo].[s_role] DROP CONSTRAINT [PK_s_role]; +ALTER TABLE [dbo].[s_role] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_role] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_role] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_role] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_role.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_role] ADD CONSTRAINT [PK_s_role] PRIMARY KEY ([b_id]); +GO + +-- ===== s_role_power ===== +ALTER TABLE [dbo].[s_role_power] DROP CONSTRAINT [PK_s_role_power]; +ALTER TABLE [dbo].[s_role_power] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_role_power] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_role_power] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_role_power] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_role_power.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_role_power] ADD CONSTRAINT [PK_s_role_power] PRIMARY KEY ([b_id]); +GO + +-- ===== s_role_scope ===== +ALTER TABLE [dbo].[s_role_scope] DROP CONSTRAINT [PK_s_role_scope]; +ALTER TABLE [dbo].[s_role_scope] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_role_scope] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_role_scope] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_role_scope] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_role_scope.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_role_scope] ADD CONSTRAINT [PK_s_role_scope] PRIMARY KEY ([b_id]); +GO + +-- ===== s_user_role ===== +ALTER TABLE [dbo].[s_user_role] DROP CONSTRAINT [PK_s_user_role]; +ALTER TABLE [dbo].[s_user_role] ADD [b_id_new] bigint NULL; +UPDATE [dbo].[s_user_role] SET [b_id_new] = [b_id]; +ALTER TABLE [dbo].[s_user_role] ALTER COLUMN [b_id_new] bigint NOT NULL; +ALTER TABLE [dbo].[s_user_role] DROP COLUMN [b_id]; +GO +EXEC sp_rename 'dbo.s_user_role.b_id_new', 'b_id', 'COLUMN'; +GO +ALTER TABLE [dbo].[s_user_role] ADD CONSTRAINT [PK_s_user_role] PRIMARY KEY ([b_id]); +GO +-- End of migration diff --git a/code/fms/.tmp-fkcheck/restore_fks.sql b/code/fms/.tmp-fkcheck/restore_fks.sql new file mode 100644 index 00000000..e7574a5d --- /dev/null +++ b/code/fms/.tmp-fkcheck/restore_fks.sql @@ -0,0 +1,110 @@ +-- Restore script for G3HD (FMS-NEW) physical foreign keys +-- Generated: 2026-08-21 19:14:20.882 +USE [FMS-NEW]; +GO +ALTER TABLE [dbo].[s_autocode] WITH CHECK ADD CONSTRAINT [FK_s_autocode_field] FOREIGN KEY([b_module_id], [b_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_autocode] WITH CHECK ADD CONSTRAINT [FK_s_autocode_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_edit] WITH CHECK ADD CONSTRAINT [FK_s_field_edit_field] FOREIGN KEY([b_module_id], [b_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_edit] WITH CHECK ADD CONSTRAINT [FK_s_field_edit_group] FOREIGN KEY([b_module_id], [b_group_id]) +REFERENCES [dbo].[s_field_group] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_edit] WITH CHECK ADD CONSTRAINT [FK_s_field_edit_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_group] WITH CHECK ADD CONSTRAINT [FK_s_field_group_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_group] WITH CHECK ADD CONSTRAINT [FK_s_field_group_parent] FOREIGN KEY([b_module_id], [b_parent_id]) +REFERENCES [dbo].[s_field_group] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field] WITH CHECK ADD CONSTRAINT [FK_s_field_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_query] WITH CHECK ADD CONSTRAINT [FK_s_field_query_field] FOREIGN KEY([b_field_module_id], [b_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_query] WITH CHECK ADD CONSTRAINT [FK_s_field_query_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_query] WITH CHECK ADD CONSTRAINT [FK_s_field_query_relation] FOREIGN KEY([b_relation_id]) +REFERENCES [dbo].[s_relation] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_view] WITH CHECK ADD CONSTRAINT [FK_s_field_view_field] FOREIGN KEY([b_module_id], [b_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_view] WITH CHECK ADD CONSTRAINT [FK_s_field_view_group] FOREIGN KEY([b_module_id], [b_group_id]) +REFERENCES [dbo].[s_field_group] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_field_view] WITH CHECK ADD CONSTRAINT [FK_s_field_view_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_log_audit_field] WITH CHECK ADD CONSTRAINT [FK_s_log_audit_field_event] FOREIGN KEY([b_event_id]) +REFERENCES [dbo].[s_log_audit] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_menu] WITH CHECK ADD CONSTRAINT [FK_s_menu_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_menu] WITH CHECK ADD CONSTRAINT [FK_s_menu_parent] FOREIGN KEY([b_parent_id]) +REFERENCES [dbo].[s_menu] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_module] WITH CHECK ADD CONSTRAINT [FK_s_module_parent] FOREIGN KEY([b_parent_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_power] WITH CHECK ADD CONSTRAINT [FK_s_power_field] FOREIGN KEY([b_module_id], [b_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_power] WITH CHECK ADD CONSTRAINT [FK_s_power_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_relation] WITH CHECK ADD CONSTRAINT [FK_s_relation_junction_module] FOREIGN KEY([b_junction_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_relation] WITH CHECK ADD CONSTRAINT [FK_s_relation_junction_module_field] FOREIGN KEY([b_junction_module_id], [b_junction_module_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_relation] WITH CHECK ADD CONSTRAINT [FK_s_relation_junction_target_field] FOREIGN KEY([b_junction_module_id], [b_junction_target_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_relation] WITH CHECK ADD CONSTRAINT [FK_s_relation_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_relation] WITH CHECK ADD CONSTRAINT [FK_s_relation_module_field] FOREIGN KEY([b_module_id], [b_module_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_relation] WITH CHECK ADD CONSTRAINT [FK_s_relation_order_field] FOREIGN KEY([b_order_module_id], [b_order_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_relation] WITH CHECK ADD CONSTRAINT [FK_s_relation_order_module] FOREIGN KEY([b_order_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_relation] WITH CHECK ADD CONSTRAINT [FK_s_relation_target_field] FOREIGN KEY([b_target_module_id], [b_target_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_relation] WITH CHECK ADD CONSTRAINT [FK_s_relation_target_module] FOREIGN KEY([b_target_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_role_power] WITH CHECK ADD CONSTRAINT [FK_s_role_power_power] FOREIGN KEY([b_power_id]) +REFERENCES [dbo].[s_power] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_role_power] WITH CHECK ADD CONSTRAINT [FK_s_role_power_role] FOREIGN KEY([b_role_id]) +REFERENCES [dbo].[s_role] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_role_scope] WITH CHECK ADD CONSTRAINT [FK_s_role_scope_field] FOREIGN KEY([b_module_id], [b_scope_field_id]) +REFERENCES [dbo].[s_field] ([b_module_id], [b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_role_scope] WITH CHECK ADD CONSTRAINT [FK_s_role_scope_module] FOREIGN KEY([b_module_id]) +REFERENCES [dbo].[s_module] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_role_scope] WITH CHECK ADD CONSTRAINT [FK_s_role_scope_role] FOREIGN KEY([b_role_id]) +REFERENCES [dbo].[s_role] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +ALTER TABLE [dbo].[s_user_role] WITH CHECK ADD CONSTRAINT [FK_s_user_role_role] FOREIGN KEY([b_role_id]) +REFERENCES [dbo].[s_role] ([b_id]) ON DELETE NO_ACTION ON UPDATE NO_ACTION; +GO +-- End of restore script diff --git a/code/fms/FMS新系统核心表结构设计.md b/code/fms/FMS新系统核心表结构设计.md index 2e4ac9e1..6212b1f1 100644 --- a/code/fms/FMS新系统核心表结构设计.md +++ b/code/fms/FMS新系统核心表结构设计.md @@ -15,7 +15,7 @@ 其余约定: -- 内部主键 `b_id bigint identity(1, 1)`,跨环境稳定标识使用 `b_code`(参考旧系统)。 +- 内部主键 `b_id bigint` 由应用层雪花算法生成(全局唯一、带时间序,配置跨系统导出/导入时引用原样保留、无需重映射);跨环境稳定标识使用 `b_code`(参考旧系统)。 - 全系统不建立物理外键;关联字段保留索引,引用完整性、跨模块一致性、停用与删除保护统一由服务层维护。 - 布尔语义使用 `int`(0/1),与旧系统 `s_module` 风格一致。 - 时间统一使用 UTC:字段类型为 `datetime2`,默认 `sysutcdatetime()`;前端按用户时区转换显示。 @@ -55,7 +55,7 @@ ```sql create table s_module ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_code varchar(50) not null, -- 模块编码(全局唯一) b_parent_id bigint null, -- 父模块(配置树位置) b_name nvarchar(100) not null, -- 模块名称 @@ -83,7 +83,7 @@ create table s_module ( or (b_module_type = 'query' and b_viewtable is not null and b_savetable is null and b_delete_policy is null) ), constraint ck_s_module_parent check (b_parent_id is null or b_parent_id <> b_id), - constraint fk_s_module_parent foreign key (b_parent_id) references s_module (b_id) + -- constraint fk_s_module_parent foreign key (b_parent_id) references s_module (b_id) ); create index ix_s_module_parent on s_module (b_parent_id, b_xh, b_canuse, b_id); @@ -116,7 +116,7 @@ hard 临时记录:校验权限后直接物理删除 ```sql create table s_field ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_module_id bigint not null, -- 所属数据模块 b_field varchar(50) not null, -- 物理列名(与真实表列一致) b_name nvarchar(100) not null, -- 字段名称 @@ -139,7 +139,7 @@ create table s_field ( constraint pk_s_field primary key (b_id), constraint ux_s_field_module_field unique (b_module_id, b_field), constraint ux_s_field_module_id unique (b_module_id, b_id), - constraint fk_s_field_module foreign key (b_module_id) references s_module (b_id), + -- constraint fk_s_field_module foreign key (b_module_id) references s_module (b_id), constraint ck_s_field_type check (b_type in ('input', 'textarea', 'number', 'money', 'date', 'datetime', 'checkbox', 'select', 'selectId')), constraint ck_s_field_dbtype check (b_dbtype in ('varchar', 'nvarchar', 'char', 'nchar', 'int', 'bigint', 'smallint', 'tinyint', 'decimal', 'numeric', 'float', 'real', 'date', 'datetime', 'datetime2', 'bit', 'text', 'ntext', 'uniqueidentifier')), constraint ck_s_field_writemode check (b_writemode in ('direct', 'createOnly', 'readOnly', 'computed', 'system')) @@ -187,7 +187,7 @@ selectId 关联模块选择器,保存目标记录 ID ```sql create table s_field_group ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_module_id bigint not null, -- 所属数据模块 b_parent_id bigint null, -- 父分组(分组树) b_title nvarchar(100) not null, -- 分组标题 @@ -197,8 +197,8 @@ create table s_field_group ( constraint pk_s_field_group primary key (b_id), constraint ux_s_field_group_module_id unique (b_module_id, b_id), constraint ck_s_field_group_parent check (b_parent_id is null or b_parent_id <> b_id), - constraint fk_s_field_group_module foreign key (b_module_id) references s_module (b_id), - constraint fk_s_field_group_parent foreign key (b_module_id, b_parent_id) references s_field_group (b_module_id, b_id) + -- constraint fk_s_field_group_module foreign key (b_module_id) references s_module (b_id), + -- constraint fk_s_field_group_parent foreign key (b_module_id, b_parent_id) references s_field_group (b_module_id, b_id) ); create index ix_s_field_group_module on s_field_group (b_module_id, b_xh, b_canuse); @@ -210,7 +210,7 @@ create index ix_s_field_group_module on s_field_group (b_module_id, b_xh, b_canu ```sql create table s_field_view ( - b_id bigint identity(1, 1) not null, + b_id bigint not null, b_module_id bigint not null, -- 所属数据模块(与字段、分组一致) b_field_id bigint not null, -- 字段 b_group_id bigint null, -- 列表分组 @@ -220,9 +220,9 @@ create table s_field_view ( b_width int null, -- 列宽(px) constraint pk_s_field_view primary key (b_id), constraint ux_s_field_view_field unique (b_module_id, b_field_id), - constraint fk_s_field_view_module foreign key (b_module_id) references s_module (b_id), - constraint fk_s_field_view_field foreign key (b_module_id, b_field_id) references s_field (b_module_id, b_id), - constraint fk_s_field_view_group foreign key (b_module_id, b_group_id) references s_field_group (b_module_id, b_id) + -- constraint fk_s_field_view_module foreign key (b_module_id) references s_module (b_id), + -- constraint fk_s_field_view_field foreign key (b_module_id, b_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_field_view_group foreign key (b_module_id, b_group_id) references s_field_group (b_module_id, b_id) ); create index ix_s_field_view_order on s_field_view (b_xh, b_id); @@ -234,7 +234,7 @@ create index ix_s_field_view_order on s_field_view (b_xh, b_id); ```sql create table s_field_edit ( - b_id bigint identity(1, 1) not null, + b_id bigint not null, b_module_id bigint not null, -- 所属数据模块(与字段、分组一致) b_field_id bigint not null, -- 字段 b_group_id bigint null, -- 表单分组 @@ -245,9 +245,9 @@ create table s_field_edit ( b_xh int not null default 0, -- 表单排序 constraint pk_s_field_edit primary key (b_id), constraint ux_s_field_edit_field unique (b_module_id, b_field_id), - constraint fk_s_field_edit_module foreign key (b_module_id) references s_module (b_id), - constraint fk_s_field_edit_field foreign key (b_module_id, b_field_id) references s_field (b_module_id, b_id), - constraint fk_s_field_edit_group foreign key (b_module_id, b_group_id) references s_field_group (b_module_id, b_id) + -- constraint fk_s_field_edit_module foreign key (b_module_id) references s_module (b_id), + -- constraint fk_s_field_edit_field foreign key (b_module_id, b_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_field_edit_group foreign key (b_module_id, b_group_id) references s_field_group (b_module_id, b_id) ); create index ix_s_field_edit_order on s_field_edit (b_xh, b_id); @@ -261,7 +261,7 @@ create index ix_s_field_edit_order on s_field_edit (b_xh, b_id); ```sql create table s_field_query ( - b_id bigint identity(1, 1) not null, + b_id bigint not null, b_module_id bigint not null, -- 查询所属模块 b_field_module_id bigint not null, -- 条件字段所属模块 b_field_id bigint not null, -- 条件字段 @@ -274,8 +274,8 @@ create table s_field_query ( b_xh int not null default 0, -- 查询条件排序 constraint pk_s_field_query primary key (b_id), constraint ux_s_field_query_condition unique (b_module_id, b_field_module_id, b_field_id, b_relation_id, b_operator, b_condition_no), - constraint fk_s_field_query_module foreign key (b_module_id) references s_module (b_id), - constraint fk_s_field_query_field foreign key (b_field_module_id, b_field_id) references s_field (b_module_id, b_id) + -- constraint fk_s_field_query_module foreign key (b_module_id) references s_module (b_id), + -- constraint fk_s_field_query_field foreign key (b_field_module_id, b_field_id) references s_field (b_module_id, b_id) ); create index ix_s_field_query_order on s_field_query (b_canuse, b_xh, b_id); @@ -289,7 +289,7 @@ create index ix_s_field_query_order on s_field_query (b_canuse, b_xh, b_id); ```sql create table s_relation ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_code varchar(50) not null, -- 跨环境稳定关系编码 b_module_id bigint not null, -- 关系端 A 模块 b_target_module_id bigint not null, -- 关系端 B 模块 @@ -309,15 +309,15 @@ create table s_relation ( b_xh int not null default 0, -- 排序号 constraint pk_s_relation primary key (b_id), constraint ux_s_relation_code unique (b_code), - constraint fk_s_relation_module foreign key (b_module_id) references s_module (b_id), - constraint fk_s_relation_target_module foreign key (b_target_module_id) references s_module (b_id), - constraint fk_s_relation_module_field foreign key (b_module_id, b_module_field_id) references s_field (b_module_id, b_id), - constraint fk_s_relation_target_field foreign key (b_target_module_id, b_target_field_id) references s_field (b_module_id, b_id), - constraint fk_s_relation_junction_module foreign key (b_junction_module_id) references s_module (b_id), - constraint fk_s_relation_junction_module_field foreign key (b_junction_module_id, b_junction_module_field_id) references s_field (b_module_id, b_id), - constraint fk_s_relation_junction_target_field foreign key (b_junction_module_id, b_junction_target_field_id) references s_field (b_module_id, b_id), - constraint fk_s_relation_order_module foreign key (b_order_module_id) references s_module (b_id), - constraint fk_s_relation_order_field foreign key (b_order_module_id, b_order_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_relation_module foreign key (b_module_id) references s_module (b_id), + -- constraint fk_s_relation_target_module foreign key (b_target_module_id) references s_module (b_id), + -- constraint fk_s_relation_module_field foreign key (b_module_id, b_module_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_relation_target_field foreign key (b_target_module_id, b_target_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_relation_junction_module foreign key (b_junction_module_id) references s_module (b_id), + -- constraint fk_s_relation_junction_module_field foreign key (b_junction_module_id, b_junction_module_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_relation_junction_target_field foreign key (b_junction_module_id, b_junction_target_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_relation_order_module foreign key (b_order_module_id) references s_module (b_id), + -- constraint fk_s_relation_order_field foreign key (b_order_module_id, b_order_field_id) references s_field (b_module_id, b_id), constraint ux_s_relation_key unique (b_module_id, b_target_module_id, b_module_field_id, b_target_field_id), constraint ck_s_relation_self_fields check (b_module_id <> b_target_module_id or b_module_field_id <> b_target_field_id), constraint ck_s_relation_type check (b_relation_type in ('one-to-one', 'one-to-many', 'many-to-one', 'many-to-many')), @@ -341,8 +341,7 @@ create table s_relation ( create index ix_s_relation_target on s_relation (b_target_module_id); -alter table s_field_query add constraint fk_s_field_query_relation - foreign key (b_relation_id) references s_relation (b_id); +-- 不建物理外键:s_field_query.b_relation_id -> s_relation.b_id 为逻辑外键,由服务层校验 ``` 关系配置必须满足以下规则: @@ -363,7 +362,7 @@ alter table s_field_query add constraint fk_s_field_query_relation ```sql create table s_menu ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_parent_id bigint null, -- 父菜单(导航树自引用) b_code varchar(50) not null, -- 菜单编码 b_name nvarchar(100) not null, -- 菜单名称 @@ -378,8 +377,8 @@ create table s_menu ( constraint pk_s_menu primary key (b_id), constraint ux_s_menu_code unique (b_code), constraint ck_s_menu_parent check (b_parent_id is null or b_parent_id <> b_id), - constraint fk_s_menu_parent foreign key (b_parent_id) references s_menu (b_id), - constraint fk_s_menu_module foreign key (b_module_id) references s_module (b_id) + -- constraint fk_s_menu_parent foreign key (b_parent_id) references s_menu (b_id), + -- constraint fk_s_menu_module foreign key (b_module_id) references s_module (b_id) ); create index ix_s_menu_parent on s_menu (b_parent_id, b_xh, b_canuse); @@ -398,7 +397,7 @@ create index ix_s_menu_module on s_menu (b_module_id); ```sql create table s_autocode ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_module_id bigint not null, -- 所属数据模块 b_field_id bigint not null, -- 目标字段 b_prefix nvarchar(100) not null default '', -- 前缀 @@ -412,8 +411,8 @@ create table s_autocode ( b_canuse int not null default 1, -- 是否启用 constraint pk_s_autocode primary key (b_id), constraint ux_s_autocode_module_field unique (b_module_id, b_field_id), - constraint fk_s_autocode_module foreign key (b_module_id) references s_module (b_id), - constraint fk_s_autocode_field foreign key (b_module_id, b_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_autocode_module foreign key (b_module_id) references s_module (b_id), + -- constraint fk_s_autocode_field foreign key (b_module_id, b_field_id) references s_field (b_module_id, b_id), constraint ck_s_autocode_resettype check (b_resettype in ('none', 'day', 'month', 'year')), constraint ck_s_autocode_values check (b_seqwidth > 0 and b_startvalue >= 0 and b_currentvalue >= 0) ); @@ -425,7 +424,7 @@ create table s_autocode ( ```sql create table s_role ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_code varchar(50) not null, -- 角色编码 b_name nvarchar(100) not null, -- 角色名称 b_i18n varchar(150) null, -- 显示名多语言 key @@ -447,14 +446,14 @@ create table s_role ( ```sql create table s_user_role ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_user_id bigint not null, -- 用户(逻辑外键,当前身份系统) b_role_id bigint not null, -- 角色 b_canuse int not null default 1, -- 是否启用该关系 b_inputdatetime datetime2 null default sysutcdatetime(), constraint pk_s_user_role primary key (b_id), constraint ux_s_user_role unique (b_user_id, b_role_id), - constraint fk_s_user_role_role foreign key (b_role_id) references s_role (b_id) + -- constraint fk_s_user_role_role foreign key (b_role_id) references s_role (b_id) ); create index ix_s_user_role_role on s_user_role (b_role_id, b_canuse); @@ -464,7 +463,7 @@ create index ix_s_user_role_role on s_user_role (b_role_id, b_canuse); ```sql create table s_power ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_code varchar(50) not null, -- 权限编码(如 booking.confirm) b_name nvarchar(100) not null, -- 权限名称 b_i18n varchar(150) null, -- 显示名多语言 key @@ -477,8 +476,8 @@ create table s_power ( b_xh int not null default 0, -- 排序号 constraint pk_s_power primary key (b_id), constraint ux_s_power_code unique (b_code), - constraint fk_s_power_module foreign key (b_module_id) references s_module (b_id), - constraint fk_s_power_field foreign key (b_module_id, b_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_power_module foreign key (b_module_id) references s_module (b_id), + -- constraint fk_s_power_field foreign key (b_module_id, b_field_id) references s_field (b_module_id, b_id), constraint ck_s_power_type check (b_power_type in ('menu', 'module', 'action', 'field')), constraint ck_s_power_shape check ( (b_power_type = 'menu' and b_resource is not null and b_module_id is null and b_field_id is null and b_operation = 'access') @@ -497,7 +496,7 @@ create index ix_s_power_resource on s_power (b_power_type, b_resource, b_canuse) ```sql create table s_role_power ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_role_id bigint not null, -- 角色 b_power_id bigint not null, -- 权限点 b_effect varchar(10) not null default 'allow', -- allow/deny @@ -505,8 +504,8 @@ create table s_role_power ( b_inputdatetime datetime2 null default sysutcdatetime(), constraint pk_s_role_power primary key (b_id), constraint ux_s_role_power unique (b_role_id, b_power_id), - constraint fk_s_role_power_role foreign key (b_role_id) references s_role (b_id), - constraint fk_s_role_power_power foreign key (b_power_id) references s_power (b_id), + -- constraint fk_s_role_power_role foreign key (b_role_id) references s_role (b_id), + -- constraint fk_s_role_power_power foreign key (b_power_id) references s_power (b_id), constraint ck_s_role_power_effect check (b_effect in ('allow', 'deny')) ); @@ -519,7 +518,7 @@ create index ix_s_role_power_power on s_role_power (b_power_id, b_canuse); ```sql create table s_role_scope ( - b_id bigint identity(1, 1) not null, + b_id bigint not null, b_role_id bigint not null, -- 角色 b_module_id bigint not null, -- 数据模块 b_operation varchar(20) not null, -- read/update/delete/export @@ -531,9 +530,9 @@ create table s_role_scope ( b_inputdatetime datetime2 null default sysutcdatetime(), constraint pk_s_role_scope primary key (b_id), constraint ux_s_role_scope unique (b_role_id, b_module_id, b_operation, b_scope_type, b_scope_field_id, b_scope_no), - constraint fk_s_role_scope_role foreign key (b_role_id) references s_role (b_id), - constraint fk_s_role_scope_module foreign key (b_module_id) references s_module (b_id), - constraint fk_s_role_scope_field foreign key (b_module_id, b_scope_field_id) references s_field (b_module_id, b_id), + -- constraint fk_s_role_scope_role foreign key (b_role_id) references s_role (b_id), + -- constraint fk_s_role_scope_module foreign key (b_module_id) references s_module (b_id), + -- constraint fk_s_role_scope_field foreign key (b_module_id, b_scope_field_id) references s_field (b_module_id, b_id), constraint ck_s_role_scope_operation check (b_operation in ('read', 'update', 'delete', 'export')), constraint ck_s_role_scope_type check (b_scope_type in ('all', 'creator', 'owner', 'department', 'department-tree', 'custom')), constraint ck_s_role_scope_no check (b_scope_no > 0), @@ -562,7 +561,7 @@ create index ix_s_role_scope_role on s_role_scope (b_role_id, b_module_id, b_ope ```sql create table s_i18n ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_key varchar(150) not null, -- 资源 key(如 booking.name) b_locale varchar(20) not null, -- 语言区域(如 zh-CN/en-US) b_value nvarchar(500) not null, -- 该语言下的显示文本 @@ -581,8 +580,7 @@ create index ix_s_i18n_locale on s_i18n (b_locale, b_canuse); ```sql create table s_log_technical ( - b_id bigint identity(1, 1) not null, -- 内部主键 - b_org_id varchar(50) null, -- 租户/组织标识(日志来源) + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_user_id bigint null, -- 操作用户(逻辑外键) b_account varchar(50) null, -- 登录名快照 b_sourcetype varchar(20) null, -- 来源类型:web/api @@ -604,7 +602,6 @@ create table s_log_technical ( ); create index ix_s_log_technical_trace on s_log_technical (b_trace_id, b_occurdatetime desc); -create index ix_s_log_technical_org on s_log_technical (b_org_id, b_occurdatetime desc); create index ix_s_log_technical_level on s_log_technical (b_loglevel, b_occurdatetime desc); ``` @@ -614,8 +611,7 @@ create index ix_s_log_technical_level on s_log_technical (b_loglevel, b_occurdat ```sql create table s_log_login ( - b_id bigint identity(1, 1) not null, -- 内部主键 - b_org_id varchar(50) null, -- 租户/组织标识 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_user_id bigint null, -- 用户(逻辑外键) b_account varchar(50) null, -- 登录名 b_login_type varchar(20) not null, -- 登录类型:password/token/oidc/ldap @@ -647,7 +643,7 @@ create index ix_s_log_login_result on s_log_login (b_login_result, b_occurdateti ```sql create table s_log_audit ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_event_code varchar(50) not null, -- 业务事件编码(如 booking.submit) b_module_code varchar(50) null, -- 模块编码 b_data_id varchar(50) null, -- 被审计的业务数据 ID @@ -672,7 +668,7 @@ create index ix_s_log_audit_trace on s_log_audit (b_trace_id); ```sql create table s_log_audit_field ( - b_id bigint identity(1, 1) not null, -- 内部主键 + b_id bigint not null, -- 雪花主键(应用层生成,全局唯一) b_event_id bigint not null, -- 所属审计事件 b_module_code varchar(50) null, -- 模块编码 b_data_id varchar(50) null, -- 业务数据 ID @@ -684,7 +680,7 @@ create table s_log_audit_field ( b_after_display nvarchar(500) null, -- 变更后显示值 b_visibility varchar(20) not null default 'internal', -- 可见性:internal 内部/customer 客户可见 constraint pk_s_log_audit_field primary key (b_id), - constraint fk_s_log_audit_field_event foreign key (b_event_id) references s_log_audit (b_id) + -- constraint fk_s_log_audit_field_event foreign key (b_event_id) references s_log_audit (b_id) ); create index ix_s_log_audit_field_event on s_log_audit_field (b_event_id); @@ -721,9 +717,13 @@ create index ix_s_log_audit_field_customer on s_log_audit_field (b_module_code, - 登录日志与业务审计属于安全与合规数据,低频但必须长期留存,落库保存;审计字段明细在字段发生修改时全量写入,不做按字段开关,数据量按变更频率自然收敛。 - 三类日志统一以 `b_request_id` / `b_trace_id` 串联,便于从业务审计反查技术日志。 +### 4.9 为什么元数据表主键采用雪花 ID + +配置数据(模块、字段、关系、菜单、权限等)需要跨系统导入导出与搬迁。若使用数据库自增主键,两个系统各自从 1 计数,搬入目标系统后必然 ID 冲突,模块子树下所有引用字段(`b_module_id`、`b_field_id`、`b_relation_id` 等 30+ 处)都必须重映射,而重映射正是搬迁工具中 bug 的高发区。雪花 ID 全局唯一,导出/导入时子树引用原样保留,无需重映射。元数据表行数少,雪花随机写入对聚簇索引的影响可忽略;`b_code` 仍作为跨环境稳定业务键,负责冲突检测与 dev/prod 对齐。业务数据表(模块生成的物理表)和用户表不在此列,用户表见 5.1。 + ## 5. 逻辑外键清单 -元数据表已建物理外键;以下为业务侧或跨系统引用,由服务层校验: +全系统(含元数据表)不建立物理外键;DDL 中的 `fk_*` 约束以注释形式保留,仅作逻辑关系参考,建表时不创建。以下引用统一由服务层校验: | 表 | 字段 | 逻辑目标 | | --- | --- | --- | @@ -750,6 +750,17 @@ create index ix_s_log_audit_field_customer on s_log_audit_field (b_module_code, -> 删除或停用前是否仍被其他资源引用 ``` +### 5.1 用户与身份系统(b_user) + +`b_user` 不属于 FMS 业务库,由独立的身份系统维护;FMS 侧只保存用户引用 `b_user_id`(bigint,不透明 ID),不解析账号,也不关心身份系统内部的 ID 生成方案。 + +身份系统用户表约定: + +- `b_id` bigint 主键:单库自增 `identity(1,1)` 即可;若未来用户需要跨系统迁移或合并,再改为全局唯一 ID(如雪花),FMS 侧无需任何改动。 +- `b_account` nvarchar(50) 唯一键:账号是自然键,允许改名;**绝不用账号做主键**。 +- 账号改名只更新用户表自身,引用 `b_user_id` 的业务数据(`s_user_role`、`s_log_*`)不受影响。 +- 日志表冗余保存 `b_account` 快照(`s_log_login.b_account`、`s_log_technical.b_account`),保证改名后历史日志仍可读、可追溯。 + ## 6. 与旧系统的映射对照 | 旧系统 | 本方案 | 变化 | diff --git a/code/fms/fms-api/config/migrations/003_create_login_log.sql b/code/fms/fms-api/config/migrations/003_create_login_log.sql index 9dc12ade..00b8d65a 100644 --- a/code/fms/fms-api/config/migrations/003_create_login_log.sql +++ b/code/fms/fms-api/config/migrations/003_create_login_log.sql @@ -1,40 +1,46 @@ /* - 登录日志表 s_login_log:记录每次登录尝试(成功/失败)的客户端环境与结果,用于登录审计。 + 登录日志表 s_log_login:记录每次登录尝试(成功/失败)的客户端环境与结果,用于登录审计。 后端在登录时统一采集 IP、IP 归属地、浏览器/版本、操作系统/版本、设备类型、原始 User-Agent, 并通过通用 saveobjt 接口写入本表;前端通过分页接口只读展示。 表名使用 s_ 前缀以区别于业务模块表(b_)。 + 说明:表名原为 s_login_log,新系统统一更名为 s_log_login(见《FMS新系统核心表结构设计.md》 + 3.17 与 5.2 旧表映射),列结构同步更新(新增 b_login_type、b_request_id、b_trace_id, + 时间列由 b_login_time 改为 b_occurdatetime,UTC 存储)。 + 可重复执行:通过 sys.tables 存在性判断避免重复建表。 */ SET NOCOUNT ON; BEGIN TRANSACTION; - IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = N's_login_log') + IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = N's_log_login') BEGIN - CREATE TABLE dbo.s_login_log ( + CREATE TABLE dbo.s_log_login ( b_id bigint NOT NULL, - b_org_id nvarchar(50) NULL, b_user_id bigint NULL, - b_account nvarchar(50) NULL, - b_login_time datetime NOT NULL CONSTRAINT DF_s_login_log_time DEFAULT (GETDATE()), - b_ip nvarchar(50) NULL, - b_ip_location nvarchar(200) NULL, - b_browser nvarchar(100) NULL, - b_browser_version nvarchar(50) NULL, - b_os nvarchar(100) NULL, - b_os_version nvarchar(50) NULL, - b_device_type nvarchar(20) NULL, - b_user_agent nvarchar(max) NULL, - b_login_result int NOT NULL CONSTRAINT DF_s_login_log_result DEFAULT (0), + b_account varchar(50) NULL, + b_login_type varchar(20) NOT NULL, -- 登录类型:password/token/oidc/ldap + b_login_result int NOT NULL CONSTRAINT DF_s_log_login_result DEFAULT (0), b_fail_reason nvarchar(200) NULL, - b_session_id nvarchar(50) NULL, - CONSTRAINT PK_s_login_log PRIMARY KEY (b_id) + b_ip varchar(50) NULL, + b_ip_location nvarchar(200) NULL, + b_browser varchar(100) NULL, + b_browser_version varchar(50) NULL, + b_os varchar(100) NULL, + b_os_version varchar(50) NULL, + b_device_type varchar(20) NULL, + b_user_agent nvarchar(max) NULL, + b_session_id varchar(50) NULL, + b_request_id varchar(64) NULL, + b_trace_id varchar(64) NULL, + b_occurdatetime datetime2 NULL CONSTRAINT DF_s_log_login_occur DEFAULT (sysutcdatetime()), + CONSTRAINT PK_s_log_login PRIMARY KEY (b_id) ); - CREATE INDEX IX_s_login_log_time ON dbo.s_login_log (b_login_time DESC, b_id); - CREATE INDEX IX_s_login_log_account ON dbo.s_login_log (b_account, b_login_time DESC); - CREATE INDEX IX_s_login_log_result ON dbo.s_login_log (b_login_result, b_login_time DESC); + CREATE INDEX IX_s_log_login_user ON dbo.s_log_login (b_user_id, b_occurdatetime DESC); + CREATE INDEX IX_s_log_login_account ON dbo.s_log_login (b_account, b_occurdatetime DESC); + CREATE INDEX IX_s_log_login_result ON dbo.s_log_login (b_login_result, b_occurdatetime DESC); END COMMIT TRANSACTION; diff --git a/code/fms/fms-api/config/migrations/100_initialize_fms_new.sql b/code/fms/fms-api/config/migrations/100_initialize_fms_new.sql index 9cdc3928..7c6c6e5d 100644 --- a/code/fms/fms-api/config/migrations/100_initialize_fms_new.sql +++ b/code/fms/fms-api/config/migrations/100_initialize_fms_new.sql @@ -9,7 +9,7 @@ BEGIN TRANSACTION; IF OBJECT_ID(N'dbo.b_user', N'U') IS NULL BEGIN CREATE TABLE dbo.b_user ( - b_id bigint NOT NULL, + b_id bigint IDENTITY(1,1) NOT NULL, b_account nvarchar(50) NOT NULL, b_name nvarchar(50) NULL, b_department nvarchar(50) NULL, @@ -56,7 +56,7 @@ END; IF OBJECT_ID(N'dbo.s_module', N'U') IS NULL BEGIN CREATE TABLE dbo.s_module ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_code varchar(50) NOT NULL, b_parent_id bigint NULL, b_name nvarchar(100) NOT NULL, @@ -92,7 +92,7 @@ END; IF OBJECT_ID(N'dbo.s_field', N'U') IS NULL BEGIN CREATE TABLE dbo.s_field ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_module_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_name nvarchar(100) NOT NULL, @@ -125,7 +125,7 @@ END; IF OBJECT_ID(N'dbo.s_field_group', N'U') IS NULL BEGIN CREATE TABLE dbo.s_field_group ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_module_id bigint NOT NULL, b_parent_id bigint NULL, b_title nvarchar(100) NOT NULL, @@ -142,7 +142,7 @@ END; IF OBJECT_ID(N'dbo.s_field_view', N'U') IS NULL BEGIN CREATE TABLE dbo.s_field_view ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_module_id bigint NOT NULL, b_field_id bigint NOT NULL, b_group_id bigint NULL, @@ -159,7 +159,7 @@ END; IF OBJECT_ID(N'dbo.s_field_edit', N'U') IS NULL BEGIN CREATE TABLE dbo.s_field_edit ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_module_id bigint NOT NULL, b_field_id bigint NOT NULL, b_group_id bigint NULL, @@ -177,7 +177,7 @@ END; IF OBJECT_ID(N'dbo.s_field_query', N'U') IS NULL BEGIN CREATE TABLE dbo.s_field_query ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_module_id bigint NOT NULL, b_field_module_id bigint NOT NULL, b_field_id bigint NOT NULL, @@ -198,7 +198,7 @@ END; IF OBJECT_ID(N'dbo.s_relation', N'U') IS NULL BEGIN CREATE TABLE dbo.s_relation ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_code varchar(50) NOT NULL, b_module_id bigint NOT NULL, b_target_module_id bigint NOT NULL, @@ -238,7 +238,7 @@ END; IF OBJECT_ID(N'dbo.s_menu', N'U') IS NULL BEGIN CREATE TABLE dbo.s_menu ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_parent_id bigint NULL, b_code varchar(50) NOT NULL, b_name nvarchar(100) NOT NULL, @@ -262,7 +262,7 @@ END; IF OBJECT_ID(N'dbo.s_autocode', N'U') IS NULL BEGIN CREATE TABLE dbo.s_autocode ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_module_id bigint NOT NULL, b_field_id bigint NOT NULL, b_prefix nvarchar(100) NOT NULL CONSTRAINT DF_s_autocode_prefix DEFAULT (N''), @@ -284,7 +284,7 @@ END; IF OBJECT_ID(N'dbo.s_role', N'U') IS NULL BEGIN CREATE TABLE dbo.s_role ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_code varchar(50) NOT NULL, b_name nvarchar(100) NOT NULL, b_i18n varchar(150) NULL, @@ -305,7 +305,7 @@ END; IF OBJECT_ID(N'dbo.s_user_role', N'U') IS NULL BEGIN CREATE TABLE dbo.s_user_role ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_user_id bigint NOT NULL, b_role_id bigint NOT NULL, b_canuse int NOT NULL CONSTRAINT DF_s_user_role_canuse DEFAULT (1), @@ -319,7 +319,7 @@ END; IF OBJECT_ID(N'dbo.s_power', N'U') IS NULL BEGIN CREATE TABLE dbo.s_power ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_code varchar(50) NOT NULL, b_name nvarchar(100) NOT NULL, b_i18n varchar(150) NULL, @@ -346,7 +346,7 @@ END; IF OBJECT_ID(N'dbo.s_role_power', N'U') IS NULL BEGIN CREATE TABLE dbo.s_role_power ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_role_id bigint NOT NULL, b_power_id bigint NOT NULL, b_effect varchar(10) NOT NULL CONSTRAINT DF_s_role_power_effect DEFAULT ('allow'), @@ -362,7 +362,7 @@ END; IF OBJECT_ID(N'dbo.s_role_scope', N'U') IS NULL BEGIN CREATE TABLE dbo.s_role_scope ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_role_id bigint NOT NULL, b_module_id bigint NOT NULL, b_operation varchar(20) NOT NULL, @@ -389,7 +389,7 @@ END; IF OBJECT_ID(N'dbo.s_i18n', N'U') IS NULL BEGIN CREATE TABLE dbo.s_i18n ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_key varchar(150) NOT NULL, b_locale varchar(20) NOT NULL, b_value nvarchar(500) NOT NULL, @@ -404,8 +404,7 @@ END; IF OBJECT_ID(N'dbo.s_log_technical', N'U') IS NULL BEGIN CREATE TABLE dbo.s_log_technical ( - b_id bigint IDENTITY(1,1) NOT NULL, - b_org_id varchar(50) NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_user_id bigint NULL, b_account varchar(50) NULL, b_sourcetype varchar(20) NULL, @@ -426,15 +425,13 @@ BEGIN CONSTRAINT PK_s_log_technical PRIMARY KEY (b_id) ); CREATE INDEX IX_s_log_technical_trace ON dbo.s_log_technical (b_trace_id, b_occurdatetime DESC); - CREATE INDEX IX_s_log_technical_org ON dbo.s_log_technical (b_org_id, b_occurdatetime DESC); CREATE INDEX IX_s_log_technical_level ON dbo.s_log_technical (b_loglevel, b_occurdatetime DESC); END; IF OBJECT_ID(N'dbo.s_log_login', N'U') IS NULL BEGIN CREATE TABLE dbo.s_log_login ( - b_id bigint IDENTITY(1,1) NOT NULL, - b_org_id varchar(50) NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_user_id bigint NULL, b_account varchar(50) NULL, b_login_type varchar(20) NOT NULL, @@ -462,7 +459,7 @@ END; IF OBJECT_ID(N'dbo.s_log_audit', N'U') IS NULL BEGIN CREATE TABLE dbo.s_log_audit ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_event_code varchar(50) NOT NULL, b_module_code varchar(50) NULL, b_data_id varchar(50) NULL, @@ -485,7 +482,7 @@ END; IF OBJECT_ID(N'dbo.s_log_audit_field', N'U') IS NULL BEGIN CREATE TABLE dbo.s_log_audit_field ( - b_id bigint IDENTITY(1,1) NOT NULL, + b_id bigint NOT NULL, -- 雪花主键(应用层生成,全局唯一) b_event_id bigint NOT NULL, b_module_code varchar(50) NULL, b_data_id varchar(50) NULL, diff --git a/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/service/LoginLogService.java b/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/service/LoginLogService.java index 7924d163..7184e6a5 100644 --- a/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/service/LoginLogService.java +++ b/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/service/LoginLogService.java @@ -10,13 +10,12 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.sql.Timestamp; -import java.time.LocalDateTime; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** - * 登录日志服务:在登录成功或失败时采集客户端信息,并通过通用 saveobjt 机制写入 s_login_log 表。 + * 登录日志服务:在登录成功或失败时采集客户端信息,并通过通用 saveobjt 机制写入 s_log_login 表。 * 写日志失败不影响登录主流程;机构上下文通过 OrgContext 临时设置并在 finally 中清理。 */ @Service @@ -24,6 +23,9 @@ public class LoginLogService { private static final Logger log = LoggerFactory.getLogger(LoginLogService.class); + /** 登录类型:密码登录(与 s_log_login.b_login_type 约定一致) */ + private static final String LOGIN_TYPE_PASSWORD = "password"; + private final DataSaveService dataSaveService; private final ClientInfoExtractor clientInfoExtractor; @@ -49,10 +51,10 @@ public class LoginLogService { Map row = new LinkedHashMap<>(); row.put("b_id", IdGenerator.nextId()); - row.put("b_org_id", orgId); row.put("b_user_id", userId); row.put("b_account", account); - row.put("b_login_time", Timestamp.valueOf(LocalDateTime.now())); + row.put("b_login_type", LOGIN_TYPE_PASSWORD); + row.put("b_occurdatetime", Timestamp.from(java.time.Instant.now())); row.put("b_ip", info.ip()); row.put("b_ip_location", info.ipLocation()); row.put("b_browser", info.browser()); @@ -66,7 +68,7 @@ public class LoginLogService { row.put("b_session_id", sessionId); Map tableOp = new LinkedHashMap<>(); - tableOp.put("table", "s_login_log"); + tableOp.put("table", "s_log_login"); tableOp.put("key_field", "b_id"); tableOp.put("inserts", List.of(row)); diff --git a/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/utils/DbUtils.java b/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/utils/DbUtils.java index c208a58e..8e731e42 100644 --- a/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/utils/DbUtils.java +++ b/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/utils/DbUtils.java @@ -166,33 +166,12 @@ public class DbUtils { String account ) throws SQLException { try (Connection connection = dataSourceManager.getConnection(orgId)) { - SQLException failure = null; - boolean privileged = false; - try { - setSystemUserVisibility(connection, 1); - privileged = true; - String sql = "SELECT [b_id], [b_account], [b_name], [b_password] " - + "FROM [b_user] WHERE [b_account] = ? AND [b_canuse] = 1"; - try (PreparedStatement statement = connection.prepareStatement(sql)) { - statement.setString(1, account); - try (ResultSet resultSet = statement.executeQuery()) { - return readRows(resultSet); - } - } - } catch (SQLException exception) { - failure = exception; - throw exception; - } finally { - if (privileged) { - try { - setSystemUserVisibility(connection, null); - } catch (SQLException clearException) { - if (failure != null) { - failure.addSuppressed(clearException); - } else { - throw clearException; - } - } + String sql = "SELECT [b_id], [b_account], [b_name], [b_password] " + + "FROM [b_user] WHERE [b_account] = ? AND [b_canuse] = 1"; + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, account); + try (ResultSet resultSet = statement.executeQuery()) { + return readRows(resultSet); } } } @@ -506,22 +485,6 @@ public class DbUtils { return result.toString(); } - private void setSystemUserVisibility( - Connection connection, - Integer value - ) throws SQLException { - String sql = "EXEC sys.sp_set_session_context @key = ?, @value = ?"; - try (PreparedStatement statement = connection.prepareStatement(sql)) { - statement.setString(1, "allow_g3soft"); - if (value == null) { - statement.setNull(2, java.sql.Types.INTEGER); - } else { - statement.setInt(2, value); - } - statement.execute(); - } - } - private String buildColumns(List searchColumns) { if (searchColumns == null || searchColumns.isEmpty()) { return "*"; diff --git a/code/fms/fms-api/src/test/java/cn/g3soft/fmsapi/utils/snowflake/DefaultIdGeneratorTests.java b/code/fms/fms-api/src/test/java/cn/g3soft/fmsapi/utils/snowflake/DefaultIdGeneratorTests.java new file mode 100644 index 00000000..dff0f57f --- /dev/null +++ b/code/fms/fms-api/src/test/java/cn/g3soft/fmsapi/utils/snowflake/DefaultIdGeneratorTests.java @@ -0,0 +1,130 @@ +package cn.g3soft.fmsapi.utils.snowflake; + +import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorException; +import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorOptions; +import cn.g3soft.fmsapi.utils.snowflake.idgen.DefaultIdGenerator; +import cn.g3soft.fmsapi.utils.snowflake.idgen.IdGenerator; +import org.junit.jupiter.api.Test; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DefaultIdGeneratorTests { + + private static IdGeneratorOptions options(short workerId) { + IdGeneratorOptions o = new IdGeneratorOptions(workerId); + o.Method = 1; // 漂移算法(生产默认,自带时钟回拨保护) + return o; + } + + @Test + void generatedIds_areUniqueInBulk() throws Exception { + DefaultIdGenerator gen = new DefaultIdGenerator(options((short) 1)); + + int n = 100_000; + Set ids = ConcurrentHashMap.newKeySet(n); + for (int i = 0; i < n; i++) { + ids.add(gen.newLong()); + } + + assertThat(ids).hasSize(n); + } + + @Test + void generatedIds_areStrictlyIncreasingInSingleThread() throws Exception { + DefaultIdGenerator gen = new DefaultIdGenerator(options((short) 1)); + + long prev = gen.newLong(); + for (int i = 0; i < 10_000; i++) { + long cur = gen.newLong(); + assertThat(cur).isGreaterThan(prev); + prev = cur; + } + } + + @Test + void generatedIds_concurrentNoDuplicates() throws Exception { + DefaultIdGenerator gen = new DefaultIdGenerator(options((short) 1)); + + int threads = 8; + int perThread = 10_000; + Set ids = ConcurrentHashMap.newKeySet(); + ExecutorService pool = Executors.newFixedThreadPool(threads); + CountDownLatch start = new CountDownLatch(1); + CountDownLatch done = new CountDownLatch(threads); + for (int t = 0; t < threads; t++) { + pool.submit(() -> { + try { + start.await(); + for (int i = 0; i < perThread; i++) { + ids.add(gen.newLong()); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + done.countDown(); + } + }); + } + + start.countDown(); + assertThat(done.await(30, TimeUnit.SECONDS)).isTrue(); + pool.shutdownNow(); + + assertThat(ids).hasSize(threads * perThread); + } + + @Test + void generatedId_encodesWorkerIdAndApproximateTimestamp() throws Exception { + IdGeneratorOptions o = options((short) 7); + DefaultIdGenerator gen = new DefaultIdGenerator(o); + + long id = gen.newLong(); + + int timestampShift = o.WorkerIdBitLength + o.SeqBitLength; // 12 + long timestamp = id >>> timestampShift; + long workerId = (id >>> o.SeqBitLength) & ((1L << o.WorkerIdBitLength) - 1); + + assertThat(workerId).isEqualTo(7L); + long expectedTimestamp = System.currentTimeMillis() - o.BaseTime; + assertThat(timestamp).isBetween(expectedTimestamp - 5_000, expectedTimestamp + 5_000); + } + + @Test + void invalidOptions_areRejected() { + IdGeneratorOptions badWorkerId = options((short) -1); + assertThatThrownBy(() -> new DefaultIdGenerator(badWorkerId)) + .isInstanceOf(IdGeneratorException.class); + + IdGeneratorOptions badBaseTime = options((short) 1); + badBaseTime.BaseTime = 1000L; // 早于校验下限 315504000000 + assertThatThrownBy(() -> new DefaultIdGenerator(badBaseTime)) + .isInstanceOf(IdGeneratorException.class); + + IdGeneratorOptions tooManyBits = options((short) 1); + tooManyBits.WorkerIdBitLength = 22; // 22 + 6 > 22 + assertThatThrownBy(() -> new DefaultIdGenerator(tooManyBits)) + .isInstanceOf(IdGeneratorException.class); + + IdGeneratorOptions tooSmallSeq = options((short) 1); + tooSmallSeq.SeqBitLength = 1; // 下限 2 + assertThatThrownBy(() -> new DefaultIdGenerator(tooSmallSeq)) + .isInstanceOf(IdGeneratorException.class); + } + + @Test + void staticSingleton_worksAfterInitializationLikeApplicationStartup() throws Exception { + IdGenerator.setIdGenerator(options((short) 1)); + + long id = IdGenerator.nextId(); + + assertThat(id).isPositive(); + } +} diff --git a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/service/LoginLogService.class b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/service/LoginLogService.class index 3cdce4b9..8c59acfd 100644 Binary files a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/service/LoginLogService.class and b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/service/LoginLogService.class differ diff --git a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$ColumnMetadata.class b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$ColumnMetadata.class index 677260a3..993aa8cf 100644 Binary files a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$ColumnMetadata.class and b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$ColumnMetadata.class differ diff --git a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$PageResult.class b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$PageResult.class index 64badc2a..06f72899 100644 Binary files a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$PageResult.class and b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$PageResult.class differ diff --git a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$TableMetadata.class b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$TableMetadata.class index d7ef3fe1..37f4037b 100644 Binary files a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$TableMetadata.class and b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils$TableMetadata.class differ diff --git a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils.class b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils.class index 528f08de..de0b0e1c 100644 Binary files a/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils.class and b/code/fms/fms-api/target/classes/cn/g3soft/fmsapi/utils/DbUtils.class differ diff --git a/code/fms/fms-api/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/code/fms/fms-api/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index e69de29b..702d8937 100644 --- a/code/fms/fms-api/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/code/fms/fms-api/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,54 @@ +cn\g3soft\fmsapi\service\LoginLogService.class +cn\g3soft\fmsapi\controller\FileController.class +cn\g3soft\fmsapi\utils\ParamUtils.class +cn\g3soft\fmsapi\utils\JwtUtils.class +cn\g3soft\fmsapi\FmsApiApplication.class +cn\g3soft\fmsapi\utils\DbUtils$TableMetadata.class +cn\g3soft\fmsapi\utils\JwtUtils$TokenClaims.class +cn\g3soft\fmsapi\utils\IpRegionSearcher.class +cn\g3soft\fmsapi\service\DataSaveService.class +cn\g3soft\fmsapi\service\DataService.class +cn\g3soft\fmsapi\utils\snowflake\core\SnowWorkerM1.class +cn\g3soft\fmsapi\database\OrgDataSourceManager.class +cn\g3soft\fmsapi\utils\ClientInfo.class +cn\g3soft\fmsapi\utils\snowflake\contract\OverCostActionArg.class +cn\g3soft\fmsapi\utils\snowflake\idgen\DefaultIdGenerator.class +cn\g3soft\fmsapi\controller\DataController.class +cn\g3soft\fmsapi\database\OrgDatabaseConfigLoader.class +cn\g3soft\fmsapi\database\OrgDatabaseConfig.class +cn\g3soft\fmsapi\exception\BusinessException.class +cn\g3soft\fmsapi\config\FileStorageProperties.class +cn\g3soft\fmsapi\config\JacksonConfig.class +cn\g3soft\fmsapi\service\FileService.class +cn\g3soft\fmsapi\database\OrgDataSourceFactory.class +cn\g3soft\fmsapi\service\DataSaveService$CodeCandidate.class +cn\g3soft\fmsapi\database\OrgContext.class +cn\g3soft\fmsapi\database\OrgDatabaseProperties$Pool.class +cn\g3soft\fmsapi\utils\snowflake\idgen\IdGenerator.class +cn\g3soft\fmsapi\service\DataSaveService$AutoCodeRule.class +cn\g3soft\fmsapi\exception\GlobalExceptionHandler.class +cn\g3soft\fmsapi\service\ActiveSessionRegistry.class +cn\g3soft\fmsapi\utils\snowflake\contract\IdGeneratorOptions.class +cn\g3soft\fmsapi\config\JwtAuthFilter.class +cn\g3soft\fmsapi\utils\DbUtils$ColumnMetadata.class +cn\g3soft\fmsapi\controller\AuthController.class +cn\g3soft\fmsapi\utils\snowflake\contract\IIdGenerator.class +cn\g3soft\fmsapi\utils\snowflake\contract\ISnowWorker.class +cn\g3soft\fmsapi\utils\IpUtils.class +cn\g3soft\fmsapi\utils\StringUtils.class +cn\g3soft\fmsapi\utils\DbUtils.class +cn\g3soft\fmsapi\exception\SaveObjectException.class +cn\g3soft\fmsapi\service\AuthService.class +cn\g3soft\fmsapi\config\AuthProperties.class +cn\g3soft\fmsapi\database\OrgRoutingDataSource.class +cn\g3soft\fmsapi\utils\ApiResponse.class +cn\g3soft\fmsapi\utils\snowflake\core\SnowWorkerM2.class +cn\g3soft\fmsapi\service\ActiveSessionRegistry$AccountKey.class +cn\g3soft\fmsapi\utils\DbUtils$PageResult.class +cn\g3soft\fmsapi\config\FileStorageProperties$Local.class +cn\g3soft\fmsapi\config\FileStorageProperties$Oss.class +cn\g3soft\fmsapi\utils\snowflake\contract\IdGeneratorException.class +cn\g3soft\fmsapi\utils\ClientInfoExtractor.class +cn\g3soft\fmsapi\exception\AuthenticationException.class +cn\g3soft\fmsapi\database\OrgDatabaseProperties.class +cn\g3soft\fmsapi\service\DataSaveService$AutoCodeFormat.class diff --git a/code/fms/fms-api/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/code/fms/fms-api/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst index e69de29b..567edb45 100644 --- a/code/fms/fms-api/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst +++ b/code/fms/fms-api/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1,10 @@ +cn\g3soft\fmsapi\database\OrgDatabaseConfigLoaderTests.class +cn\g3soft\fmsapi\utils\snowflake\DefaultIdGeneratorTests.class +cn\g3soft\fmsapi\database\OrgDataSourceFactoryTests.class +cn\g3soft\fmsapi\service\AuthServiceTests.class +cn\g3soft\fmsapi\utils\JwtUtilsTests.class +cn\g3soft\fmsapi\exception\GlobalExceptionHandlerTests.class +cn\g3soft\fmsapi\FmsApiApplicationTests.class +cn\g3soft\fmsapi\service\DataSaveServiceTests$JdbcFixture.class +cn\g3soft\fmsapi\service\ActiveSessionRegistryTests.class +cn\g3soft\fmsapi\service\DataSaveServiceTests.class diff --git a/code/fms/fms-api/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/code/fms/fms-api/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst index 98c8e119..8fe569d9 100644 --- a/code/fms/fms-api/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst +++ b/code/fms/fms-api/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -6,3 +6,4 @@ D:\workspace\code\fms\fms-api\src\test\java\cn\g3soft\fmsapi\service\ActiveSessi D:\workspace\code\fms\fms-api\src\test\java\cn\g3soft\fmsapi\service\AuthServiceTests.java D:\workspace\code\fms\fms-api\src\test\java\cn\g3soft\fmsapi\service\DataSaveServiceTests.java D:\workspace\code\fms\fms-api\src\test\java\cn\g3soft\fmsapi\utils\JwtUtilsTests.java +D:\workspace\code\fms\fms-api\src\test\java\cn\g3soft\fmsapi\utils\snowflake\DefaultIdGeneratorTests.java diff --git a/code/fms/fms-api/target/schema-generator/GenerateBusinessMigrations$Column.class b/code/fms/fms-api/target/schema-generator/GenerateBusinessMigrations$Column.class deleted file mode 100644 index 09fbe1b4..00000000 Binary files a/code/fms/fms-api/target/schema-generator/GenerateBusinessMigrations$Column.class and /dev/null differ diff --git a/code/fms/fms-api/target/schema-generator/GenerateBusinessMigrations$Table.class b/code/fms/fms-api/target/schema-generator/GenerateBusinessMigrations$Table.class deleted file mode 100644 index 8859c68f..00000000 Binary files a/code/fms/fms-api/target/schema-generator/GenerateBusinessMigrations$Table.class and /dev/null differ diff --git a/code/fms/fms-api/target/schema-generator/GenerateBusinessMigrations.class b/code/fms/fms-api/target/schema-generator/GenerateBusinessMigrations.class deleted file mode 100644 index 28670457..00000000 Binary files a/code/fms/fms-api/target/schema-generator/GenerateBusinessMigrations.class and /dev/null differ diff --git a/code/fms/fms-api/target/schema-generator/RunBusinessMigrations.class b/code/fms/fms-api/target/schema-generator/RunBusinessMigrations.class deleted file mode 100644 index cae03a88..00000000 Binary files a/code/fms/fms-api/target/schema-generator/RunBusinessMigrations.class and /dev/null differ diff --git a/code/fms/fms-api/target/schema-generator/VerifyBusinessMigration.class b/code/fms/fms-api/target/schema-generator/VerifyBusinessMigration.class deleted file mode 100644 index 15d9ef71..00000000 Binary files a/code/fms/fms-api/target/schema-generator/VerifyBusinessMigration.class and /dev/null differ diff --git a/code/fms/fms-api/target/surefire-reports/2026-07-27T21-53-37_778.dumpstream b/code/fms/fms-api/target/surefire-reports/2026-07-27T21-53-37_778.dumpstream deleted file mode 100644 index 3ceaf89b..00000000 --- a/code/fms/fms-api/target/surefire-reports/2026-07-27T21-53-37_778.dumpstream +++ /dev/null @@ -1,5 +0,0 @@ -# Created at 2026-07-27T21:53:37.909 -Boot Manifest-JAR contains absolute paths in classpath 'D:\workspace\code\fms\fms-api\target\test-classes' -Hint: -Djdk.net.URLClassPath.disableClassPathURLCheck=true -'other' has different root - diff --git a/code/fms/fms-api/target/surefire-reports/2026-07-27T21-19-50_396.dumpstream b/code/fms/fms-api/target/surefire-reports/2026-08-21T21-12-50_643.dumpstream similarity index 85% rename from code/fms/fms-api/target/surefire-reports/2026-07-27T21-19-50_396.dumpstream rename to code/fms/fms-api/target/surefire-reports/2026-08-21T21-12-50_643.dumpstream index 89f70bbd..43a674a6 100644 --- a/code/fms/fms-api/target/surefire-reports/2026-07-27T21-19-50_396.dumpstream +++ b/code/fms/fms-api/target/surefire-reports/2026-08-21T21-12-50_643.dumpstream @@ -1,4 +1,4 @@ -# Created at 2026-07-27T21:19:50.541 +# Created at 2026-08-21T21:12:50.803 Boot Manifest-JAR contains absolute paths in classpath 'D:\workspace\code\fms\fms-api\target\test-classes' Hint: -Djdk.net.URLClassPath.disableClassPathURLCheck=true 'other' has different root diff --git a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.FmsApiApplicationTests.xml b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.FmsApiApplicationTests.xml index 255e674c..505bb764 100644 --- a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.FmsApiApplicationTests.xml +++ b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.FmsApiApplicationTests.xml @@ -1,5 +1,5 @@ - + @@ -16,7 +16,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -52,11 +52,11 @@ - + - + @@ -65,12 +65,12 @@ - - + - + @@ -15,7 +15,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -59,10 +59,16 @@ - - + + \ No newline at end of file diff --git a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests.xml b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests.xml index a13d488b..258069e8 100644 --- a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests.xml +++ b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests.xml @@ -1,5 +1,5 @@ - + @@ -15,7 +15,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -59,5 +59,5 @@ - + \ No newline at end of file diff --git a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests.xml b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests.xml index ab0b4227..2f6072d1 100644 --- a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests.xml +++ b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests.xml @@ -1,5 +1,5 @@ - + @@ -15,7 +15,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -59,8 +59,8 @@ - - + - - + - + @@ -16,7 +16,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -52,11 +52,11 @@ - + - + diff --git a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.service.AuthServiceTests.xml b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.service.AuthServiceTests.xml index 5f7a794c..1ff8e3d6 100644 --- a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.service.AuthServiceTests.xml +++ b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.service.AuthServiceTests.xml @@ -1,5 +1,5 @@ - + @@ -16,7 +16,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -52,11 +52,11 @@ - + - + @@ -65,5 +65,5 @@ - + \ No newline at end of file diff --git a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.service.DataSaveServiceTests.xml b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.service.DataSaveServiceTests.xml index e9d2f724..327ee952 100644 --- a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.service.DataSaveServiceTests.xml +++ b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.service.DataSaveServiceTests.xml @@ -1,5 +1,5 @@ - + @@ -16,7 +16,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -52,11 +52,11 @@ - + - + @@ -65,7 +65,9 @@ - - - + + + + + \ No newline at end of file diff --git a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.utils.JwtUtilsTests.xml b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.utils.JwtUtilsTests.xml index 84104831..cc6c9c6f 100644 --- a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.utils.JwtUtilsTests.xml +++ b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.utils.JwtUtilsTests.xml @@ -16,7 +16,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -52,11 +52,11 @@ - + - + diff --git a/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.utils.snowflake.DefaultIdGeneratorTests.xml b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.utils.snowflake.DefaultIdGeneratorTests.xml new file mode 100644 index 00000000..f786d7db --- /dev/null +++ b/code/fms/fms-api/target/surefire-reports/TEST-cn.g3soft.fmsapi.utils.snowflake.DefaultIdGeneratorTests.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.FmsApiApplicationTests.txt b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.FmsApiApplicationTests.txt index 6b4e1e9a..4530b8a7 100644 --- a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.FmsApiApplicationTests.txt +++ b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.FmsApiApplicationTests.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: cn.g3soft.fmsapi.FmsApiApplicationTests ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.581 s -- in cn.g3soft.fmsapi.FmsApiApplicationTests +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.644 s -- in cn.g3soft.fmsapi.FmsApiApplicationTests diff --git a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.database.OrgDataSourceFactoryTests.txt b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.database.OrgDataSourceFactoryTests.txt index 38e3dd2a..54dd7516 100644 --- a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.database.OrgDataSourceFactoryTests.txt +++ b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.database.OrgDataSourceFactoryTests.txt @@ -1,4 +1,11 @@ ------------------------------------------------------------------------------- Test set: cn.g3soft.fmsapi.database.OrgDataSourceFactoryTests ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.141 s -- in cn.g3soft.fmsapi.database.OrgDataSourceFactoryTests +Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.145 s <<< FAILURE! -- in cn.g3soft.fmsapi.database.OrgDataSourceFactoryTests +cn.g3soft.fmsapi.database.OrgDataSourceFactoryTests.databaseConnectionFailureDoesNotRetry -- Time elapsed: 0.144 s <<< FAILURE! +org.opentest4j.AssertionFailedError: + +expected: 0 + but was: 3 + at cn.g3soft.fmsapi.database.OrgDataSourceFactoryTests.databaseConnectionFailureDoesNotRetry(OrgDataSourceFactoryTests.java:25) + diff --git a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests.txt b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests.txt index f5ce0f90..d03a4e93 100644 --- a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests.txt +++ b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.129 s -- in cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.139 s -- in cn.g3soft.fmsapi.database.OrgDatabaseConfigLoaderTests diff --git a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests.txt b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests.txt index 918fec0f..2d971bcc 100644 --- a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests.txt +++ b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests ------------------------------------------------------------------------------- -Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.015 s -- in cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.014 s -- in cn.g3soft.fmsapi.exception.GlobalExceptionHandlerTests diff --git a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.ActiveSessionRegistryTests.txt b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.ActiveSessionRegistryTests.txt index b62dbadc..a8084c22 100644 --- a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.ActiveSessionRegistryTests.txt +++ b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.ActiveSessionRegistryTests.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: cn.g3soft.fmsapi.service.ActiveSessionRegistryTests ------------------------------------------------------------------------------- -Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in cn.g3soft.fmsapi.service.ActiveSessionRegistryTests +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 s -- in cn.g3soft.fmsapi.service.ActiveSessionRegistryTests diff --git a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.AuthServiceTests.txt b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.AuthServiceTests.txt index d4b873be..d7686c52 100644 --- a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.AuthServiceTests.txt +++ b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.AuthServiceTests.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: cn.g3soft.fmsapi.service.AuthServiceTests ------------------------------------------------------------------------------- -Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.192 s -- in cn.g3soft.fmsapi.service.AuthServiceTests +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.201 s -- in cn.g3soft.fmsapi.service.AuthServiceTests diff --git a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.DataSaveServiceTests.txt b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.DataSaveServiceTests.txt index eb64c0d0..55cee72a 100644 --- a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.DataSaveServiceTests.txt +++ b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.service.DataSaveServiceTests.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: cn.g3soft.fmsapi.service.DataSaveServiceTests ------------------------------------------------------------------------------- -Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.414 s -- in cn.g3soft.fmsapi.service.DataSaveServiceTests +Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.434 s -- in cn.g3soft.fmsapi.service.DataSaveServiceTests diff --git a/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.utils.snowflake.DefaultIdGeneratorTests.txt b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.utils.snowflake.DefaultIdGeneratorTests.txt new file mode 100644 index 00000000..45dec5f6 --- /dev/null +++ b/code/fms/fms-api/target/surefire-reports/cn.g3soft.fmsapi.utils.snowflake.DefaultIdGeneratorTests.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: cn.g3soft.fmsapi.utils.snowflake.DefaultIdGeneratorTests +------------------------------------------------------------------------------- +Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.633 s -- in cn.g3soft.fmsapi.utils.snowflake.DefaultIdGeneratorTests diff --git a/code/fms/fms-api/target/test-classes/cn/g3soft/fmsapi/utils/snowflake/DefaultIdGeneratorTests.class b/code/fms/fms-api/target/test-classes/cn/g3soft/fmsapi/utils/snowflake/DefaultIdGeneratorTests.class new file mode 100644 index 00000000..ff9a76ed Binary files /dev/null and b/code/fms/fms-api/target/test-classes/cn/g3soft/fmsapi/utils/snowflake/DefaultIdGeneratorTests.class differ diff --git a/code/fms/fms-vue/.playwright-cli/page-2026-08-21T15-03-43-736Z.yml b/code/fms/fms-vue/.playwright-cli/page-2026-08-21T15-03-43-736Z.yml new file mode 100644 index 00000000..40357050 --- /dev/null +++ b/code/fms/fms-vue/.playwright-cli/page-2026-08-21T15-03-43-736Z.yml @@ -0,0 +1,37 @@ +- generic [ref=e1]: + - generic [ref=e2]: + - generic [ref=e4]: + - generic [ref=e5]: + - heading "登录 FMS" [level=1] [ref=e9] + - paragraph [ref=e10]: 企业管理系统 + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - text: "*" + - generic [ref=e14]: 机构码 + - textbox [active] [ref=e23] + - generic [ref=e24]: + - generic [ref=e25]: + - text: "*" + - generic [ref=e26]: 账号 + - textbox [ref=e34] + - generic [ref=e35]: + - generic [ref=e36]: 密码 + - generic [ref=e40]: + - textbox [ref=e45] + - button "显示密码" [ref=e46] [cursor=pointer] + - button "登录" [ref=e50] [cursor=pointer] + - paragraph [ref=e51]: © 2026 FMS · 企业管理系统 + - complementary "FPS 性能监控" [ref=e52]: + - generic [ref=e53]: + - generic [ref=e54]: FPS + - strong [ref=e55]: "--" + - button "重置 FPS 统计" [ref=e56] [cursor=pointer] + - generic [ref=e61]: + - generic [ref=e62]: avg -- + - generic [ref=e63]: min -- + - status: + - generic: + - generic: FMS + - generic: 数字化管理系统 + - generic: 正在加载,请稍候… \ No newline at end of file diff --git a/code/fms/fms-vue/form-demo.yaml b/code/fms/fms-vue/form-demo.yaml new file mode 100644 index 00000000..40357050 --- /dev/null +++ b/code/fms/fms-vue/form-demo.yaml @@ -0,0 +1,37 @@ +- generic [ref=e1]: + - generic [ref=e2]: + - generic [ref=e4]: + - generic [ref=e5]: + - heading "登录 FMS" [level=1] [ref=e9] + - paragraph [ref=e10]: 企业管理系统 + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - text: "*" + - generic [ref=e14]: 机构码 + - textbox [active] [ref=e23] + - generic [ref=e24]: + - generic [ref=e25]: + - text: "*" + - generic [ref=e26]: 账号 + - textbox [ref=e34] + - generic [ref=e35]: + - generic [ref=e36]: 密码 + - generic [ref=e40]: + - textbox [ref=e45] + - button "显示密码" [ref=e46] [cursor=pointer] + - button "登录" [ref=e50] [cursor=pointer] + - paragraph [ref=e51]: © 2026 FMS · 企业管理系统 + - complementary "FPS 性能监控" [ref=e52]: + - generic [ref=e53]: + - generic [ref=e54]: FPS + - strong [ref=e55]: "--" + - button "重置 FPS 统计" [ref=e56] [cursor=pointer] + - generic [ref=e61]: + - generic [ref=e62]: avg -- + - generic [ref=e63]: min -- + - status: + - generic: + - generic: FMS + - generic: 数字化管理系统 + - generic: 正在加载,请稍候… \ No newline at end of file diff --git a/code/fms/fms-vue/src/components/ui/form/form-item.vue b/code/fms/fms-vue/src/components/ui/form/form-item.vue index af2744b6..7b982010 100644 --- a/code/fms/fms-vue/src/components/ui/form/form-item.vue +++ b/code/fms/fms-vue/src/components/ui/form/form-item.vue @@ -24,6 +24,8 @@ const props = defineProps({ }, /** 单项 label 宽度覆盖(仅横向布局生效) */ labelWidth: { type: [String, Number], default: undefined }, + /** 网格布局下跨列数(不超过 Form.columns,默认 1) */ + span: { type: Number, default: 1 }, /** 外部手动指定校验状态:'' | 'error' */ validateStatus: { type: String, @@ -81,6 +83,14 @@ const labelStyle = computed(() => { return { width: labelWidth.value } }) +// 网格布局(Form columns > 1)下跨列:span 超过列数时取整行 +const itemStyle = computed(() => { + const columns = form?.columns?.value ?? 1 + if (columns <= 1) return undefined + const span = Math.min(Math.max(Number(props.span) || 1, 1), columns) + return span > 1 ? { gridColumn: `span ${span}` } : undefined +}) + /* ---------- 校验 ---------- */ async function runValidation() { const nextErrors = await validateRules({ @@ -146,7 +156,7 @@ onBeforeUnmount(() => {