You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@empire-db.apache.org by do...@apache.org on 2015/04/30 18:07:19 UTC

empire-db git commit: EMPIREDB-217 submitted by Jan on April 13th

Repository: empire-db
Updated Branches:
  refs/heads/master 9fb58953f -> 8911cc5de


EMPIREDB-217
submitted by Jan on April 13th

Project: http://git-wip-us.apache.org/repos/asf/empire-db/repo
Commit: http://git-wip-us.apache.org/repos/asf/empire-db/commit/8911cc5d
Tree: http://git-wip-us.apache.org/repos/asf/empire-db/tree/8911cc5d
Diff: http://git-wip-us.apache.org/repos/asf/empire-db/diff/8911cc5d

Branch: refs/heads/master
Commit: 8911cc5deaafee958a881e29c2d651d263f31694
Parents: 9fb5895
Author: Rainer Döbele <do...@apache.org>
Authored: Thu Apr 30 18:07:15 2015 +0200
Committer: Rainer Döbele <do...@apache.org>
Committed: Thu Apr 30 18:07:15 2015 +0200

----------------------------------------------------------------------
 .gitignore                                      |   1 +
 empire-db-examples/.gitignore                   |   1 +
 .../empire/db/oracle/OracleDBModelChecker.java  |  72 +++
 .../empire/db/validation/DBModelChecker.java    | 625 +++++++++++++++++++
 .../db/validation/DBModelErrorHandler.java      |  64 ++
 .../db/validation/DBModelErrorLogger.java       |  76 +++
 6 files changed, 839 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/empire-db/blob/8911cc5d/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 0c2b833..988e912 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 /.settings/
 /.project
+/target/

http://git-wip-us.apache.org/repos/asf/empire-db/blob/8911cc5d/empire-db-examples/.gitignore
----------------------------------------------------------------------
diff --git a/empire-db-examples/.gitignore b/empire-db-examples/.gitignore
index 0c2b833..988e912 100644
--- a/empire-db-examples/.gitignore
+++ b/empire-db-examples/.gitignore
@@ -1,2 +1,3 @@
 /.settings/
 /.project
+/target/

