blob: 817c8bd1d52ead507af017ade3763a246c075701 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.empire.db.validation;
import java.sql.Connection;
import java.util.List;
import org.apache.empire.db.DBColumn;
import org.apache.empire.db.DBDatabase;
import org.apache.empire.db.DBRelation;
import org.apache.empire.db.DBRelation.DBReference;
import org.apache.empire.db.DBTable;
import org.apache.empire.db.DBTableColumn;
import org.apache.empire.db.DBView;
import org.apache.empire.exceptions.ObjectNotValidException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DBModelChecker
{
private static final Logger log = LoggerFactory.getLogger(DBModelChecker.class);
protected final DBModelParser modelParser;
protected DBDatabase remoteDb = null;
/**
* Creates a new Model Checker
* @param catalog
* @param schemaPattern
*/
public DBModelChecker(DBModelParser modelParser)
{
this.modelParser = modelParser;
}
/**
* Returns the RemoteDatabase
* Only available after parseModel() is called
* @return the remote Database
*/
public DBDatabase getRemoteDatabase()
{
return remoteDb;
}
/**
* Populates the remote database and compares it against the given database
* @param db the Database to be checked
* @param conn the connection for retrieving the remote database metadata
* @param handler the handler that is called to handle inconsistencies
*/
public void checkModel(DBDatabase db, Connection conn, DBModelErrorHandler handler)
{
// parse first
modelParser.parseModel(conn);
// set remote
this.remoteDb = modelParser.getDatabase();
// check database
checkRemoteAgainst(db, handler);
}
/**
* Check the remote database against an existing model
* @param db the database to check the remote against
* @param handler
*/
public void checkRemoteAgainst(DBDatabase db, DBModelErrorHandler handler)
{
if (this.remoteDb==null)
{ // parseModel has not been called
throw new ObjectNotValidException(this);
}
// check Tables
for (DBTable table : db.getTables())
{
checkTable(table, handler);
}
// check Views
for (DBView view : db.getViews())
{
checkView(view, handler);
}
}
protected void checkTable(DBTable table, DBModelErrorHandler handler)
{
DBTable remoteTable = remoteDb.getTable(table.getName()); // getTable(table.getName());
if (remoteTable == null)
{ // Check if it is a view instead
if (remoteDb.getView(table.getName())!=null)
handler.objectTypeMismatch(remoteTable, table.getName(), DBTable.class);
else
handler.itemNotFound(table);
return;
}
// Check primary Key
checkPrimaryKey(table, remoteTable, handler);
// check foreign keys
checkForeignKeys(table, remoteTable, handler);
// Check Columns
for (DBColumn column : table.getColumns())
{
DBColumn remoteColumn = remoteTable.getColumn(column.getName());
if (remoteColumn == null)
{
handler.itemNotFound(column);
continue;
}
checkColumn(column, remoteColumn, handler);
}
}
protected void checkView(DBView view, DBModelErrorHandler handler)
{
DBView remoteView = remoteDb.getView(view.getName());
if (remoteView == null)
{ // Check if it is a table instead
if (remoteDb.getTable(view.getName())!=null)
handler.objectTypeMismatch(remoteView, view.getName(), DBView.class);
else
handler.itemNotFound(view);
return;
}
for (DBColumn column : view.getColumns())
{
DBColumn remoteColumn = remoteView.getColumn(column.getName());
if (remoteColumn == null)
{
handler.itemNotFound(column);
continue;
}
// checkColumn(column, remoteColumn, handler);
checkColumnType(column, remoteColumn, handler);
}
}
protected void checkPrimaryKey(DBTable table, DBTable remoteTable, DBModelErrorHandler handler)
{
if (table.getPrimaryKey() == null)
{
// no primary key defined
return;
}
if (remoteTable.getPrimaryKey() == null)
{
// primary key missing in DB
handler.itemNotFound(table.getPrimaryKey());
return;
}
DBColumn[] pk = table.getPrimaryKey().getColumns();
DBColumn[] remotePk = remoteTable.getPrimaryKey().getColumns();
pkColLoop: for (DBColumn pkCol : pk)
{
for (DBColumn remotePkCol : remotePk)
{
if (pkCol.getFullName().equalsIgnoreCase(remotePkCol.getFullName()))
{
// found
continue pkColLoop;
}
}
// PK-Column not found
handler.primaryKeyColumnMissing(table.getPrimaryKey(), pkCol);
}
}
protected void checkForeignKeys(DBTable table, DBTable remoteTable, DBModelErrorHandler handler)
{
if (table.getForeignKeyRelations().isEmpty())
{
// no foreign keys defined
return;
}
List<DBRelation> relations = table.getForeignKeyRelations();
List<DBRelation> remoteRelations = remoteTable.getForeignKeyRelations();
for (DBRelation relation : relations)
{
referenceLoop: for (DBReference reference : relation.getReferences())
{
if (reference.getTargetColumn().getRowSet() instanceof DBTable)
{
DBTable targetTable = (DBTable) reference.getTargetColumn().getRowSet();
DBTableColumn targetColumn = reference.getTargetColumn();
if (!targetTable.getPrimaryKey().contains(targetColumn))
{
DBModelChecker.log.info("The column "
+ targetColumn.getName()
+ " of foreign key {} is not a primary key of table {} and cant be checked because of a limitation in JDBC",
relation.getName(), targetTable.getName());
continue;
}
}
for (DBRelation remoteRelation : remoteRelations)
{
for (DBReference remoteReference : remoteRelation.getReferences())
{
if (reference.getSourceColumn().getFullName().equalsIgnoreCase(remoteReference.getSourceColumn().getFullName())
&& reference.getTargetColumn().getFullName().equalsIgnoreCase(remoteReference.getTargetColumn().getFullName()))
{
// found
continue referenceLoop;
}
}
}
// Not found
handler.itemNotFound(relation);
break referenceLoop;
}
}
}
protected void checkColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
switch (column.getDataType())
{
case UNKNOWN:
checkUnknownColumn(column, remoteColumn, handler);
break;
case INTEGER:
checkIntegerColumn(column, remoteColumn, handler);
break;
case AUTOINC:
checkAutoIncColumn(column, remoteColumn, handler);
break;
case VARCHAR:
checkTextColumn(column, remoteColumn, handler);
break;
case DATE:
case DATETIME:
case TIMESTAMP:
checkDateColumn(column, remoteColumn, handler);
break;
case CHAR:
checkCharColumn(column, remoteColumn, handler);
break;
case FLOAT:
checkFloatColumn(column, remoteColumn, handler);
break;
case DECIMAL:
checkDecimalColumn(column, remoteColumn, handler);
break;
case BOOL:
checkBoolColumn(column, remoteColumn, handler);
break;
case CLOB:
checkClobColumn(column, remoteColumn, handler);
break;
case BLOB:
checkBlobColumn(column, remoteColumn, handler);
break;
case UNIQUEID:
checkUniqueIdColumn(column, remoteColumn, handler);
break;
default:
throw new RuntimeException("Invalid DataType " + column.getDataType());
}
}
private void checkGenericColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
checkColumnType(column, remoteColumn, handler);
checkColumnNullable(column, remoteColumn, handler);
checkColumnSize(column, remoteColumn, handler);
}
protected void checkColumnType(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
if (column.getDataType() != remoteColumn.getDataType())
{
handler.columnTypeMismatch(column, remoteColumn.getDataType());
}
}
protected void checkColumnNullable(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
if (column.isRequired() && !remoteColumn.isRequired())
{
handler.columnNullableMismatch(column, remoteColumn.isRequired());
}
}
protected void checkColumnSize(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
int colSize = (int) column.getSize();
if (colSize== 0)
{ // When size is 0, don't check
return;
}
if (colSize != (int) remoteColumn.getSize())
{
handler.columnSizeMismatch(column, (int) remoteColumn.getSize(), 0);
}
}
/** empire-db DataType-specific checker **/
protected void checkUnknownColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
checkGenericColumn(column, remoteColumn, handler);
}
protected void checkIntegerColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
checkGenericColumn(column, remoteColumn, handler);
}
protected void checkAutoIncColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
checkColumnSize(column, remoteColumn, handler);
checkColumnNullable(column, remoteColumn, handler);
}
protected void checkTextColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
checkGenericColumn(column, remoteColumn, handler);
}
protected void checkDateColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
// check nullable
checkColumnNullable(column, remoteColumn, handler);
// check type
if (!remoteColumn.getDataType().isDate())
{
handler.columnTypeMismatch(column, remoteColumn.getDataType());
}
}
protected void checkCharColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
checkGenericColumn(column, remoteColumn, handler);
}
protected void checkFloatColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
checkGenericColumn(column, remoteColumn, handler);
}
protected void checkDecimalColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
checkGenericColumn(column, remoteColumn, handler);
// check scale
if (column instanceof DBTableColumn && remoteColumn instanceof DBTableColumn)
{
DBTableColumn tableColumn = (DBTableColumn) column;
DBTableColumn tableRemoteColumn = (DBTableColumn) remoteColumn;
if (tableColumn.getDecimalScale() != tableRemoteColumn.getDecimalScale())
{
handler.columnSizeMismatch(column, (int) remoteColumn.getSize(), tableRemoteColumn.getDecimalScale());
}
}
}
protected void checkBoolColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
// Dont check size
checkColumnType(column, remoteColumn, handler);
checkColumnNullable(column, remoteColumn, handler);
}
protected void checkBlobColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
// Dont check size
checkColumnType(column, remoteColumn, handler);
checkColumnNullable(column, remoteColumn, handler);
}
protected void checkClobColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
// Dont check size
checkColumnType(column, remoteColumn, handler);
checkColumnNullable(column, remoteColumn, handler);
}
protected void checkUniqueIdColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
{
checkGenericColumn(column, remoteColumn, handler);
}
}