DBDatabaseMetaData.java
/*
* Copyright 2023 Gregory Graham.
*
* Commercial licenses are available, please contact info@gregs.co.nz for details.
*
* This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
* or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
*
* You are free to:
* Share - copy and redistribute the material in any medium or format
* Adapt - remix, transform, and build upon the material
*
* The licensor cannot revoke these freedoms as long as you follow the license terms.
* Under the following terms:
*
* Attribution -
* You must give appropriate credit, provide a link to the license, and indicate if changes were made.
* You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
* NonCommercial -
* You may not use the material for commercial purposes.
* ShareAlike -
* If you remix, transform, or build upon the material,
* you must distribute your contributions under the same license as the original.
* No additional restrictions -
* You may not apply legal terms or technological measures that legally restrict others from doing anything the
* license permits.
*
* Check the Creative Commons website for any details, legalese, and updates.
*/
package nz.co.gregs.dbvolution.databases.metadata;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import nz.co.gregs.dbvolution.databases.DBDatabase;
import nz.co.gregs.dbvolution.databases.DBStatement;
import nz.co.gregs.dbvolution.databases.connections.DBConnection;
import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
import nz.co.gregs.dbvolution.datatypes.*;
import nz.co.gregs.dbvolution.exceptions.DBRuntimeException;
import nz.co.gregs.dbvolution.exceptions.UnknownJavaSQLTypeException;
import nz.co.gregs.dbvolution.generation.Utility;
import nz.co.gregs.dbvolution.utility.StringCheck;
import nz.co.gregs.regexi.Regex;
/**
*
* @author gregorygraham
*/
public class DBDatabaseMetaData {
private final DBDatabase database;
private final String packageName;
private final Options options;
private List<TableMetaData> finalList;
private String catalog;
private String schema;
public DBDatabaseMetaData(Options options) throws SQLException {
this.database = options.getDBDatabase();
this.packageName = options.getPackageName();
this.options = options.copy();
loadSchema();
}
protected void loadSchema() throws SQLException {
ArrayList<TableMetaData> tablesFound = new ArrayList<>(0);
DBDatabase db = this.database;
// should be impossible but just in case
if (db != null) {
// we'll need these later...
DBDefinition definition = db.getDefinition();
Regex nonSystemTableRegex = definition.getSystemTableExclusionPattern();
try (DBStatement dbStatement = db.getDBStatement()) {
DBConnection connection = dbStatement.getConnection();
catalog = connection.getCatalog();
schema = null;
try {
schema = connection.getSchema();
} catch (java.sql.SQLFeatureNotSupportedException nope) {
// SOMEONE DIDN'T WRITE THEIR DRIVER PROPERLY
} catch (java.lang.AbstractMethodError exp) {
// NOT USING Java 1.7+ apparently
} catch (IllegalArgumentException ex) {
// NOT USING Java 1.7+ apparently
} catch (SecurityException ex) {
// NOT USING Java 1.7+ apparently
}
DatabaseMetaData metaData = connection.getMetaData();
try (ResultSet tables = metaData.getTables(catalog, schema, null, options.getObjectTypes())) {
while (tables.next()) {
final String tableName = tables.getString("TABLE_NAME");
if (schema == null) {
schema = tables.getString("TABLE_SCHEM");
}
// don't model system tables
boolean nonSystemTableCheck = nonSystemTableRegex.matchesEntireString(tableName);
if (nonSystemTableCheck) {
// create the table model
TableMetaData tableMetaData = new TableMetaData(catalog, schema, tableName);
tablesFound.add(tableMetaData);
Map<String, TableMetaData.PrimaryKey> pkNames = new HashMap<>(0);
try (ResultSet primaryKeysRS = metaData.getPrimaryKeys(catalog, schema, tableName)) {
while (primaryKeysRS.next()) {
String pkColumnName = primaryKeysRS.getString("COLUMN_NAME");
storedFoundPrimaryKey(tableName, pkColumnName, pkNames, tableMetaData);
}
}
Map<String, TableMetaData.ForeignKey> fkNames = new HashMap<>(0);
try (ResultSet foreignKeysRS = metaData.getImportedKeys(catalog, schema, tableName)) {
while (foreignKeysRS.next()) {
String pkTableName = foreignKeysRS.getString("PKTABLE_NAME");
String pkColumnName = foreignKeysRS.getString("PKCOLUMN_NAME");
String fkColumnName = foreignKeysRS.getString("FKCOLUMN_NAME");
fkNames.put(fkColumnName, new TableMetaData.ForeignKey(tableName, fkColumnName, pkTableName, pkColumnName));
}
}
try (ResultSet columns = metaData.getColumns(catalog, schema, tableName, null)) {
while (columns.next()) {
TableMetaData.Column column = new TableMetaData.Column(catalog, schema, tableName);
tableMetaData.addColumn(column);
copyColumnData(column, columns, db);
TableMetaData.PrimaryKey pkNameFound = pkNames.get(column.columnName);
copyPrimaryKeyData(tableName, column, pkNameFound, tableMetaData, pkNames);
TableMetaData.ForeignKey fkData = fkNames.get(column.columnName);
copyForeignKeyData(fkData, column, tableMetaData, tableName);
}
}
}
}
}
}
}
postProcessing(options, tablesFound);
finalList = List.copyOf(tablesFound);
}
public void storedFoundPrimaryKey(final String tableName, String pkColumnName, Map<String, TableMetaData.PrimaryKey> pkNames, TableMetaData tableMetaData) {
TableMetaData.PrimaryKey primaryKey = new TableMetaData.PrimaryKey(schema, tableName, pkColumnName);
pkNames.put(pkColumnName, primaryKey);
tableMetaData.addPrimaryKey(primaryKey);
}
public void copyColumnData(TableMetaData.Column column, final ResultSet columns, DBDatabase db) throws SQLException {
column.setColumnName(columns.getString("COLUMN_NAME"));
try {
column.setReferencedTable(columns.getString("SCOPE_TABLE"));
} catch (SQLException exp) {
; // MSSQLServer throws an exception on this
}
column.precision = columns.getInt("COLUMN_SIZE");
column.comments = columns.getString("REMARKS");
String isAutoIncr = null;
try {
isAutoIncr = columns.getString("IS_AUTOINCREMENT");
} catch (SQLException sqlex) {
;// SQLite-JDBC throws an exception when retrieving IS_AUTOINCREMENT
}
column.isAutoIncrement = isAutoIncr != null && isAutoIncr.equals("YES");
try {
column.sqlDataTypeInt = columns.getInt("DATA_TYPE");
column.sqlDataTypeName = columns.getString("TYPE_NAME");
column.columnType = Utility.getQDTClassOfSQLType(db, column.sqlDataTypeName, column.sqlDataTypeInt, column.precision, options.getTrimCharColumns());
} catch (UnknownJavaSQLTypeException ex) {
column.columnType = DBUnknownDatatype.class;
column.javaSQLDatatype = ex.getUnknownJavaSQLType();
}
}
public void copyPrimaryKeyData(final String tableName, TableMetaData.Column column, TableMetaData.PrimaryKey pkNameFound, TableMetaData tableMetaData, Map<String, TableMetaData.PrimaryKey> pkNames) {
PrimaryKeyRecognisor pkRecog = options.getPkRecog();
if (pkRecog != null) {
boolean recogFound = pkRecog.isPrimaryKeyColumn(tableName, column.columnName);
if (pkNameFound == null && recogFound) {
column.isPrimaryKey = true;
storedFoundPrimaryKey(tableName, column.columnName, pkNames, tableMetaData);
}
}
}
public void copyForeignKeyData(TableMetaData.ForeignKey fkData, TableMetaData.Column column, TableMetaData tableMetaData, final String tableName) {
if (fkData != null) {
column.isForeignKey = true;
column.referencesTableName = fkData.primaryKeyTableName;
column.referencesClass = Utility.toClassCase(fkData.primaryKeyTableName);
column.referencesField = fkData.primaryKeyColumnName;
tableMetaData.addForeignKey(fkData);
} else {
ForeignKeyRecognisor fkRecog = options.getFkRecog();
if (fkRecog != null) {
if (fkRecog.isForeignKeyColumn(tableName, column.columnName)) {
column.isForeignKey = true;
column.referencesTableName = options.getFkRecog().getReferencedTable(tableName, column.columnName);
column.referencesField = options.getFkRecog().getReferencedColumn(tableName, column.columnName);
column.referencesClass = Utility.toClassCase(options.getFkRecog().getReferencedTable(tableName, column.columnName));
TableMetaData.ForeignKey fk = new TableMetaData.ForeignKey(tableName, column.columnName, column.referencesTableName, column.referencesField);
tableMetaData.addForeignKey(fk);
}
}
}
// sanity check
if (StringCheck.isEmptyOrNull(column.referencesClass, column.referencesField, column.referencesTableName)) {
column.isForeignKey = false;
column.referencesTableName = null;
column.referencesField = null;
column.referencesClass = null;
}
}
public String getCatalog() {
return catalog;
}
public String getSchema() {
return schema;
}
public List<TableMetaData> getTables() {
return finalList;
}
/**
* @return the database
*/
public DBDatabase getDatabase() {
return database;
}
/**
* @return the packageName
*/
public String getPackageName() {
return packageName;
}
/**
* @return the options
*/
public Options getOptions() {
return options;
}
protected void postProcessing(Options options, ArrayList<TableMetaData> tablesFound) throws SQLException, DBRuntimeException {
// default operation is a null op
}
}