public void checkDb(Map<String, ModelEntity> modelEntities, List<String> colWrongSize, List<String> messages, boolean checkPks, boolean checkFks, boolean checkFkIdx, boolean addMissing) {
if (isLegacy) {
throw new RuntimeException("Cannot run checkDb on a legacy database connection; configure a database helper (entityengine.xml)");
}
UtilTimer timer = new UtilTimer();
timer.timerString("Start - Before Get Database Meta Data");
// get ALL tables from this database
TreeSet<String> tableNames = this.getTableNames(messages);
TreeSet<String> fkTableNames = tableNames == null ? null : new TreeSet<String>(tableNames);
TreeSet<String> indexTableNames = tableNames == null ? null : new TreeSet<String>(tableNames);
if (tableNames == null) {
String message = "Could not get table name information from the database, aborting.";
if (messages != null) messages.add(message);
Debug.logError(message, module);
return;
}
timer.timerString("After Get All Table Names");
// get ALL column info, put into hashmap by table name
Map<String, Map<String, ColumnCheckInfo>> colInfo = this.getColumnInfo(tableNames, checkPks, messages);
if (colInfo == null) {
String message = "Could not get column information from the database, aborting.";
if (messages != null) messages.add(message);
Debug.logError(message, module);
return;
}
timer.timerString("After Get All Column Info");
// -make sure all entities have a corresponding table
// -list all tables that do not have a corresponding entity
// -display message if number of table columns does not match number of entity fields
// -list all columns that do not have a corresponding field
// -make sure each corresponding column is of the correct type
// -list all fields that do not have a corresponding column
timer.timerString("Before Individual Table/Column Check");
ArrayList<ModelEntity> modelEntityList = new ArrayList<ModelEntity>(modelEntities.values());
// sort using compareTo method on ModelEntity
Collections.sort(modelEntityList);
int curEnt = 0;
int totalEnt = modelEntityList.size();
List<ModelEntity> entitiesAdded = FastList.newInstance();
for (ModelEntity entity: modelEntityList) {
curEnt++;
// if this is a view entity, do not check it...
if (entity instanceof ModelViewEntity) {
String entMessage = "(" + timer.timeSinceLast() + "ms) NOT Checking #" + curEnt + "/" + totalEnt + " View Entity " + entity.getEntityName();
Debug.logVerbose(entMessage, module);
if (messages != null) messages.add(entMessage);
continue;
}
String entMessage = "(" + timer.timeSinceLast() + "ms) Checking #" + curEnt + "/" + totalEnt +
" Entity " + entity.getEntityName() + " with table " + entity.getTableName(datasourceInfo);
Debug.logVerbose(entMessage, module);
if (messages != null) messages.add(entMessage);
// -make sure all entities have a corresponding table
if (tableNames.contains(entity.getTableName(datasourceInfo))) {
tableNames.remove(entity.getTableName(datasourceInfo));
if (colInfo != null) {
Map<String, ModelField> fieldColNames = FastMap.newInstance();
Iterator<ModelField> fieldIter = entity.getFieldsIterator();
while (fieldIter.hasNext()) {
ModelField field = fieldIter.next();
fieldColNames.put(field.getColName(), field);
}
Map<String, ColumnCheckInfo> colMap = colInfo.get(entity.getTableName(datasourceInfo));
if (colMap != null) {
for (ColumnCheckInfo ccInfo: colMap.values()) {
// -list all columns that do not have a corresponding field
if (fieldColNames.containsKey(ccInfo.columnName)) {
ModelField field = null;
field = fieldColNames.remove(ccInfo.columnName);
ModelFieldType modelFieldType = modelFieldTypeReader.getModelFieldType(field.getType());
if (modelFieldType != null) {
// make sure each corresponding column is of the correct type
String fullTypeStr = modelFieldType.getSqlType();
String typeName;
int columnSize = -1;
int decimalDigits = -1;
int openParen = fullTypeStr.indexOf('(');
int closeParen = fullTypeStr.indexOf(')');
int comma = fullTypeStr.indexOf(',');
if (openParen > 0 && closeParen > 0 && closeParen > openParen) {
typeName = fullTypeStr.substring(0, openParen);
if (comma > 0 && comma > openParen && comma < closeParen) {
String csStr = fullTypeStr.substring(openParen + 1, comma);
try {
columnSize = Integer.parseInt(csStr);
} catch (NumberFormatException e) {
Debug.logError(e, module);
}
String ddStr = fullTypeStr.substring(comma + 1, closeParen);
try {
decimalDigits = Integer.parseInt(ddStr);
} catch (NumberFormatException e) {
Debug.logError(e, module);
}
} else {
String csStr = fullTypeStr.substring(openParen + 1, closeParen);
try {
columnSize = Integer.parseInt(csStr);
} catch (NumberFormatException e) {
Debug.logError(e, module);
}
}
} else {
typeName = fullTypeStr;
}
// override the default typeName with the sqlTypeAlias if it is specified
if (UtilValidate.isNotEmpty(modelFieldType.getSqlTypeAlias())) {
typeName = modelFieldType.getSqlTypeAlias();
}
// NOTE: this may need a toUpperCase in some cases, keep an eye on it, okay just compare with ignore case
if (!ccInfo.typeName.equalsIgnoreCase(typeName)) {
String message = "WARNING: Column [" + ccInfo.columnName + "] of table [" + entity.getTableName(datasourceInfo) + "] of entity [" +
entity.getEntityName() + "] is of type [" + ccInfo.typeName + "] in the database, but is defined as type [" +
typeName + "] in the entity definition.";
Debug.logError(message, module);
if (messages != null) messages.add(message);
}
if (columnSize != -1 && ccInfo.columnSize != -1 && columnSize != ccInfo.columnSize && (columnSize * 3) != ccInfo.columnSize) {
String message = "WARNING: Column [" + ccInfo.columnName + "] of table [" + entity.getTableName(datasourceInfo) + "] of entity [" +
entity.getEntityName() + "] has a column size of [" + ccInfo.columnSize +
"] in the database, but is defined to have a column size of [" + columnSize + "] in the entity definition.";
Debug.logWarning(message, module);
if (messages != null) messages.add(message);
if (columnSize > ccInfo.columnSize && colWrongSize != null) {
// add item to list of wrong sized columns; only if the entity is larger
colWrongSize.add(entity.getEntityName() + "." + field.getName());
}
}
if (decimalDigits != -1 && decimalDigits != ccInfo.decimalDigits) {
String message = "WARNING: Column [" + ccInfo.columnName + "] of table [" + entity.getTableName(datasourceInfo) + "] of entity [" +
entity.getEntityName() + "] has a decimalDigits of [" + ccInfo.decimalDigits +
"] in the database, but is defined to have a decimalDigits of [" + decimalDigits + "] in the entity definition.";
Debug.logWarning(message, module);
if (messages != null) messages.add(message);
}
// do primary key matching check
if (checkPks && ccInfo.isPk && !field.getIsPk()) {
String message = "WARNING: Column [" + ccInfo.columnName + "] of table [" + entity.getTableName(datasourceInfo) + "] of entity [" +
entity.getEntityName() + "] IS a primary key in the database, but IS NOT a primary key in the entity definition. The primary key for this table needs to be re-created or modified so that this column is NOT party of the primary key.";
Debug.logError(message, module);
if (messages != null) messages.add(message);
}
if (checkPks && !ccInfo.isPk && field.getIsPk()) {
String message = "WARNING: Column [" + ccInfo.columnName + "] of table [" + entity.getTableName(datasourceInfo) + "] of entity [" +
entity.getEntityName() + "] IS NOT a primary key in the database, but IS a primary key in the entity definition. The primary key for this table needs to be re-created or modified to add this column to the primary key. Note that data may need to be added first as a primary key column cannot have an null values.";
Debug.logError(message, module);
if (messages != null) messages.add(message);
}
} else {
String message = "Column [" + ccInfo.columnName + "] of table [" + entity.getTableName(datasourceInfo) + "] of entity [" + entity.getEntityName() +
"] has a field type name of [" + field.getType() + "] which is not found in the field type definitions";
Debug.logError(message, module);
if (messages != null) messages.add(message);
}
} else {
String message = "Column [" + ccInfo.columnName + "] of table [" + entity.getTableName(datasourceInfo) + "] of entity [" + entity.getEntityName() + "] exists in the database but has no corresponding field" + ((checkPks && ccInfo.isPk) ? " (and it is a PRIMARY KEY COLUMN)" : "");
Debug.logWarning(message, module);
if (messages != null) messages.add(message);
}
}
// -display message if number of table columns does not match number of entity fields
if (colMap.size() != entity.getFieldsSize()) {
String message = "Entity [" + entity.getEntityName() + "] has " + entity.getFieldsSize() + " fields but table [" + entity.getTableName(datasourceInfo) + "] has " + colMap.size() + " columns.";
Debug.logWarning(message, module);
if (messages != null) messages.add(message);
}
}
// -list all fields that do not have a corresponding column
for (String colName: fieldColNames.keySet()) {
ModelField field = (ModelField) fieldColNames.get(colName);
String message = "Field [" + field.getName() + "] of entity [" + entity.getEntityName() + "] is missing its corresponding column [" + field.getColName() + "]" + (field.getIsPk() ? " (and it is a PRIMARY KEY FIELD)" : "");
Debug.logWarning(message, module);
if (messages != null) messages.add(message);
if (addMissing) {
// add the column
String errMsg = addColumn(entity, field);
if (errMsg != null && errMsg.length() > 0) {
message = "Could not add column [" + field.getColName() + "] to table [" + entity.getTableName(datasourceInfo) + "]: " + errMsg;
Debug.logError(message, module);
if (messages != null) messages.add(message);
} else {
message = "Added column [" + field.getColName() + "] to table [" + entity.getTableName(datasourceInfo) + "]" + (field.getIsPk() ? " (NOTE: this is a PRIMARY KEY FIELD, but the primary key was not updated automatically (not considered a safe operation), be sure to fill in any needed data and re-create the primary key)" : "");
Debug.logImportant(message, module);
if (messages != null) messages.add(message);
}
}
}
}
} else {
String message = "Entity [" + entity.getEntityName() + "] has no table in the database";
Debug.logWarning(message, module);
if (messages != null) messages.add(message);
if (addMissing) {
// create the table
String errMsg = createTable(entity, modelEntities, false);
if (errMsg != null && errMsg.length() > 0) {
message = "Could not create table [" + entity.getTableName(datasourceInfo) + "]: " + errMsg;
Debug.logError(message, module);
if (messages != null) messages.add(message);
} else {
entitiesAdded.add(entity);
message = "Created table [" + entity.getTableName(datasourceInfo) + "]";
Debug.logImportant(message, module);
if (messages != null) messages.add(message);
}
}
}
}
timer.timerString("After Individual Table/Column Check");
// -list all tables that do not have a corresponding entity
for (String tableName: tableNames) {
String message = "Table named [" + tableName + "] exists in the database but has no corresponding entity";
Debug.logWarning(message, module);
if (messages != null) messages.add(message);
}
// for each newly added table, add fk indices
if (datasourceInfo.useFkIndices) {
int totalFkIndices = 0;
for (ModelEntity curEntity: entitiesAdded) {
if (curEntity.getRelationsOneSize() > 0) {
totalFkIndices += this.createForeignKeyIndices(curEntity, datasourceInfo.constraintNameClipLength, messages);
}
}
if (totalFkIndices > 0) Debug.logImportant("==== TOTAL Foreign Key Indices Created: " + totalFkIndices, module);
}
// for each newly added table, add fks
if (datasourceInfo.useFks) {
int totalFks = 0;
for (ModelEntity curEntity: entitiesAdded) {
totalFks += this.createForeignKeys(curEntity, modelEntities, datasourceInfo.constraintNameClipLength, datasourceInfo.fkStyle, datasourceInfo.useFkInitiallyDeferred, messages);
}
if (totalFks > 0) Debug.logImportant("==== TOTAL Foreign Keys Created: " + totalFks, module);
}
// for each newly added table, add declared indexes
if (datasourceInfo.useIndices) {
int totalDis = 0;
for (ModelEntity curEntity: entitiesAdded) {
if (curEntity.getIndexesSize() > 0) {
totalDis += this.createDeclaredIndices(curEntity, messages);
}
}
if (totalDis > 0) Debug.logImportant("==== TOTAL Declared Indices Created: " + totalDis, module);
}
// make sure each one-relation has an FK
if (checkFks) {
//if (!justColumns && datasourceInfo.useFks && datasourceInfo.checkForeignKeysOnStart) {
// NOTE: This ISN'T working for Postgres or MySQL, who knows about others, may be from JDBC driver bugs...
int numFksCreated = 0;
// TODO: check each key-map to make sure it exists in the FK, if any differences warn and then remove FK and recreate it
// get ALL column info, put into hashmap by table name
Map<String, Map<String, ReferenceCheckInfo>> refTableInfoMap = this.getReferenceInfo(fkTableNames, messages);
// Debug.logVerbose("Ref Info Map: " + refTableInfoMap, module);
if (refTableInfoMap == null) {
// uh oh, something happened while getting info...
if (Debug.verboseOn()) Debug.logVerbose("Ref Table Info Map is null", module);
} else {
for (ModelEntity entity: modelEntityList) {
String entityName = entity.getEntityName();
// if this is a view entity, do not check it...
if (entity instanceof ModelViewEntity) {
String entMessage = "NOT Checking View Entity " + entity.getEntityName();
Debug.logVerbose(entMessage, module);
if (messages != null) {
messages.add(entMessage);
}
continue;
}
// get existing FK map for this table
Map<String, ReferenceCheckInfo> rcInfoMap = refTableInfoMap.get(entity.getTableName(datasourceInfo));
// Debug.logVerbose("Got ref info for table " + entity.getTableName(datasourceInfo) + ": " + rcInfoMap, module);
// go through each relation to see if an FK already exists
Iterator<ModelRelation> relations = entity.getRelationsIterator();
boolean createdConstraints = false;
while (relations.hasNext()) {
ModelRelation modelRelation = relations.next();
if (!"one".equals(modelRelation.getType())) {
continue;
}
ModelEntity relModelEntity = modelEntities.get(modelRelation.getRelEntityName());
if (relModelEntity == null) {
Debug.logError("No such relation: " + entity.getEntityName() + " -> " + modelRelation.getRelEntityName(), module);
continue;
}
String relConstraintName = makeFkConstraintName(modelRelation, datasourceInfo.constraintNameClipLength);
ReferenceCheckInfo rcInfo = null;
if (rcInfoMap != null) {
rcInfo = rcInfoMap.get(relConstraintName);
}
if (rcInfo != null) {
rcInfoMap.remove(relConstraintName);
} else {
// if not, create one
String noFkMessage = "No Foreign Key Constraint [" + relConstraintName + "] found for entity [" + entityName + "]";
if (messages != null) messages.add(noFkMessage);
if (Debug.infoOn()) Debug.logInfo(noFkMessage, module);
if (addMissing) {
String errMsg = createForeignKey(entity, modelRelation, relModelEntity, datasourceInfo.constraintNameClipLength, datasourceInfo.fkStyle, datasourceInfo.useFkInitiallyDeferred);
if (errMsg != null && errMsg.length() > 0) {
String message = "Could not create foreign key " + relConstraintName + " for entity [" + entity.getEntityName() + "]: " + errMsg;
Debug.logError(message, module);
if (messages != null) messages.add(message);
} else {
String message = "Created foreign key " + relConstraintName + " for entity [" + entity.getEntityName() + "]";
Debug.logVerbose(message, module);
if (messages != null) messages.add(message);
createdConstraints = true;
numFksCreated++;
}
}
}
}
if (createdConstraints) {
String message = "Created foreign key(s) for entity [" + entity.getEntityName() + "]";
Debug.logImportant(message, module);
if (messages != null) messages.add(message);
}
// show foreign key references that exist but are unknown
if (rcInfoMap != null) {
for (String rcKeyLeft: rcInfoMap.keySet()) {
String message = "Unknown Foreign Key Constraint " + rcKeyLeft + " found in table " + entity.getTableName(datasourceInfo);
Debug.logImportant(message, module);
if (messages != null) messages.add(message);
}
}
}
}
if (Debug.infoOn()) Debug.logInfo("Created " + numFksCreated + " fk refs", module);
}
// make sure each one-relation has an index
if (checkFkIdx) {
//if (!justColumns && datasourceInfo.useFkIndices && datasourceInfo.checkFkIndicesOnStart) {
int numIndicesCreated = 0;
// TODO: check each key-map to make sure it exists in the index, if any differences warn and then remove the index and recreate it
// get ALL column info, put into hashmap by table name
Map<String, Set<String>> tableIndexListMap = this.getIndexInfo(indexTableNames, messages);
// Debug.logVerbose("Ref Info Map: " + refTableInfoMap, module);
if (tableIndexListMap == null) {
// uh oh, something happened while getting info...
if (Debug.verboseOn()) Debug.logVerbose("Ref Table Info Map is null", module);
} else {
for (ModelEntity entity: modelEntityList) {
String entityName = entity.getEntityName();
// if this is a view entity, do not check it...
if (entity instanceof ModelViewEntity) {
String entMessage = "NOT Checking View Entity " + entity.getEntityName();
Debug.logVerbose(entMessage, module);
if (messages != null) messages.add(entMessage);
continue;
}
// get existing index list for this table
Set<String> tableIndexList = tableIndexListMap.get(entity.getTableName(datasourceInfo));
// Debug.logVerbose("Got ind info for table " + entity.getTableName(datasourceInfo) + ": " + tableIndexList, module);
if (tableIndexList == null) {
// evidently no indexes in the database for this table, do the create all
this.createForeignKeyIndices(entity, datasourceInfo.constraintNameClipLength, messages);
} else {
// go through each relation to see if an FK already exists
boolean createdConstraints = false;
Iterator<ModelRelation> relations = entity.getRelationsIterator();
while (relations.hasNext()) {
ModelRelation modelRelation = relations.next();
if (!"one".equals(modelRelation.getType())) {
continue;
}
String relConstraintName = makeFkConstraintName(modelRelation, datasourceInfo.constraintNameClipLength);
if (tableIndexList.contains(relConstraintName)) {
tableIndexList.remove(relConstraintName);
} else {
// if not, create one
String noIdxMessage = "No Index [" + relConstraintName + "] found for entity [" + entityName + "]";
if (messages != null) messages.add(noIdxMessage);
if (Debug.infoOn()) Debug.logInfo(noIdxMessage, module);
if (addMissing) {
String errMsg = createForeignKeyIndex(entity, modelRelation, datasourceInfo.constraintNameClipLength);
if (errMsg != null && errMsg.length() > 0) {
String message = "Could not create foreign key index " + relConstraintName + " for entity [" + entity.getEntityName() + "]: " + errMsg;
Debug.logError(message, module);
if (messages != null) messages.add(message);
} else {
String message = "Created foreign key index " + relConstraintName + " for entity [" + entity.getEntityName() + "]";
Debug.logVerbose(message, module);
if (messages != null) messages.add(message);
createdConstraints = true;
numIndicesCreated++;
}
}
}
}
if (createdConstraints) {
String message = "Created foreign key index/indices for entity [" + entity.getEntityName() + "]";
Debug.logImportant(message, module);
if (messages != null) messages.add(message);
}
}
// show foreign key references that exist but are unknown
if (tableIndexList != null) {
for (String indexLeft: tableIndexList) {
String message = "Unknown Index " + indexLeft + " found in table " + entity.getTableName(datasourceInfo);
Debug.logImportant(message, module);
if (messages != null) messages.add(message);
}
}
}
}
if (Debug.infoOn()) Debug.logInfo("Created " + numIndicesCreated + " indices", module);
}
if (datasourceInfo.checkIndicesOnStart){
int numIndicesCreated = 0;
// TODO: check each key-map to make sure it exists in the index, if any differences warn and then remove the index and recreate it
// get ALL column info, put into hashmap by table name
Map<String, Set<String>> tableIndexListMap = this.getIndexInfo(indexTableNames, messages);
// Debug.logVerbose("Ref Info Map: " + refTableInfoMap, module);
if (tableIndexListMap == null) {
// uh oh, something happened while getting info...
if (Debug.verboseOn()) Debug.logVerbose("Ref Table Info Map is null", module);
} else {
for (ModelEntity entity: modelEntityList) {
String entityName = entity.getEntityName();
// if this is a view entity, do not check it...
if (entity instanceof ModelViewEntity) {
String entMessage = "NOT Checking View Entity " + entity.getEntityName();
Debug.logVerbose(entMessage, module);
if (messages != null) messages.add(entMessage);
continue;
}
// get existing index list for this table
Set<String> tableIndexList = tableIndexListMap.get(entity.getTableName(datasourceInfo));
// Debug.logVerbose("Got ind info for table " + entity.getTableName(datasourceInfo) + ": " + tableIndexList, module);
if (tableIndexList == null) {
// evidently no indexes in the database for this table, do the create all
this.createDeclaredIndices(entity, messages);
} else {
// go through each indice to see if an indice already exists
boolean createdIndexes = false;
Iterator<ModelIndex> indexes = entity.getIndexesIterator();
while (indexes.hasNext()) {
ModelIndex modelIndex = indexes.next();
String relIndexName = makeIndexName(modelIndex, datasourceInfo.constraintNameClipLength);
if (tableIndexList.contains(relIndexName)) {
tableIndexList.remove(relIndexName);
} else {
// if not, create one
String noIdxMessage = "No Index [" + relIndexName + "] found for entity [" + entityName + "]";
if (messages != null) messages.add(noIdxMessage);
if (Debug.infoOn()) Debug.logInfo(noIdxMessage, module);
if (addMissing) {
String errMsg = createDeclaredIndex(entity, modelIndex);
if (errMsg != null && errMsg.length() > 0) {
String message = "Could not create index " + relIndexName + " for entity [" + entity.getEntityName() + "]: " + errMsg;
Debug.logError(message, module);
if (messages != null) messages.add(message);
} else {
String message = "Created index " + relIndexName + " for entity [" + entity.getEntityName() + "]";
Debug.logVerbose(message, module);
if (messages != null) messages.add(message);
createdIndexes = true;
numIndicesCreated++;
}
}
}
}
if (createdIndexes) {
String message = "Created foreign key index/indices for entity [" + entity.getEntityName() + "]";
Debug.logImportant(message, module);
if (messages != null) messages.add(message);
}
}
// show Indexe key references that exist but are unknown
if (tableIndexList != null) {
for (String indexLeft: tableIndexList) {
String message = "Unknown Index " + indexLeft + " found in table " + entity.getTableName(datasourceInfo);
Debug.logImportant(message, module);
if (messages != null) messages.add(message);
}
}
}
}
if (Debug.infoOn()) Debug.logInfo("Created " + numIndicesCreated + " indices", module);
}
timer.timerString("Finished Checking Entity Database");
}