http://git-wip-us.apache.org/repos/asf/empire-db/blob/8911cc5d/empire-db/src/main/java/org/apache/empire/db/oracle/OracleDBModelChecker.java
----------------------------------------------------------------------
diff --git a/empire-db/src/main/java/org/apache/empire/db/oracle/OracleDBModelChecker.java b/empire-db/src/main/java/org/apache/empire/db/oracle/OracleDBModelChecker.java
new file mode 100644
index 0000000..611af4a
--- /dev/null
+++ b/empire-db/src/main/java/org/apache/empire/db/oracle/OracleDBModelChecker.java
@@ -0,0 +1,72 @@
+package org.apache.empire.db.oracle;
+
+import java.sql.Connection;
+
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.DBColumn;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.DBDatabaseDriver;
+import org.apache.empire.db.oracle.DBDatabaseDriverOracle.BooleanType;
+import org.apache.empire.db.validation.DBModelChecker;
+import org.apache.empire.db.validation.DBModelErrorHandler;
+import org.apache.empire.exceptions.InvalidPropertyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This implementation is used to check Oracle Databases
+ */
+public class OracleDBModelChecker extends DBModelChecker
+{
+    private static final Logger log = LoggerFactory.getLogger(OracleDBModelChecker.class);
+    
+    private BooleanType booleanType = BooleanType.NUMBER; 
+
+    @Override
+    public void checkModel(DBDatabase db, Connection conn, String dbSchema, DBModelErrorHandler handler)
+    {
+        // Get boolean type from driver
+        DBDatabaseDriver driver = db.getDriver();
+        if (driver instanceof DBDatabaseDriverOracle)
+        {
+            booleanType = ((DBDatabaseDriverOracle)driver).getBooleanType();
+        }
+        else
+            log.warn("Provided driver is not of type DBDatabaseDriverOracle");
+
+        // check now
+        super.checkModel(db, conn, dbSchema, handler);
+        
+    }
+
+    @Override
+    protected void checkBoolColumn(DBColumn column, DBColumn remoteColumn, DBModelErrorHandler handler)
+    {
+        checkColumnNullable(column, remoteColumn, handler);
+
+        // check data type
+        DataType booleanDataType = null;
+        switch (booleanType)
+        {
+            case NUMBER:
+                booleanDataType = DataType.DECIMAL;
+                break;
+            case CHAR:
+                booleanDataType = DataType.CHAR;
+                break;
+            default:
+                throw new InvalidPropertyException("booleanType", booleanType);
+        }
+
+        if (remoteColumn.getDataType() != booleanDataType)
+        {
+            handler.columnTypeMismatch(column, booleanDataType);
+        }
+
+        // size should always be 1
+        if (remoteColumn.getSize() != 1)
+        {
+            handler.columnSizeMismatch(column, 1, 0);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/empire-db/blob/8911cc5d/empire-db/src/main/java/org/apache/empire/db/validation/DBModelChecker.java
----------------------------------------------------------------------
diff --git a/empire-db/src/main/java/org/apache/empire/db/validation/DBModelChecker.java b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelChecker.java
new file mode 100644
index 0000000..4e850dc
--- /dev/null
+++ b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelChecker.java
@@ -0,0 +1,625 @@
+package org.apache.empire.db.validation;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.empire.data.DataType;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DBModelChecker
+{
+    private static final Logger        log      = LoggerFactory.getLogger(DBModelChecker.class);
+
+    private final Map<String, DBTable> tableMap = new HashMap<String, DBTable>();
+    private final DBDatabase           remoteDb = new InMemoryDatabase();
+
+    public static class InMemoryDatabase extends DBDatabase
+    {
+        private static final long serialVersionUID = 1L;
+    }
+
+    /**
+     * This method is used to check the database model
+     * 
+     * @param db
+     *            The Empire-db definition to be checked
+     * @param conn
+     *            A connection to the database
+     * @param dbSchema
+     *            The database schema
+     * @param handler
+     *            The {@link DBModelErrorHandler} implementation that is called whenever an error
+     *            occurs
+     */
+    public void checkModel(DBDatabase db, Connection conn, String dbSchema, DBModelErrorHandler handler)
+    {
+        try
+        {
+            DatabaseMetaData dbMeta = conn.getMetaData();
+
+            // collect tables & views
+            collectTables(dbMeta, dbSchema);
+
+            // Collect all columns
+            collectColumns(dbMeta, dbSchema);
+
+            // Collect PKs
+            collectPrimaryKeys(dbMeta, dbSchema);
+
+            // Collect FKs
+            collectForeignKeys(dbMeta, dbSchema);
+
+            // check Tables
+            for (DBTable table : db.getTables())
+            {
+                checkTable(table, handler);
+            }
+
+            // check Views
+            for (DBView view : db.getViews())
+            {
+                checkView(view, conn, handler);
+            }
+        }
+        catch (SQLException e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    private void collectTables(DatabaseMetaData dbMeta, String dbSchema)
+        throws SQLException
+    {
+        ResultSet dbTables = dbMeta.getTables(null, dbSchema, null, new String[] { "TABLE", "VIEW" });
+        while (dbTables.next())
+        {
+            String name = dbTables.getString("TABLE_NAME");
+            // Ignore system tables containing a '$' symbol (required for Oracle!)
+            if (name.indexOf('$') >= 0)
+            {
+                DBModelChecker.log.info("Ignoring system table " + name);
+                continue;
+            }
+            this.tableMap.put(name.toUpperCase(), new DBTable(name, this.remoteDb));
+        }
+    }
+
+    private void collectColumns(DatabaseMetaData dbMeta, String dbSchema)
+        throws SQLException
+    {
+        ResultSet dbColumns = dbMeta.getColumns(null, dbSchema, null, null);
+        while (dbColumns.next())
+        {
+            String tableName = dbColumns.getString("TABLE_NAME");
+            DBTable t = this.tableMap.get(tableName.toUpperCase());
+            if (t == null)
+            {
+                DBModelChecker.log.error("Table not found: {}", tableName);
+                continue;
+            }
+            addColumn(t, dbColumns);
+        }
+    }
+
+    private void collectPrimaryKeys(DatabaseMetaData dbMeta, String dbSchema)
+        throws SQLException
+    {
+        for (String t : this.tableMap.keySet())
+        {
+            List<String> pkCols = new ArrayList<String>();
+            ResultSet primaryKeys = dbMeta.getPrimaryKeys(null, dbSchema, t);
+            while (primaryKeys.next())
+            {
+                pkCols.add(primaryKeys.getString("COLUMN_NAME"));
+            }
+            if (pkCols.size() > 0)
+            {
+                DBTable table = this.tableMap.get(t.toUpperCase());
+                DBColumn[] keys = new DBColumn[pkCols.size()];
+                for (int i = 0; i < keys.length; i++)
+                {
+                    keys[i] = table.getColumn(pkCols.get(i).toUpperCase());
+                }
+                table.setPrimaryKey(keys);
+            }
+        }
+    }
+
+    // Findet nur Foreign Keys die auf eine Primary Key Spalte gehen
+    private void collectForeignKeys(DatabaseMetaData dbMeta, String dbSchema)
+        throws SQLException
+    {
+        ResultSet foreignKeys = dbMeta.getImportedKeys(null, dbSchema, null);
+        while (foreignKeys.next())
+        {
+            String fkTable = foreignKeys.getString("FKTABLE_NAME");
+            String fkColumn = foreignKeys.getString("FKCOLUMN_NAME");
+
+            String pkTable = foreignKeys.getString("PKTABLE_NAME");
+            String pkColumn = foreignKeys.getString("PKCOLUMN_NAME");
+
+            String fkName = foreignKeys.getString("FK_NAME");
+
+            DBTableColumn c1 = (DBTableColumn) this.remoteDb.getTable(fkTable.toUpperCase()).getColumn(fkColumn.toUpperCase());
+            DBTableColumn c2 = (DBTableColumn) this.remoteDb.getTable(pkTable.toUpperCase()).getColumn(pkColumn.toUpperCase());
+
+            DBRelation relation = this.remoteDb.getRelation(fkName);
+            if (relation == null)
+            {
+                this.remoteDb.addRelation(fkName, c1.referenceOn(c2));
+            }
+            else
+            {
+                // get existing references
+                DBReference[] refs = relation.getReferences();
+                // remove old
+                this.remoteDb.getRelations().remove(relation);
+                DBReference[] newRefs = new DBReference[refs.length + 1];
+                // copy existing
+                DBReference newRef = new DBReference(c1, c2);
+                for (int i = 0; i < refs.length; i++)
+                {
+                    newRefs[i] = refs[i];
+                }
+                newRefs[newRefs.length - 1] = newRef;
+                this.remoteDb.addRelation(fkName, newRefs);
+            }
+        }
+    }
+
+    private void checkTable(DBTable table, DBModelErrorHandler handler)
+    {
+        DBTable remoteTable = this.tableMap.get(table.getName().toUpperCase());
+
+        if (remoteTable == null)
+        {
+            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);
+        }
+    }
+
+    private 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);
+        }
+    }
+
+    private 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);
+            }
+
+        }
+
+    }
+
+    private void checkView(DBView view, Connection conn, DBModelErrorHandler handler)
+    {
+        DBTable remoteView = this.tableMap.get(view.getName().toUpperCase());
+
+        if (remoteView == null)
+        {
+            handler.itemNotFound(remoteView);
+            return;
+        }
+
+        for (DBColumn column : view.getColumns())
+        {
+            DBColumn remoteColumn = remoteView.getColumn(column.getName());
+            if (remoteColumn == null)
+            {
+                handler.itemNotFound(column);
+                continue;
+            }
+            checkColumn(column, remoteColumn, handler);
+        }
+    }
+
+    private 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 TEXT:
+                checkTextColumn(column, remoteColumn, handler);
+                break;
+            case DATE:
+            case DATETIME:
+                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)
+    {
+        if (((int) column.getSize() != (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() == DataType.DATE || remoteColumn.getDataType() == DataType.DATETIME))
+        {
+            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);
+    }
+
+    /** taken from CodeGenParser **/
+    private DBTableColumn addColumn(DBTable t, ResultSet rs)
+        throws SQLException
+    {
+        String name = rs.getString("COLUMN_NAME");
+        DataType empireType = getEmpireDataType(rs.getInt("DATA_TYPE"));
+
+        double colSize = rs.getInt("COLUMN_SIZE");
+        if (empireType == DataType.DECIMAL || empireType == DataType.FLOAT)
+        { // decimal digits
+            int decimalDig = rs.getInt("DECIMAL_DIGITS");
+            if (decimalDig > 0)
+            { // parse
+                try
+                {
+                    int intSize = rs.getInt("COLUMN_SIZE");
+                    colSize = Double.parseDouble(String.valueOf(intSize) + '.' + decimalDig);
+                }
+                catch (Exception e)
+                {
+                    DBModelChecker.log.error("Failed to parse decimal digits for column " + name);
+                }
+            }
+            // make integer?
+            if (colSize < 1.0d)
+            { // Turn into an integer
+                empireType = DataType.INTEGER;
+            }
+        }
+
+        // mandatory field?
+        boolean required = false;
+        String defaultValue = rs.getString("COLUMN_DEF");
+        if (rs.getString("IS_NULLABLE").equalsIgnoreCase("NO"))
+        {
+            required = true;
+        }
+
+        // The following is a hack for MySQL which currently gets sent a string "CURRENT_TIMESTAMP" from the Empire-db driver for MySQL.
+        // This will avoid the driver problem because CURRENT_TIMESTAMP in the db will just do the current datetime.
+        // Essentially, Empire-db needs the concept of default values of one type that get mapped to another.
+        // In this case, MySQL "CURRENT_TIMESTAMP" for Types.TIMESTAMP needs to emit from the Empire-db driver the null value and not "CURRENT_TIMESTAMP".
+        if (rs.getInt("DATA_TYPE") == Types.TIMESTAMP && defaultValue != null && defaultValue.equals("CURRENT_TIMESTAMP"))
+        {
+            required = false; // It is in fact not required even though MySQL schema is required because it has a default value. Generally, should Empire-db emit (required && defaultValue != null) to truly determine if a column is required?
+            defaultValue = null; // If null (and required per schema?) MySQL will apply internal default value.
+        }
+
+        // AUTOINC indicator is not in java.sql.Types but rather meta data from DatabaseMetaData.getColumns()
+        // getEmpireDataType() above is not enough to support AUTOINC as it will only return DataType.INTEGER
+        DataType originalType = empireType;
+        ResultSetMetaData metaData = rs.getMetaData();
+        int colCount = metaData.getColumnCount();
+        String colName;
+        for (int i = 1; i <= colCount; i++)
+        {
+            colName = metaData.getColumnName(i);
+            // MySQL matches on IS_AUTOINCREMENT column.
+            // SQL Server matches on TYPE_NAME column with identity somewhere in the string value.
+            if ((colName.equalsIgnoreCase("IS_AUTOINCREMENT") && rs.getString(i).equalsIgnoreCase("YES"))
+                || (colName.equals("TYPE_NAME") && rs.getString(i).matches(".*(?i:identity).*")))
+            {
+                empireType = DataType.AUTOINC;
+
+            }
+        }
+
+        // Move from the return statement below so we can add
+        // some AUTOINC meta data to the column to be used by
+        // the ParserUtil and ultimately the template.
+        //        DBModelChecker.log.info("\tCOLUMN:\t" + name + " (" + empireType + ")");
+        DBTableColumn col = t.addColumn(name, empireType, colSize, required, defaultValue);
+
+        // We still need to know the base data type for this AUTOINC
+        // because the Record g/setters need to know this, right?
+        // So, let's add it as meta data every time the column is AUTOINC
+        // and reference it in the template.
+        if (empireType.equals(DataType.AUTOINC))
+        {
+            col.setAttribute("AutoIncDataType", originalType);
+        }
+        return col;
+
+    }
+
+    private DataType getEmpireDataType(int sqlType)
+    {
+        DataType empireType = DataType.UNKNOWN;
+        switch (sqlType)
+        {
+            case Types.INTEGER:
+            case Types.SMALLINT:
+            case Types.TINYINT:
+            case Types.BIGINT:
+                empireType = DataType.INTEGER;
+                break;
+            case Types.VARCHAR:
+                empireType = DataType.TEXT;
+                break;
+            case Types.DATE:
+                empireType = DataType.DATE;
+                break;
+            case Types.TIMESTAMP:
+            case Types.TIME:
+                empireType = DataType.DATETIME;
+                break;
+            case Types.CHAR:
+                empireType = DataType.CHAR;
+                break;
+            case Types.DOUBLE:
+            case Types.FLOAT:
+            case Types.REAL:
+                empireType = DataType.FLOAT;
+                break;
+            case Types.DECIMAL:
+            case Types.NUMERIC:
+                empireType = DataType.DECIMAL;
+                break;
+            case Types.BIT:
+            case Types.BOOLEAN:
+                empireType = DataType.BOOL;
+                break;
+            case Types.CLOB:
+            case Types.LONGVARCHAR:
+                empireType = DataType.CLOB;
+                break;
+            case Types.BINARY:
+            case Types.VARBINARY:
+            case Types.LONGVARBINARY:
+            case Types.BLOB:
+                empireType = DataType.BLOB;
+                break;
+            default:
+                empireType = DataType.UNKNOWN;
+                DBModelChecker.log.warn("SQL column type " + sqlType + " not supported.");
+        }
+        DBModelChecker.log.debug("Mapping date type " + String.valueOf(sqlType) + " to " + empireType);
+        return empireType;
+    }
+}

