package de.superx.bin; import static de.superx.servlet.SxSQL_Server.DEFAULT_MANDANTEN_ID; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.JDBCType; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Optional; import javax.sql.DataSource; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFDataFormat; import org.apache.poi.xssf.usermodel.XSSFFont; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.RowMapper; import de.superx.servlet.SuperXManager; import de.superx.servlet.SxPools; import de.superx.spring.AppConfig; import de.superx.spring.batch.His1DataSources; import de.superx.spring.cli.config.CLIConfig; import de.superx.spring.config.DataJdbcConfiguration; import de.superx.spring.config.ServiceConfig; /** * A utility for creating data profiling statistics for tables in a database * to be used in Data Warehouse Design. * This can be used as a command line utility or embedded in an application. */ public class DataProfiler { private final static String SQL_COUNT_NULL = "select count(*) from %s where %s is null"; private final static String SQL_PERCENT_UNIQUE = "select\n" + " count(distinct %s) as unique_anz,\n" + " count(distinct %s)::float / count(*) * 100 as unique_percentage\n" + "from\n" + " %s;"; private final static String SQL_RANKING = "select %s, count(*) as anz from %s where %s is not null group by %s order by 2 desc limit 10;"; private final static String SQL_MIN_MAX_LEN = "select min(length(%s)) as min_length, max(length(%s)) as max_length\n" + "from %s\n" + "where %s is not null"; private final static String SQL_COUNT_VALUE = "select count(*) from %s where %s = %s"; private final static String SQL_MIN_MAX_AVG = "select min(%s), max(%s), avg(%s) from %s where %s is not null;"; private final static String SQL_START_END = "select min(%s), max(%s) from %s where %s is not null"; private static GenericApplicationContext APPLICATION_CONTEXT = null; private static String HELP_STRING = "Use this tool to profile database tables for dwh design. " + "It needs the config file 'his1_databases.properties' inside the classpath; " + "this file gets written automatically when starting the web application."; static Logger logger = Logger.getLogger(DataProfiler.class); private DataSource dataSource; private String database; private String schema; private String[] tables; /** * Instantiate a new DataProfiler. * @param dataSource The DataSource from which to read the table statistics. * @param schema The schema from which to read. If null public is assumed. * @param tables An Array of the names of the tables for which statistics should be created. */ public DataProfiler(DataSource dataSource, String schema, String[] tables) { try (Connection con = dataSource.getConnection()) { this.database = con.getCatalog(); } catch (SQLException e) { logger.error("Couldn't read catalog", e); } this.schema = schema != null ? schema : "public"; List tableList = Arrays.asList(tables); // TODO: Make sorting configurable? tableList.sort(null); this.tables = tableList.toArray(new String[] {}); this.dataSource = dataSource; } public static void main(String[] args) { System.setProperty(SuperXManager.SUPER_X_HISINONE_VERSION, "non-empty-value"); Options options = createOptions(); CommandLine parsedArgs = parseArgs(args, options); if (parsedArgs.hasOption("h")) { printHelp(options); System.exit(0); } String database = null; String schema = "public"; String[] tables = null; if (parsedArgs.hasOption("d")) { database = parsedArgs.getOptionValue('d'); } if (parsedArgs.hasOption("s")) { schema = parsedArgs.getOptionValue('s'); } if (parsedArgs.hasOption("t")) { tables = parsedArgs.getOptionValues('t'); } if (!parsedArgs.hasOption('d') || !parsedArgs.hasOption('t')) { printHelp(options); System.exit(0); } try (GenericApplicationContext context = createContext()) { initSxPools(); DataSource dataSource = context.getBean(His1DataSources.class).get(database); DataProfiler profiler = new DataProfiler(dataSource, schema, tables); profiler.outputExcel(profiler.createStatistics(), null); } } /** * Create the List of TableStatistics. * @return List of TableStatistics. */ public List createStatistics() { List tableStats = new ArrayList<>(); try { JdbcTemplate jt = new JdbcTemplate(dataSource); logger.info("Database: " + database); logger.info("Schema: " + schema); logger.info("Tables: " + Arrays.asList(tables)); try (Connection con = dataSource.getConnection()) { jt.execute("set search_path to " + schema); DatabaseMetaData meta = con.getMetaData(); for (String table : tables) { long rowCount = jt.queryForObject("select count(*) from " + table, Long.class).longValue(); TableStatistic tableStat = new TableStatistic(table, rowCount); logger.info("Table " + table); try(ResultSet columns = meta.getColumns(null, schema, table, null); ResultSet exported = meta.getExportedKeys(null, schema, table); ResultSet imported = meta.getImportedKeys(null, schema, table); ResultSet pks = meta.getPrimaryKeys(null, schema, table)) { while(columns.next()) { ColumnStatistic columnStat = new ColumnStatistic(); columnStat.name = columns.getString("COLUMN_NAME"); columnStat.size = columns.getInt("COLUMN_SIZE"); columnStat.decimalDigits = columns.getInt("DECIMAL_DIGITS"); columnStat.type = JDBCType.valueOf(columns.getInt("DATA_TYPE")); columnStat.comment = columns.getString("REMARKS"); columnStat.isNullable = columns.getString("IS_NULLABLE").equalsIgnoreCase("yes"); columnStat.isAutoincrement = columns.getString("IS_AUTOINCREMENT").equalsIgnoreCase("yes"); columnStat.countNull = jt.queryForObject(String.format(SQL_COUNT_NULL, tableStat.name, columnStat.name), Long.class).longValue(); columnStat.percentNull = (double) columnStat.countNull / (double) tableStat.rowCount * 100.0; jt.query(String.format(SQL_PERCENT_UNIQUE, columnStat.name, columnStat.name, tableStat.name), new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { columnStat.uniqueCount = rs.getLong(1); columnStat.uniquePercent = rs.getDouble(2); } }); columnStat.ranking = jt.query(String.format(SQL_RANKING, columnStat.name, tableStat.name, columnStat.name, columnStat.name), new RowMapper() { @Override public RankingEntry mapRow(ResultSet rs, int rowNum) throws SQLException { return new RankingEntry(rs.getString(1), rs.getInt(2)); } }); switch (columnStat.type) { case CHAR: case NCHAR: case VARCHAR: case NVARCHAR: case LONGVARCHAR: case LONGNVARCHAR: jt.query(String.format(SQL_MIN_MAX_LEN, columnStat.name, columnStat.name, tableStat.name, columnStat.name), new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { columnStat.minLen = Optional.of(Integer.valueOf(rs.getInt(1))); columnStat.maxLen = Optional.of(Integer.valueOf(rs.getInt(2))); } }); columnStat.min_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, "length(" + columnStat.name + ")", columnStat.minLen.get()), Integer.class)); columnStat.max_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, "length(" + columnStat.name + ")", columnStat.maxLen.get()), Integer.class)); break; case BIGINT: case DECIMAL: case DOUBLE: case FLOAT: case INTEGER: case REAL: case NUMERIC: case SMALLINT: case TINYINT: jt.query(String.format(SQL_MIN_MAX_AVG, columnStat.name, columnStat.name, columnStat.name, tableStat.name, columnStat.name), new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { columnStat.min = Optional.of(Double.valueOf(rs.getDouble(1))); columnStat.max = Optional.of(Double.valueOf(rs.getDouble(2))); columnStat.avg = Optional.of(Double.valueOf(rs.getDouble(3))); } }); columnStat.min_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, columnStat.name, columnStat.min.get()), Integer.class)); columnStat.max_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, columnStat.name, columnStat.max.get()), Integer.class)); break; case DATE: jt.query(String.format(SQL_START_END, columnStat.name, columnStat.name, tableStat.name, columnStat.name), new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { columnStat.earliestDate = Optional.ofNullable(rs.getDate(1)); columnStat.latestDate = Optional.ofNullable(rs.getDate(2)); } }); if (columnStat.earliestDate.isPresent()) { columnStat.min_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, columnStat.name, quote(columnStat.earliestDate.get())), Integer.class)); } if (columnStat.latestDate.isPresent()) { columnStat.max_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, columnStat.name, quote(columnStat.latestDate.get())), Integer.class)); } break; case TIMESTAMP: case TIMESTAMP_WITH_TIMEZONE: jt.query(String.format(SQL_START_END, columnStat.name, columnStat.name, tableStat.name, columnStat.name), new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { columnStat.earliestTimestamp = Optional.ofNullable(rs.getTimestamp(1)); columnStat.latestTimestamp = Optional.ofNullable(rs.getTimestamp(2)); } }); if (columnStat.earliestTimestamp.isPresent()) { columnStat.min_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, columnStat.name, quote(columnStat.earliestTimestamp.get())), Integer.class)); } if (columnStat.latestTimestamp.isPresent()) { columnStat.max_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, columnStat.name, quote(columnStat.latestTimestamp.get())), Integer.class)); } break; case TIME: case TIME_WITH_TIMEZONE: jt.query(String.format(SQL_START_END, columnStat.name, columnStat.name, tableStat.name, columnStat.name), new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { columnStat.earliestTime = Optional.ofNullable(rs.getTime(1)); columnStat.latestTime = Optional.ofNullable(rs.getTime(2)); } }); if (columnStat.earliestTime.isPresent()) { columnStat.min_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, columnStat.name, quote(columnStat.earliestTime.get())), Integer.class)); } if (columnStat.latestTime.isPresent()) { columnStat.max_count = Optional.of(jt.queryForObject(String.format(SQL_COUNT_VALUE, tableStat.name, columnStat.name, quote(columnStat.latestTime.get())), Integer.class)); } break; default: } tableStat.columns.add(columnStat); } while (exported.next()) { String fromColumn = exported.getString("PKCOLUMN_NAME"); String toTable = exported.getString("FKTABLE_NAME"); String toColumn = exported .getString("FKCOLUMN_NAME"); tableStat.exportedKeys.add(new ForeignKey(fromColumn, toTable, toColumn)); } while (imported.next()) { String fromColumn = imported.getString("FKCOLUMN_NAME"); String toTable = imported.getString("PKTABLE_NAME"); String toColumn = imported .getString("PKCOLUMN_NAME"); tableStat.importedKeys.add(new ForeignKey(fromColumn, toTable, toColumn)); } while (pks.next()) { String column = pks.getString("COLUMN_NAME"); tableStat.primaryKeys.add(column); } } tableStats.add(tableStat); } } } catch (SQLException e) { logger.error("SQL Fehler", e); } return tableStats; } /** * Output statistic for a list of tables to an Excel file. * The statistics of each table are written to a separate sheet. * @param tableStats The list of TableStats * @param outputFile The File to output to. If null output to current dir with a default file name. */ public void outputExcel(List tableStats, File outputFile) { XSSFWorkbook workbook = new XSSFWorkbook(); XSSFDataFormat dataFormat = workbook.createDataFormat(); XSSFCellStyle cellStyleDouble = workbook.createCellStyle(); cellStyleDouble.setDataFormat(dataFormat.getFormat("0.##")); XSSFCellStyle headerStyle = workbook.createCellStyle(); XSSFFont bold = workbook.createFont(); bold.setBold(true); headerStyle.setFont(bold); for (TableStatistic tableStat : tableStats) { XSSFSheet sheet = workbook.createSheet("Table " + tableStat.name); XSSFRow header = sheet.createRow(0); XSSFCell cell = header.createCell(0); cell.setCellValue("Database: " + database); cell.setCellStyle(headerStyle); cell = header.createCell(1); cell.setCellStyle(headerStyle); cell.setCellValue("Schema: " + schema); XSSFRow first = sheet.createRow(2); first.createCell(0).setCellValue("Table"); first.getCell(0).setCellStyle(headerStyle); first.createCell(1).setCellValue("Row Count"); first.getCell(1).setCellStyle(headerStyle); first.createCell(2).setCellValue("Column"); first.getCell(2).setCellStyle(headerStyle); first.createCell(3).setCellValue("Type"); first.getCell(3).setCellStyle(headerStyle); first.createCell(4).setCellValue("Size"); first.getCell(4).setCellStyle(headerStyle); first.createCell(5).setCellValue("Not Null"); first.getCell(5).setCellStyle(headerStyle); first.createCell(6).setCellValue("Autoincrement"); first.getCell(6).setCellStyle(headerStyle); first.createCell(7).setCellValue("Count NULL"); first.getCell(7).setCellStyle(headerStyle); first.createCell(8).setCellValue("% NULL"); first.getCell(8).setCellStyle(headerStyle); first.createCell(9).setCellValue("Count Unique"); first.getCell(9).setCellStyle(headerStyle); first.createCell(10).setCellValue("% Unique"); first.getCell(10).setCellStyle(headerStyle); first.createCell(11).setCellValue("Min Len"); first.getCell(11).setCellStyle(headerStyle); first.createCell(12).setCellValue("Max Len"); first.getCell(12).setCellStyle(headerStyle); first.createCell(13).setCellValue("Min"); first.getCell(13).setCellStyle(headerStyle); first.createCell(14).setCellValue("Max"); first.getCell(14).setCellStyle(headerStyle); first.createCell(15).setCellValue("Avg"); first.getCell(15).setCellStyle(headerStyle); first.createCell(16).setCellValue("Min Count"); first.getCell(16).setCellStyle(headerStyle); first.createCell(17).setCellValue("Max Count"); first.getCell(17).setCellStyle(headerStyle); first.createCell(18).setCellValue("Earliest"); first.getCell(18).setCellStyle(headerStyle); first.createCell(19).setCellValue("Latest"); first.getCell(19).setCellStyle(headerStyle); first.createCell(20).setCellValue("Comment"); first.getCell(20).setCellStyle(headerStyle); int row = 3; XSSFRow tableRow = sheet.createRow(row); tableRow.createCell(0).setCellValue(tableStat.name); tableRow.getCell(0).setCellStyle(headerStyle); tableRow.createCell(1).setCellValue(tableStat.rowCount); tableRow.getCell(1).setCellStyle(headerStyle); for (ColumnStatistic columnStat : tableStat.columns) { row += 1; XSSFRow descRow = sheet.createRow(row); descRow.createCell(2).setCellValue(columnStat.name); if (tableStat.primaryKeys.contains(columnStat.name)) { descRow.getCell(2).setCellValue(columnStat.name + " (PK)"); descRow.getCell(2).setCellStyle(headerStyle); } descRow.createCell(3).setCellValue(columnStat.type.getName()); descRow.createCell(4).setCellValue(columnStat.size); if (columnStat.decimalDigits != 0) { descRow.getCell(4).setCellValue( Double.valueOf(columnStat.size + "." + columnStat.decimalDigits).doubleValue() ); descRow.getCell(4).setCellStyle(cellStyleDouble); } descRow.createCell(5).setCellValue(!columnStat.isNullable); descRow.createCell(6).setCellValue(columnStat.isAutoincrement); descRow.createCell(7).setCellValue(columnStat.countNull); descRow.createCell(8).setCellValue(columnStat.percentNull); descRow.getCell(8).setCellStyle(cellStyleDouble); descRow.createCell(9).setCellValue(columnStat.uniqueCount); descRow.createCell(10).setCellValue(columnStat.uniquePercent); descRow.getCell(10).setCellStyle(cellStyleDouble); if (columnStat.minLen.isPresent()) { descRow.createCell(11).setCellValue(columnStat.minLen.get().doubleValue()); } if (columnStat.maxLen.isPresent()) { descRow.createCell(12).setCellValue(columnStat.maxLen.get().doubleValue()); } if (columnStat.min.isPresent()) { descRow.createCell(13).setCellValue(columnStat.min.get().doubleValue()); descRow.getCell(13).setCellStyle(cellStyleDouble); } if (columnStat.max.isPresent()) { descRow.createCell(14).setCellValue(columnStat.max.get().doubleValue()); descRow.getCell(14).setCellStyle(cellStyleDouble); } if (columnStat.avg.isPresent()) { descRow.createCell(15).setCellValue(columnStat.avg.get().doubleValue()); descRow.getCell(15).setCellStyle(cellStyleDouble); } if (columnStat.min_count.isPresent()) { descRow.createCell(16).setCellValue(columnStat.min_count.get().doubleValue()); descRow.getCell(16).setCellStyle(cellStyleDouble); } if (columnStat.max_count.isPresent()) { descRow.createCell(17).setCellValue(columnStat.max_count.get().doubleValue()); descRow.getCell(17).setCellStyle(cellStyleDouble); } if (columnStat.earliestDate.isPresent()) { descRow.createCell(18).setCellValue(columnStat.earliestDate.get().toString()); } if (columnStat.latestDate.isPresent()) { descRow.createCell(19).setCellValue(columnStat.latestDate.get().toString()); } if (columnStat.earliestTime.isPresent()) { descRow.createCell(18).setCellValue(columnStat.earliestTime.get().toString()); } if (columnStat.latestTime.isPresent()) { descRow.createCell(19).setCellValue(columnStat.latestTime.get().toString()); } if (columnStat.earliestTimestamp.isPresent()) { descRow.createCell(18).setCellValue(columnStat.earliestTimestamp.get().toString()); } if (columnStat.latestTimestamp.isPresent()) { descRow.createCell(19).setCellValue(columnStat.latestTimestamp.get().toString()); } descRow.createCell(20).setCellValue(columnStat.comment); } for (int i = 0; i < 20; i++) { sheet.autoSizeColumn(i); } XSSFRow frequHeader1 = sheet.createRow(row + 2); XSSFRow frequHeader2 = sheet.createRow(row + 3); frequHeader1.createCell(0).setCellValue("Frequency"); frequHeader1.getCell(0).setCellStyle(headerStyle); frequHeader2.createCell(0).setCellValue("Column"); frequHeader2.getCell(0).setCellStyle(headerStyle); for (int n = 1; n <= 10; n++) { frequHeader1.createCell(n).setCellValue(n); frequHeader1.getCell(n).setCellStyle(headerStyle); } for (int colNr = 0; colNr < tableStat.columns.size(); colNr++) { XSSFRow frequRowLabel = sheet.createRow(row + 4 + 2 * colNr); XSSFRow frequRowCount = sheet.createRow(row + 5 + 2 * colNr); frequRowLabel.createCell(0).setCellValue(tableStat.columns.get(colNr).name); for (int rankNr = 0; rankNr < tableStat.columns.get(colNr).ranking.size(); rankNr++) { frequRowLabel.createCell(rankNr + 1).setCellValue(tableStat.columns.get(colNr).ranking.get(rankNr).label); frequRowCount.createCell(rankNr + 1).setCellValue(tableStat.columns.get(colNr).ranking.get(rankNr).count); } } row = row + 2 * tableStat.columns.size() + 5; XSSFRow exHeader1 = sheet.createRow(row); exHeader1.createCell(0).setCellValue("Exported Keys"); exHeader1.getCell(0).setCellStyle(headerStyle); XSSFRow exHeader2 = sheet.createRow(row + 1); exHeader2.createCell(0).setCellValue("From Column"); exHeader2.getCell(0).setCellStyle(headerStyle); exHeader2.createCell(1).setCellValue("To Table"); exHeader2.getCell(1).setCellStyle(headerStyle); exHeader2.createCell(2).setCellValue("To Column"); exHeader2.getCell(2).setCellStyle(headerStyle); for (int fkNr = 0; fkNr < tableStat.exportedKeys.size(); fkNr++) { XSSFRow fkRow = sheet.createRow(row + 2 + fkNr); fkRow.createCell(0).setCellValue(tableStat.exportedKeys.get(fkNr).fromColumn); fkRow.createCell(1).setCellValue(tableStat.exportedKeys.get(fkNr).toTable); fkRow.createCell(2).setCellValue(tableStat.exportedKeys.get(fkNr).toColumn); } row = row + 3 + tableStat.exportedKeys.size(); XSSFRow imHeader1 = sheet.createRow(row); imHeader1.createCell(0).setCellValue("Imported Keys"); imHeader1.getCell(0).setCellStyle(headerStyle); XSSFRow imHeader2 = sheet.createRow(row + 1); imHeader2.createCell(0).setCellValue("From Table"); imHeader2.getCell(0).setCellStyle(headerStyle); imHeader2.createCell(1).setCellValue("From Column"); imHeader2.getCell(1).setCellStyle(headerStyle); imHeader2.createCell(2).setCellValue("To Column"); imHeader2.getCell(2).setCellStyle(headerStyle); for (int fkNr = 0; fkNr < tableStat.importedKeys.size(); fkNr++) { XSSFRow fkRow = sheet.createRow(row + 2 + fkNr); fkRow.createCell(0).setCellValue(tableStat.importedKeys.get(fkNr).toTable); fkRow.createCell(1).setCellValue(tableStat.importedKeys.get(fkNr).toColumn); fkRow.createCell(2).setCellValue(tableStat.importedKeys.get(fkNr).fromColumn); } } File currDir = new File("."); String path = currDir.getAbsolutePath(); String fileLocation = path.substring(0, path.length() - 1) + "db_profile_" + database + ".xlsx"; if (outputFile != null) { fileLocation = outputFile.getAbsolutePath(); } logger.info("Writing to " + fileLocation); FileOutputStream outputStream; try { outputStream = new FileOutputStream(fileLocation); workbook.write(outputStream); workbook.close(); } catch (IOException e) { logger.error("Couldn't write excel file", e); } } private static Options createOptions() { Options options = new Options(); Option opt; opt = new Option("h", "help", false, "get help"); options.addOption(opt); opt = new Option("t", "tables", true, "tables"); opt.setArgs(Option.UNLIMITED_VALUES); // opt.setRequired(true); options.addOption(opt); opt = new Option("s", "schema", true, "schema"); options.addOption(opt); opt = new Option("d", "database", true, "database"); // opt.setRequired(true); options.addOption(opt); return options; } private static CommandLine parseArgs(String[] args, Options options) { CommandLineParser parser = new GnuParser(); try { return parser.parse(options, args, false); } catch (ParseException e) { System.out.println("error while reading the command line parameters:"); e.printStackTrace(); System.exit(1); } return null; } private static void initSxPools() { try { List mandantenNamen = new LinkedList(); mandantenNamen.add(DEFAULT_MANDANTEN_ID); SxPools.closeAll(); SxPools.init(mandantenNamen); SxPools.get(DEFAULT_MANDANTEN_ID).init(); SxPools.get(DEFAULT_MANDANTEN_ID).initLogging(true, Level.DEBUG); // also init kettle env, set plugin dir SuperXManager.initKettleEnv(APPLICATION_CONTEXT); } catch (Exception e) { System.out.println("error while initialising the SuperX pools:"); e.printStackTrace(); System.exit(1); } } private static void printHelp(Options options) { HelpFormatter help = new HelpFormatter(); help.printHelp(HELP_STRING, options); } private static GenericApplicationContext createContext() { /* * https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/AnnotationConfigApplicationContext.html * quote: * "In case of multiple @Configuration classes, @Bean methods defined in later classes will override those defined in earlier classes. * This can be leveraged to deliberately override certain bean definitions via an extra @Configuration class." * - so it's alright to override some beans via "CLIConfig" */ if (APPLICATION_CONTEXT == null) { APPLICATION_CONTEXT = new AnnotationConfigApplicationContext(AppConfig.class, DataJdbcConfiguration.class, CLIConfig.class, ServiceConfig.class); } return APPLICATION_CONTEXT; } private static String quote(Object o) { return "'" + o + "'"; } } class TableStatistic { public String name; public long rowCount; public List columns; public List exportedKeys; public List importedKeys; public List primaryKeys; public TableStatistic(String name, long rowCount) { this.name = name; this.rowCount = rowCount; this.columns = new ArrayList<>(); this.exportedKeys = new ArrayList<>(); this.importedKeys = new ArrayList<>(); this.primaryKeys = new ArrayList<>(); } } class ColumnStatistic { public String name; public JDBCType type; public int size; public int decimalDigits; public boolean isNullable; public boolean isAutoincrement; public String comment; public long countNull; public double percentNull; public long uniqueCount; public double uniquePercent; public Optional max_count = Optional.empty(); public Optional min_count = Optional.empty(); public List ranking; public Optional minLen = Optional.empty(); public Optional maxLen = Optional.empty(); public Optional min = Optional.empty(); public Optional max = Optional.empty(); public Optional avg = Optional.empty(); public Optional earliestDate = Optional.empty(); public Optional latestDate = Optional.empty(); public Optional