org.tentackle.sql.metadata.TableMetaData Maven / Gradle / Ivy
The newest version!
/*
* Tentackle - https://tentackle.org.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.tentackle.sql.metadata;
import org.tentackle.sql.BackendException;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
/**
* Database metadata for a table that is part of the model.
*
* @author harald
*/
public class TableMetaData {
private final ModelMetaData modelMetaData; // the model containing this table
private final String modelTableName; // the full table name including optional schema
private String schemaName; // the schema name, null if default or none
private String tableName; // the tablename without schema
private String comment; // the comment
private final Collection columns; // the columns
private final Map indexes; // the indexes
private final Map foreignKeys; // the foreign keys
/**
* Creates a table meta instance.
*
* @param modelMetaData the model this table belongs to
* @param modelTableName the tablename used in the model
*/
public TableMetaData(ModelMetaData modelMetaData, String modelTableName) {
this.modelMetaData = modelMetaData;
this.modelTableName = modelTableName;
columns = new ArrayList<>();
indexes = new TreeMap<>();
foreignKeys = new TreeMap<>();
}
/**
* Sets up the column from the database metadata result.
*
* @param metaData the database metadata
* @param schemas valid schemas to scan, null if all
* @param schemaPattern the schema (null if none, "" if without schema)
* @param tablePattern the table pattern
* @throws SQLException the processing failed
* @throws BackendException if logical meta data processing error
*/
public void setupTableFromMetaData(DatabaseMetaData metaData, String[] schemas, String schemaPattern, String tablePattern) throws SQLException {
try (ResultSet resultSet = metaData.getTables(null, schemaPattern, tablePattern, null)) {
boolean valid = false; // true if valid table found
while (resultSet.next()) {
schemaName = resultSet.getString("TABLE_SCHEM");
tableName = resultSet.getString("TABLE_NAME");
comment = resultSet.getString("REMARKS");
// restrict to optional schemas
if (isValidSchema(schemaName, schemas)) {
valid = true;
break;
}
}
if (!valid) {
throw new BackendException("no metadata for schema " + schemaPattern + ", table " + tablePattern);
}
}
try (ResultSet resultSet = metaData.getColumns(null, schemaName, tableName, null)) {
while (resultSet.next()) {
// check same schema, tablename
String schema = resultSet.getString("TABLE_SCHEM");
String table = resultSet.getString("TABLE_NAME");
// check that we're getting the same results
if (schema != null && schemaName != null && !schemaName.equals(schema)) {
throw new BackendException("different schemas apply to '" + (schemaPattern == null ? "" : (schemaPattern + ".")) + tablePattern + "': " +
schemaName + " != " + schema);
}
else if (schema == null && schemaName != null ||
schema != null && schemaName == null) {
// schema is null??
throw new BackendException("null- and non-null schemas found for '" + (schemaPattern == null ? "" : (schemaPattern + ".")) + tablePattern + "': " +
(schemaName == null ? "" : schemaName) + " != " +
(schema == null ? "" : schema));
}
if (table != null && tableName != null && !tableName.equals(table)) {
throw new BackendException("different tablenames apply to '" + (schemaPattern == null ? "" : (schemaPattern + ".")) + tablePattern + "': " +
tableName + " != " + table);
}
else if (table == null && tableName != null ||
table != null && tableName == null) {
// table is null??
throw new BackendException("null- and non-null tablenames found for '" + (schemaPattern == null ? "" : (schemaPattern + ".")) + tablePattern + "': " +
(tableName == null ? "" : tableName) + " != " +
(table == null ? "" : table));
}
ColumnMetaData columnMetaData = modelMetaData.getBackend().createColumnMetaData(this);
columnMetaData.setupColumnFromMetaData(resultSet);
if (columnMetaData.getColumnName() != null &&
!modelMetaData.getBackend().isTemporaryName(columnMetaData.getColumnName())) {
columns.add(columnMetaData);
}
}
}
// extract index information
try (ResultSet resultSet = metaData.getIndexInfo(null, schemaName, tableName, false, false)) {
while (resultSet.next()) {
String indexName = resultSet.getString("INDEX_NAME");
if (indexName != null && !modelMetaData.getBackend().isTemporaryName(indexName)) {
// find index metadata, if new: create one
IndexMetaData indexMetaData = indexes.get(indexName);
if (indexMetaData == null) {
// new: create it and add to tableMetaData
indexMetaData = modelMetaData.getBackend().createIndexMetaData(this);
indexMetaData.setupIndexFromMetaData(resultSet);
indexes.put(indexName, indexMetaData);
}
indexMetaData.addIndexColumnFromMetaData(resultSet);
}
}
}
// extract foreign keys
ForeignKeyMetaData foreignKeyMetaData;
try (ResultSet resultSet = metaData.getImportedKeys(null, schemaName, tableName)) {
while (resultSet.next()) {
short keySeq = resultSet.getShort("KEY_SEQ");
if (keySeq == 1) {
// new foreign key
foreignKeyMetaData = new ForeignKeyMetaData(this);
foreignKeyMetaData.setupForeignKeyFromMetaData(resultSet);
if (foreignKeyMetaData.getForeignKeyName() != null &&
!modelMetaData.getBackend().isTemporaryName(foreignKeyMetaData.getForeignKeyName())) {
foreignKeys.put(foreignKeyMetaData.getForeignKeyName(), foreignKeyMetaData);
}
}
else {
throw new BackendException("unexpected KEY_SEQ " + keySeq + " in foreign key meta data");
}
ForeignKeyColumnMetaData column = new ForeignKeyColumnMetaData(foreignKeyMetaData);
column.setupForeignKeyColumnFromMetaData(resultSet);
foreignKeyMetaData.addForeignKeyColumn(column);
}
}
validate();
}
/**
* Gets the model this table belongs to.
*
* @return the model
*/
public ModelMetaData getModelMetaData() {
return modelMetaData;
}
/**
* Gets the table name used in the model.
*
* @return the model's tablename
*/
public String getModelTableName() {
return modelTableName;
}
/**
* Gets the database schema name.
* Always in lowercase.
*
* @return the schema, null if none
*/
public String getSchemaName() {
return schemaName;
}
/**
* Gets the database table name.
* Always in lowercase.
*
* @return the table name (without schema)
*/
public String getTableName() {
return tableName;
}
/**
* Gets the database table name with optional schemaname prepended.
* Always in lowercase.
*
* @return the full table name
*/
public String getFullTableName() {
if (schemaName != null) {
return schemaName + "." + tableName;
}
return tableName;
}
/**
* Gets the comment.
*
* @return the comment, null if none
*/
public String getComment() {
return comment;
}
/**
* Sets the comment.
*
* @param comment the comment, null if none
*/
public void setComment(String comment) {
this.comment = comment;
}
/**
* Gets the metadata for columns.
*
* @return the columns
*/
public Collection getColumns() {
return columns;
}
/**
* Gets a column by its column name.
*
* @param columnName the column name
* @return the column, null if no such column
*/
public ColumnMetaData getColumnByName(String columnName) {
columnName = columnName.toLowerCase(Locale.ROOT);
for (ColumnMetaData column: columns) {
if (column.getColumnName().equals(columnName)) {
return column;
}
}
return null;
}
/**
* Gets the metadata for indexes.
*
* @return the indexes
*/
public Collection getIndexes() {
return indexes.values();
}
/**
* Gets the foreign keys.
*
* @return the foreign keys
*/
public Collection getForeignKeys() {
return foreignKeys.values();
}
/**
* Validates and post-processes the table data.
*/
public void validate() {
String defaultSchema = getModelMetaData().getBackend().getDefaultSchema();
if (defaultSchema != null && defaultSchema.equalsIgnoreCase(schemaName)) {
schemaName = null;
}
if (comment != null && comment.isEmpty()) {
comment = null;
}
}
@Override
public int hashCode() {
int hash = 5;
hash = 71 * hash + Objects.hashCode(this.schemaName);
hash = 71 * hash + Objects.hashCode(this.tableName);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TableMetaData other = (TableMetaData) obj;
if (!Objects.equals(this.schemaName, other.schemaName)) {
return false;
}
return Objects.equals(this.tableName, other.tableName);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("TABLE ");
buf.append(getFullTableName());
buf.append(" (");
for (ColumnMetaData column: getColumns()) {
buf.append("\n ");
buf.append(column);
}
buf.append("\n)");
for (IndexMetaData index: getIndexes()) {
buf.append("\n");
buf.append(index);
}
for (ForeignKeyMetaData foreignKey: getForeignKeys()) {
buf.append("\n");
buf.append(foreignKey);
}
buf.append("\n");
return buf.toString();
}
/**
* Checks whether given schema name is valid.
*
* @param schema the schema name
* @param schemas the (optional) schemas
* @return true if schema belongs to optional given schemas (or no schemas given)
*/
private boolean isValidSchema(String schema, String[] schemas) {
boolean valid = false;
if (schemas != null) {
if (schema != null) {
for (String s: schemas) {
if (s.equalsIgnoreCase(schema)) {
valid = true;
break;
}
}
}
}
else {
valid = true;
}
return valid;
}
}