http://git-wip-us.apache.org/repos/asf/empire-db/blob/8911cc5d/empire-db/src/main/java/org/apache/empire/db/validation/DBModelErrorHandler.java
----------------------------------------------------------------------
diff --git a/empire-db/src/main/java/org/apache/empire/db/validation/DBModelErrorHandler.java b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelErrorHandler.java
new file mode 100644
index 0000000..41da57d
--- /dev/null
+++ b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelErrorHandler.java
@@ -0,0 +1,64 @@
+package org.apache.empire.db.validation;
+
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.DBColumn;
+import org.apache.empire.db.DBIndex;
+import org.apache.empire.db.DBObject;
+
+public interface DBModelErrorHandler
+{
+
+    /**
+     * This method is called when an object (e. g. table or column) is missing in
+     * the database.
+     * 
+     * @param dbo
+     *            The missing object
+     */
+    void itemNotFound(DBObject dbo);
+
+    /**
+     * This method is called when a column in a primary key of the Empire-db definition
+     * is missing in the database
+     * 
+     * @param primaryKey
+     *            The primary key that misses the column
+     * @param column
+     *            The missing column
+     */
+    void primaryKeyColumnMissing(DBIndex primaryKey, DBColumn column);
+
+    /**
+     * This method is called when the type of a column in the Empire-db
+     * definition does not match the database.
+     * 
+     * @param col
+     *            The affected column
+     * @param type
+     */
+    void columnTypeMismatch(DBColumn col, DataType type);
+
+    /**
+     * This method is called when the size of a column in the Empire-db
+     * definition does not match the database.
+     * 
+     * @param col
+     *            The affected column
+     * @param size
+     *            Size in the database
+     * @param scale
+     *            Decimal scale in the database (only for decimal types, 0 otherwise)
+     */
+    void columnSizeMismatch(DBColumn col, int size, int scale);
+
+    /**
+     * This method is called when a NOT NULL constraints of a column in
+     * the Empire-db definition does not match the database.
+     * 
+     * @param col
+     *            The affected column
+     * @param nullable
+     *            true if the column is required in the database
+     */
+    void columnNullableMismatch(DBColumn col, boolean nullable);
+}

