001package edu.pdx.cs.joy.jdbc; 002 003import java.io.File; 004import java.sql.*; 005import java.util.Set; 006 007/** 008 * A command-line program that uses the JDBC DatabaseMetaData API to print 009 * information about the tables in an H2 database file. 010 */ 011public class PrintH2DatabaseSchema { 012 013 private static final Set<String> uninterestingTablePrefixes = Set.of( 014 "CONSTANTS", "ENUM_VALUES", "INDEXES", "INDEX_COLUMNS", "INFORMATION_SCHEMA_CATALOG_NAME", 015 "IN_DOUBT", "LOCKS", "QUERY_STATISTICS", "RIGHTS", "ROLES", "SESSIONS", "SESSION_STATE", 016 "SETTINGS", "SYNONYMS", "USERS" 017 ); 018 019 /** 020 * Prints information about all tables in the database. 021 * 022 * @param connection the database connection 023 * @throws SQLException if a database error occurs 024 */ 025 private static void printDatabaseSchema(Connection connection) throws SQLException { 026 DatabaseMetaData metaData = connection.getMetaData(); 027 028 System.out.println("=== Database Information ==="); 029 System.out.println("Database Product: " + metaData.getDatabaseProductName()); 030 System.out.println("Database Version: " + metaData.getDatabaseProductVersion()); 031 System.out.println("Driver Name: " + metaData.getDriverName()); 032 System.out.println("Driver Version: " + metaData.getDriverVersion()); 033 System.out.println(); 034 035 // Get all tables 036 System.out.println("=== Tables ==="); 037 try (ResultSet tables = metaData.getTables(null, null, "%", new String[]{"TABLE"})) { 038 boolean foundTables = false; 039 040 while (tables.next()) { 041 foundTables = true; 042 String tableName = tables.getString("TABLE_NAME"); 043 044 if (tableNameIsNotInteresting(tableName)) { 045 continue; // Skip system or uninteresting tables 046 } 047 048 String tableType = tables.getString("TABLE_TYPE"); 049 String remarks = tables.getString("REMARKS"); 050 051 System.out.println("\nTable: " + tableName); 052 System.out.println(" Type: " + tableType); 053 if (remarks != null && !remarks.isEmpty()) { 054 System.out.println(" Remarks: " + remarks); 055 } 056 057 // Print columns for this table 058 printTableColumns(metaData, tableName); 059 060 // Print primary keys 061 printPrimaryKeys(metaData, tableName); 062 063 // Print foreign keys 064 printForeignKeys(metaData, tableName); 065 066 // Print indexes 067 printIndexes(metaData, tableName); 068 } 069 070 if (!foundTables) { 071 System.out.println("No tables found in the database."); 072 } 073 } 074 } 075 076 private static boolean tableNameIsNotInteresting(String tableName) { 077 return uninterestingTablePrefixes.stream().anyMatch(tableName::startsWith); 078 } 079 080 /** 081 * Prints information about columns in a table. 082 */ 083 private static void printTableColumns(DatabaseMetaData metaData, String tableName) throws SQLException { 084 System.out.println(" Columns:"); 085 try (ResultSet columns = metaData.getColumns(null, null, tableName, "%")) { 086 while (columns.next()) { 087 String columnName = columns.getString("COLUMN_NAME"); 088 String columnType = columns.getString("TYPE_NAME"); 089 int columnSize = columns.getInt("COLUMN_SIZE"); 090 String nullable = columns.getString("IS_NULLABLE"); 091 String defaultValue = columns.getString("COLUMN_DEF"); 092 093 System.out.print(" - " + columnName + " " + columnType); 094 if (columnSize > 0) { 095 System.out.print("(" + columnSize + ")"); 096 } 097 System.out.print(" [" + (nullable.equals("YES") ? "NULL" : "NOT NULL") + "]"); 098 if (defaultValue != null) { 099 System.out.print(" DEFAULT " + defaultValue); 100 } 101 System.out.println(); 102 } 103 } 104 } 105 106 /** 107 * Prints information about primary keys in a table. 108 */ 109 private static void printPrimaryKeys(DatabaseMetaData metaData, String tableName) throws SQLException { 110 System.out.println(" Primary Keys:"); 111 try (ResultSet primaryKeys = metaData.getPrimaryKeys(null, null, tableName)) { 112 boolean foundKeys = false; 113 while (primaryKeys.next()) { 114 foundKeys = true; 115 String columnName = primaryKeys.getString("COLUMN_NAME"); 116 String pkName = primaryKeys.getString("PK_NAME"); 117 int keySeq = primaryKeys.getInt("KEY_SEQ"); 118 119 System.out.println(" - " + columnName + " (Key: " + pkName + ", Sequence: " + keySeq + ")"); 120 } 121 if (!foundKeys) { 122 System.out.println(" None"); 123 } 124 } 125 } 126 127 /** 128 * Prints information about foreign keys in a table. 129 */ 130 private static void printForeignKeys(DatabaseMetaData metaData, String tableName) throws SQLException { 131 System.out.println(" Foreign Keys:"); 132 try (ResultSet foreignKeys = metaData.getImportedKeys(null, null, tableName)) { 133 boolean foundKeys = false; 134 while (foreignKeys.next()) { 135 foundKeys = true; 136 String fkColumnName = foreignKeys.getString("FKCOLUMN_NAME"); 137 String pkTableName = foreignKeys.getString("PKTABLE_NAME"); 138 String pkColumnName = foreignKeys.getString("PKCOLUMN_NAME"); 139 String fkName = foreignKeys.getString("FK_NAME"); 140 141 System.out.println(" - " + fkColumnName + " -> " + pkTableName + "(" + pkColumnName + ")" + 142 (fkName != null ? " [" + fkName + "]" : "")); 143 } 144 if (!foundKeys) { 145 System.out.println(" None"); 146 } 147 } 148 } 149 150 /** 151 * Prints information about indexes in a table. 152 */ 153 private static void printIndexes(DatabaseMetaData metaData, String tableName) throws SQLException { 154 System.out.println(" Indexes:"); 155 try (ResultSet indexes = metaData.getIndexInfo(null, null, tableName, false, false)) { 156 boolean foundIndexes = false; 157 String lastIndexName = null; 158 StringBuilder indexColumns = new StringBuilder(); 159 160 while (indexes.next()) { 161 String indexName = indexes.getString("INDEX_NAME"); 162 String columnName = indexes.getString("COLUMN_NAME"); 163 boolean nonUnique = indexes.getBoolean("NON_UNIQUE"); 164 165 if (indexName == null) { 166 continue; // Skip table statistics 167 } 168 169 if (lastIndexName != null && !lastIndexName.equals(indexName)) { 170 // Print the previous index 171 System.out.println(" - " + lastIndexName + " (" + indexColumns + ")"); 172 indexColumns.setLength(0); 173 } 174 175 if (indexColumns.length() > 0) { 176 indexColumns.append(", "); 177 } 178 indexColumns.append(columnName); 179 lastIndexName = indexName; 180 foundIndexes = true; 181 } 182 183 // Print the last index 184 if (lastIndexName != null) { 185 System.out.println(" - " + lastIndexName + " (" + indexColumns + ")"); 186 } 187 188 if (!foundIndexes) { 189 System.out.println(" None"); 190 } 191 } 192 } 193 194 /** 195 * Main method that takes a database file path and prints the schema. 196 * 197 * @param args command line arguments where args[0] is the path to the H2 database file 198 * @throws SQLException if a database error occurs 199 */ 200 public static void main(String[] args) throws SQLException { 201 if (args.length < 1) { 202 System.err.println("Missing database file path argument"); 203 System.err.println("Usage: java PrintH2DatabaseSchema <database-file-path>"); 204 return; 205 } 206 207 String dbFilePath = args[0]; 208 File dbFile = new File(dbFilePath); 209 210 System.out.println("Reading schema from H2 database: " + dbFile.getAbsolutePath()); 211 System.out.println(); 212 213 try (Connection connection = H2DatabaseHelper.createFileBasedConnection(dbFile)) { 214 printDatabaseSchema(connection); 215 } 216 } 217}