import java.sql.*; import java.util.*; import java.nio.file.*; import java.nio.file.attribute.PosixFilePermissions; import javax.sql.rowset.*; /** Read-only identity/schema probe. Credentials only from process environment. */ public class QaDatabaseProbe { static final String DATABASE = "railwaysystem_fusion_qa_20261013"; static void query(Connection c, String label, String sql) throws SQLException { System.out.println("[" + label + "]"); try (Statement s = c.createStatement()) { s.setQueryTimeout(15); try (ResultSet r = s.executeQuery(sql)) { int n = r.getMetaData().getColumnCount(); while (r.next()) { List row = new ArrayList<>(); for (int i = 1; i <= n; i++) row.add(String.valueOf(r.getObject(i))); System.out.println(String.join(" | ", row)); } } } } public static void main(String[] args) throws Exception { String url = System.getenv("QA_DB_URL"); // Validate target before even opening a connection; no URL credentials permitted. if (url == null || !url.matches("jdbc:kingbase8://[A-Za-z0-9.:-]+/" + DATABASE + "(?:\\?.*)?") || url.matches("(?i).*[?&](user|password)=.*")) throw new SecurityException("Refusing non-target URL or URL credentials"); if (!"fusion_qa_app".equals(System.getenv("QA_DB_USER"))) throw new SecurityException("Read-only probe requires fusion_qa_app"); Class.forName("com.kingbase8.Driver"); Properties p = new Properties(); p.setProperty("user", System.getenv("QA_DB_USER")); p.setProperty("password", Objects.requireNonNull(System.getenv("QA_DB_PASSWORD"))); try (Connection c = DriverManager.getConnection(url, p)) { c.setAutoCommit(false); c.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); try (Statement s = c.createStatement()) { s.execute("SET TRANSACTION READ ONLY"); } try (Statement s = c.createStatement(); ResultSet r = s.executeQuery("SELECT current_database()")) { r.next(); if (!DATABASE.equals(r.getString(1))) throw new SecurityException("Database mismatch"); } query(c,"identity", "SELECT current_database(), current_user, current_schema(), version()"); query(c,"server_endpoint", "SELECT inet_server_addr(),inet_server_port(),current_database(),current_user"); query(c,"read_only_transaction", "SHOW transaction_read_only"); query(c,"search_path", "SHOW search_path"); query(c,"schemas", "SELECT schema_name FROM information_schema.schemata ORDER BY schema_name"); // Kingbase information_schema.tables can require extra catalog privileges; // reuse the readable pg_tables view rather than broadening the app role. query(c,"table_inventory", "SELECT schemaname,count(*) FROM pg_tables WHERE schemaname IN ('public','iotlocks') GROUP BY schemaname ORDER BY schemaname"); query(c,"training_counts", "SELECT 'train_student',count(*) FROM public.train_student UNION ALL SELECT 'train_enrollment',count(*) FROM public.train_enrollment UNION ALL SELECT 'train_accommodation',count(*) FROM public.train_accommodation"); query(c,"round5s_prestate", "SELECT count(*) FROM information_schema.columns WHERE table_schema='iotlocks' AND table_name IN ('lock_auth','lock_command') AND column_name IN ('execution_mode','environment_id','intent_key','intent_version','execution_state','intent_action','intent_at','attempt_started_at')"); query(c,"columns", "SELECT table_schema,table_name,column_name,data_type,is_nullable,column_default " + "FROM information_schema.columns WHERE (table_schema='iotlocks' AND table_name IN ('lock_auth','lock_command')) ORDER BY table_name,ordinal_position"); query(c,"constraints", "SELECT table_schema,table_name,constraint_name,constraint_type FROM information_schema.table_constraints " + "WHERE table_schema IN ('iotlocks','public') AND table_name IN ('lock_auth','lock_command') ORDER BY table_name,constraint_name"); query(c,"counts", "SELECT 'lock_auth',count(*) FROM iotlocks.lock_auth UNION ALL SELECT 'lock_command',count(*) FROM iotlocks.lock_command"); query(c,"indexes", "SELECT schemaname,tablename,indexname,indexdef FROM pg_indexes WHERE schemaname='iotlocks' AND tablename IN ('lock_auth','lock_command') ORDER BY tablename,indexname"); query(c,"runtime_account", "SELECT rolname,rolsuper,rolcreatedb,rolcreaterole FROM pg_roles WHERE rolname=current_user"); query(c,"schema_privileges", "SELECT nspname,has_schema_privilege(current_user,oid,'USAGE'),has_schema_privilege(current_user,oid,'CREATE') FROM pg_namespace WHERE nspname IN ('public','iotlocks') ORDER BY nspname"); query(c,"table_owners", "SELECT schemaname,tablename,tableowner FROM pg_tables WHERE schemaname IN ('public','iotlocks') AND tablename IN ('sys_user','lock_auth','lock_command') ORDER BY schemaname,tablename"); query(c,"table_grants", "SELECT table_schema,privilege_type,count(*) FROM information_schema.role_table_grants WHERE grantee=current_user AND table_schema IN ('public','iotlocks') GROUP BY table_schema,privilege_type ORDER BY table_schema,privilege_type"); query(c,"extra_grant_review", "SELECT table_schema,table_name,privilege_type FROM information_schema.role_table_grants WHERE grantee=current_user AND table_schema IN ('public','iotlocks') AND privilege_type IN ('TRUNCATE','TRIGGER','REFERENCES') ORDER BY table_schema,table_name,privilege_type"); query(c,"target_sessions", "SELECT usename,application_name,state,count(*) FROM pg_stat_activity WHERE datname=current_database() GROUP BY usename,application_name,state ORDER BY usename,application_name,state"); if (args.length == 2 && "--backup".equals(args[0])) { Path directory = Paths.get(args[1]); Files.createDirectory(directory, PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"))); for (String table : List.of("lock_auth", "lock_command")) { Path destination = directory.resolve(table + ".xml"); Files.createFile(destination, PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-------"))); try (Statement s = c.createStatement(); ResultSet rows = s.executeQuery("SELECT * FROM iotlocks." + table + " ORDER BY 1"); WebRowSet backup = RowSetProvider.newFactory().createWebRowSet(); java.io.Writer writer = Files.newBufferedWriter(destination)) { backup.populate(rows); backup.writeXml(writer); System.out.println("PRIVATE_TABLE_SNAPSHOT " + table + " rows=" + backup.size()); } } System.out.println("TABLE_SNAPSHOT_ONLY: no native full-database dump or restore drill claimed"); } if (Arrays.asList(args).contains("--require-migrated")) { String common = "'execution_mode','environment_id','intent_key','intent_version','execution_state','intent_action','intent_at'"; try (Statement s = c.createStatement(); ResultSet r = s.executeQuery("SELECT count(*) FROM information_schema.columns WHERE table_schema='iotlocks' AND ((table_name='lock_auth' AND column_name IN (" + common + ")) OR (table_name='lock_command' AND column_name IN (" + common + ",'device_sn','attempt_started_at')))")) { r.next(); if (r.getInt(1) != 16) throw new SecurityException("MIGRATION_REQUIRED: runtime refused"); } try (Statement s = c.createStatement(); ResultSet r = s.executeQuery("SELECT count(*) FROM pg_indexes WHERE schemaname='iotlocks' AND indexname IN ('uq_round5s_command_intent','uq_round5s_auth_intent','ix_round5s_command_recovery','ix_round5s_auth_recovery')")) { r.next(); if (r.getInt(1) != 4) throw new SecurityException("MIGRATION_INDEXES_REQUIRED: runtime refused"); } } c.rollback(); System.out.println("READ_ONLY_ROLLBACK_COMPLETE"); } catch (SQLException e) { // Server exceptions can contain values; expose SQLSTATE only. System.err.println("QA_DATABASE_PROBE_FAILED SQLSTATE=" + e.getSQLState()); System.exit(2); } } }