http://git-wip-us.apache.org/repos/asf/empire-db/blob/8911cc5d/empire-db/src/main/java/org/apache/empire/db/validation/DBModelErrorLogger.java
----------------------------------------------------------------------
diff --git a/empire-db/src/main/java/org/apache/empire/db/validation/DBModelErrorLogger.java b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelErrorLogger.java
new file mode 100644
index 0000000..690122c
--- /dev/null
+++ b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelErrorLogger.java
@@ -0,0 +1,76 @@
+package org.apache.empire.db.validation;
+
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.DBColumn;
+import org.apache.empire.db.DBIndex;
+import org.apache.empire.db.DBObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An implemtnation of the {@link DBModelErrorHandler} interface that logs all errors
+ */
+public class DBModelErrorLogger implements DBModelErrorHandler
+{
+    private static final Logger log = LoggerFactory.getLogger(DBModelErrorLogger.class);
+
+    /**
+     * handle itemNotFound errors
+     */
+    public void itemNotFound(DBObject dbo)
+    {
+        if (dbo instanceof DBIndex)
+        {
+            DBIndex dbi = (DBIndex) dbo;
+            DBModelErrorLogger.log.error("The primary key " + dbi.getName() + " for table " + dbi.getTable().getName()
+                                         + " does not exist in the target database.");
+        }
+        else
+        {
+            DBModelErrorLogger.log.error("The object " + dbo.toString() + " does not exist in the target database.");
+        }
+    }
+
+    /**
+     * handle columnTypeMismatch errors
+     */
+    public void columnTypeMismatch(DBColumn col, DataType type)
+    {
+        DBModelErrorLogger.log.error("The column " + col.getFullName() + " type of " + col.getDataType().toString()
+                                     + " does not match the database type of " + type.toString());
+    }
+
+    /**
+     * handle columnSizeMismatch errors
+     */
+    public void columnSizeMismatch(DBColumn col, int size, int scale)
+    {
+        DBModelErrorLogger.log.error("The column " + col.getFullName() + " size of " + String.valueOf(col.getSize())
+                                     + " does not match the size database size of " + String.valueOf(size));
+    }
+
+    /**
+     * handle columnNullableMismatch errors
+     */
+    public void columnNullableMismatch(DBColumn col, boolean nullable)
+    {
+        if (nullable)
+        {
+            DBModelErrorLogger.log.error("The column " + col.getFullName() + " must not be nullable");
+        }
+        else
+        {
+            DBModelErrorLogger.log.error("The column " + col.getFullName() + " must be nullable");
+        }
+    }
+
+    /**
+     * handle primaryKeyColumnMissing errors
+     */
+    public void primaryKeyColumnMissing(DBIndex primaryKey, DBColumn column)
+    {
+        DBModelErrorLogger.log.error("The primary key " + primaryKey.getName() + " of table " + primaryKey.getTable().getName()
+                                     + " misses the column " + column.getName());
+    }
+
+}