You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by ka...@apache.org on 2013/07/22 10:10:21 UTC

[08/64] [partial] Hard rename of all 'org/eobjects' folders to 'org/apache'.

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataContext.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataContext.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataContext.java
new file mode 100644
index 0000000..ddac728
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataContext.java
@@ -0,0 +1,794 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import javax.sql.DataSource;
+
+import org.eobjects.metamodel.AbstractDataContext;
+import org.eobjects.metamodel.BatchUpdateScript;
+import org.eobjects.metamodel.MetaModelException;
+import org.eobjects.metamodel.UpdateScript;
+import org.eobjects.metamodel.UpdateableDataContext;
+import org.eobjects.metamodel.data.DataSet;
+import org.eobjects.metamodel.data.EmptyDataSet;
+import org.eobjects.metamodel.data.MaxRowsDataSet;
+import org.eobjects.metamodel.jdbc.dialects.DB2QueryRewriter;
+import org.eobjects.metamodel.jdbc.dialects.DefaultQueryRewriter;
+import org.eobjects.metamodel.jdbc.dialects.H2QueryRewriter;
+import org.eobjects.metamodel.jdbc.dialects.HsqldbQueryRewriter;
+import org.eobjects.metamodel.jdbc.dialects.IQueryRewriter;
+import org.eobjects.metamodel.jdbc.dialects.MysqlQueryRewriter;
+import org.eobjects.metamodel.jdbc.dialects.PostgresqlQueryRewriter;
+import org.eobjects.metamodel.jdbc.dialects.SQLServerQueryRewriter;
+import org.eobjects.metamodel.query.CompiledQuery;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.schema.Schema;
+import org.eobjects.metamodel.schema.TableType;
+import org.eobjects.metamodel.util.FileHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DataContextStrategy to use for JDBC-compliant databases
+ */
+public class JdbcDataContext extends AbstractDataContext implements UpdateableDataContext {
+
+    public static final String SYSTEM_PROPERTY_BATCH_UPDATES = "metamodel.jdbc.batch.updates";
+    public static final String SYSTEM_PROPERTY_CONVERT_LOBS = "metamodel.jdbc.convert.lobs";
+
+    public static final String DATABASE_PRODUCT_POSTGRESQL = "PostgreSQL";
+    public static final String DATABASE_PRODUCT_MYSQL = "MySQL";
+    public static final String DATABASE_PRODUCT_HSQLDB = "HSQL Database Engine";
+    public static final String DATABASE_PRODUCT_H2 = "H2";
+    public static final String DATABASE_PRODUCT_SQLSERVER = "Microsoft SQL Server";
+    public static final String DATABASE_PRODUCT_DB2 = "DB2";
+    public static final String DATABASE_PRODUCT_DB2_PREFIX = "DB2/";
+
+    private static final Logger logger = LoggerFactory.getLogger(JdbcDataContext.class);
+
+    private final FetchSizeCalculator _fetchSizeCalculator;
+    private final Connection _connection;
+    private final DataSource _dataSource;
+    private final TableType[] _tableTypes;
+    private final String _catalogName;
+    private final boolean _singleConnection;
+
+    private final MetadataLoader _metadataLoader;
+
+    /**
+     * Defines the way that queries are written once dispatched to the database
+     */
+    private IQueryRewriter _queryRewriter;
+    private final String _databaseProductName;
+
+    /**
+     * There are some confusion as to the definition of catalogs and schemas.
+     * Some databases seperate "groups of tables" by using schemas, others by
+     * catalogs. This variable indicates whether a MetaModel schema really
+     * represents a catalog.
+     */
+    private final String _identifierQuoteString;
+    private final boolean _supportsBatchUpdates;
+    private final boolean _isDefaultAutoCommit;
+    private final boolean _usesCatalogsAsSchemas;
+
+    /**
+     * Creates the strategy based on a data source, some table types and an
+     * optional catalogName
+     * 
+     * @param dataSource
+     *            the datasource objcet to use for making connections
+     * @param tableTypes
+     *            the types of tables to include
+     * @param catalogName
+     *            a catalog name to use, can be null
+     */
+    public JdbcDataContext(DataSource dataSource, TableType[] tableTypes, String catalogName) {
+        this(dataSource, null, tableTypes, catalogName);
+    }
+
+    /**
+     * Creates the strategy based on a {@link Connection}, some table types and
+     * an optional catalogName
+     * 
+     * @param connection
+     *            the database connection
+     * @param tableTypes
+     *            the types of tables to include
+     * @param catalogName
+     *            a catalog name to use, can be null
+     */
+    public JdbcDataContext(Connection connection, TableType[] tableTypes, String catalogName) {
+        this(null, connection, tableTypes, catalogName);
+    }
+
+    /**
+     * Creates the strategy based on a {@link DataSource}, some table types and
+     * an optional catalogName
+     * 
+     * @param dataSource
+     *            the data source
+     * @param tableTypes
+     *            the types of tables to include
+     * @param catalogName
+     *            a catalog name to use, can be null
+     */
+    private JdbcDataContext(DataSource dataSource, Connection connection, TableType[] tableTypes, String catalogName) {
+        _dataSource = dataSource;
+        _connection = connection;
+        _tableTypes = tableTypes;
+        _catalogName = catalogName;
+
+        if (_dataSource == null) {
+            _singleConnection = true;
+        } else {
+            _singleConnection = false;
+        }
+
+        // available memory for fetching is so far fixed at 16 megs.
+        _fetchSizeCalculator = new FetchSizeCalculator(16 * 1024 * 1024);
+
+        boolean supportsBatchUpdates = false;
+        String identifierQuoteString = null;
+        String databaseProductName = null;
+        boolean usesCatalogsAsSchemas = false;
+
+        final Connection con = getConnection();
+
+        try {
+            _isDefaultAutoCommit = con.getAutoCommit();
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "determine auto-commit behaviour");
+        }
+
+        try {
+            DatabaseMetaData metaData = con.getMetaData();
+
+            supportsBatchUpdates = supportsBatchUpdates(metaData);
+
+            try {
+                identifierQuoteString = metaData.getIdentifierQuoteString();
+                if (identifierQuoteString != null) {
+                    identifierQuoteString = identifierQuoteString.trim();
+                }
+            } catch (SQLException e) {
+                logger.warn("could not retrieve identifier quote string from database metadata", e);
+            }
+
+            usesCatalogsAsSchemas = usesCatalogsAsSchemas(metaData);
+            try {
+                databaseProductName = metaData.getDatabaseProductName();
+            } catch (SQLException e) {
+                logger.warn("Could not retrieve database product name: " + e.getMessage());
+            }
+        } catch (SQLException e) {
+            logger.debug("Unexpected exception during JdbcDataContext initialization", e);
+        } finally {
+            closeIfNescesary(con);
+        }
+        _databaseProductName = databaseProductName;
+        logger.debug("Database product name: {}", _databaseProductName);
+        if (DATABASE_PRODUCT_MYSQL.equals(_databaseProductName)) {
+            setQueryRewriter(new MysqlQueryRewriter(this));
+        } else if (DATABASE_PRODUCT_POSTGRESQL.equals(_databaseProductName)) {
+            setQueryRewriter(new PostgresqlQueryRewriter(this));
+        } else if (DATABASE_PRODUCT_SQLSERVER.equals(_databaseProductName)) {
+            setQueryRewriter(new SQLServerQueryRewriter(this));
+        } else if (DATABASE_PRODUCT_DB2.equals(_databaseProductName)
+                || (_databaseProductName != null && _databaseProductName.startsWith(DATABASE_PRODUCT_DB2_PREFIX))) {
+            setQueryRewriter(new DB2QueryRewriter(this));
+        } else if (DATABASE_PRODUCT_HSQLDB.equals(_databaseProductName)) {
+            setQueryRewriter(new HsqldbQueryRewriter(this));
+        } else if (DATABASE_PRODUCT_H2.equals(_databaseProductName)) {
+            setQueryRewriter(new H2QueryRewriter(this));
+        } else {
+            setQueryRewriter(new DefaultQueryRewriter(this));
+        }
+
+        _supportsBatchUpdates = supportsBatchUpdates;
+        _identifierQuoteString = identifierQuoteString;
+        _usesCatalogsAsSchemas = usesCatalogsAsSchemas;
+        _metadataLoader = new JdbcMetadataLoader(this, _usesCatalogsAsSchemas, _identifierQuoteString);
+    }
+
+    /**
+     * Creates the strategy based on a {@link Connection}
+     * 
+     * @param connection
+     *            the database connection
+     */
+    public JdbcDataContext(Connection connection) {
+        this(connection, TableType.DEFAULT_TABLE_TYPES, null);
+    }
+
+    /**
+     * Creates the strategy based on a {@link DataSource}
+     * 
+     * @param dataSource
+     *            the data source
+     */
+    public JdbcDataContext(DataSource dataSource) {
+        this(dataSource, TableType.DEFAULT_TABLE_TYPES, null);
+    }
+
+    private boolean supportsBatchUpdates(DatabaseMetaData metaData) {
+        if ("true".equals(System.getProperty(SYSTEM_PROPERTY_BATCH_UPDATES))) {
+            return true;
+        }
+        if ("false".equals(System.getProperty(SYSTEM_PROPERTY_BATCH_UPDATES))) {
+            return false;
+        }
+
+        try {
+            return metaData.supportsBatchUpdates();
+        } catch (Exception e) {
+            logger.warn("Could not determine if driver support batch updates, returning false", e);
+            return false;
+        }
+    }
+
+    private boolean usesCatalogsAsSchemas(DatabaseMetaData metaData) {
+        boolean result = true;
+        ResultSet rs = null;
+        try {
+            rs = metaData.getSchemas();
+            while (rs.next() && result) {
+                result = false;
+            }
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "retrieve schema and catalog metadata");
+        } finally {
+            close(null, rs, null);
+        }
+        return result;
+    }
+
+    @Override
+    public CompiledQuery compileQuery(Query query) {
+        return new JdbcCompiledQuery(this, query);
+    }
+
+    @Override
+    public DataSet executeQuery(CompiledQuery compiledQuery, Object... values) {
+
+        final JdbcCompiledQuery jdbcCompiledQuery = (JdbcCompiledQuery) compiledQuery;
+
+        final Query query = jdbcCompiledQuery.getQuery();
+        final int countMatches = jdbcCompiledQuery.getParameters().size();
+
+        final int valueArrayLength = values.length;
+
+        if (countMatches != valueArrayLength) {
+            throw new MetaModelException("Number of parameters in query and number of values does not match.");
+        }
+
+        final JdbcCompiledQueryLease lease = jdbcCompiledQuery.borrowLease();
+        final DataSet dataSet;
+        try {
+            dataSet = execute(lease.getConnection(), query, lease.getStatement(), jdbcCompiledQuery, lease, values);
+        } catch (SQLException e) {
+            // only close in case of an error - the JdbcDataSet will close
+            // otherwise
+            jdbcCompiledQuery.returnLease(lease);
+            throw JdbcUtils.wrapException(e, "execute compiled query");
+        }
+
+        return dataSet;
+    }
+
+    private DataSet execute(Connection connection, Query query, Statement statement, JdbcCompiledQuery compiledQuery,
+            JdbcCompiledQueryLease lease, Object[] values) throws SQLException {
+        if (_databaseProductName.equals(DATABASE_PRODUCT_POSTGRESQL)) {
+
+            try {
+                // this has to be done in order to make a result set not load
+                // all data in memory only for Postgres.
+                connection.setAutoCommit(false);
+            } catch (Exception e) {
+                logger.warn("Could not disable auto-commit (PostgreSQL specific hack)", e);
+            }
+        }
+
+        ResultSet resultSet = null;
+
+        boolean postProcessFirstRow = false;
+        final Integer firstRow = query.getFirstRow();
+        if (firstRow != null) {
+            if (_queryRewriter.isFirstRowSupported()) {
+                logger.debug("First row property will be treated by query rewriter");
+            } else {
+                postProcessFirstRow = true;
+            }
+        }
+
+        boolean postProcessMaxRows = false;
+        Integer maxRows = query.getMaxRows();
+        if (maxRows != null) {
+            if (postProcessFirstRow) {
+                // if First row is being post processed, we need to
+                // increment the "Max rows" accordingly (but subtract one, since
+                // firstRow is 1-based).
+                maxRows = maxRows + (firstRow - 1);
+                query = query.clone().setMaxRows(maxRows);
+
+                logger.debug("Setting Max rows to {} because of post processing strategy of First row.", maxRows);
+            }
+
+            if (_queryRewriter.isMaxRowsSupported()) {
+                logger.debug("Max rows property will be treated by query rewriter");
+            } else {
+                try {
+                    statement.setMaxRows(maxRows);
+                } catch (SQLException e) {
+                    if (logger.isInfoEnabled()) {
+                        logger.info("setMaxRows(" + maxRows + ") was rejected.", e);
+                    }
+                    postProcessMaxRows = true;
+                }
+            }
+        }
+
+        DataSet dataSet = null;
+        try {
+            final int fetchSize = getFetchSize(query, statement);
+
+            logger.debug("Applying fetch_size={}", fetchSize);
+
+            try {
+                statement.setFetchSize(fetchSize);
+            } catch (Exception e) {
+                // Ticket #372: Sometimes an exception is thrown here even
+                // though it's contrary to the jdbc spec. We'll proceed without
+                // doing anything about it though.
+                logger.info("Could not get or set fetch size on Statement: {}", e.getMessage());
+            }
+
+            if (lease == null) {
+                final String queryString = _queryRewriter.rewriteQuery(query);
+
+                logger.debug("Executing rewritten query: {}", queryString);
+
+                resultSet = statement.executeQuery(queryString);
+            } else {
+                PreparedStatement preparedStatement = (PreparedStatement) statement;
+                for (int i = 0; i < values.length; i++) {
+                    preparedStatement.setObject(i + 1, values[i]);
+                }
+                resultSet = preparedStatement.executeQuery();
+            }
+            try {
+                resultSet.setFetchSize(fetchSize);
+            } catch (Exception e) {
+                logger.warn("Could not set fetch size on ResultSet: {}", e.getMessage());
+            }
+
+            if (postProcessFirstRow) {
+                // we iterate to the "first row" using the resultset itself.
+                for (int i = 1; i < firstRow; i++) {
+                    if (!resultSet.next()) {
+                        // the result set was not as long as the first row
+                        if (resultSet != null) {
+                            resultSet.close();
+                        }
+                        return new EmptyDataSet(query.getSelectClause().getItems());
+                    }
+                }
+            }
+
+            if (lease == null) {
+                dataSet = new JdbcDataSet(query, this, connection, statement, resultSet);
+            } else {
+                dataSet = new JdbcDataSet(compiledQuery, lease, resultSet);
+            }
+
+            if (postProcessMaxRows) {
+                dataSet = new MaxRowsDataSet(dataSet, maxRows);
+            }
+        } catch (SQLException exception) {
+            if (resultSet != null) {
+                resultSet.close();
+            }
+            throw exception;
+        }
+        return dataSet;
+    }
+
+    public DataSet executeQuery(Query query) throws MetaModelException {
+
+        final Connection connection = getConnection();
+        final Statement statement;
+        try {
+            statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "create statement for query");
+        }
+        DataSet dataSet = null;
+        try {
+
+            dataSet = execute(connection, query, statement, null, null, null);
+
+        } catch (SQLException e) {
+            // only close in case of an error - the JdbcDataSet will close
+            // otherwise
+            close(connection, null, statement);
+            throw JdbcUtils.wrapException(e, "execute query");
+        }
+
+        return dataSet;
+    }
+
+    private int getFetchSize(Query query, final Statement statement) {
+        try {
+            final int defaultFetchSize = statement.getFetchSize();
+            if (DATABASE_PRODUCT_MYSQL.equals(_databaseProductName) && defaultFetchSize == Integer.MIN_VALUE) {
+                return defaultFetchSize;
+            }
+        } catch (Exception e) {
+            // exceptions here are ignored.
+            logger.debug("Ignoring exception while getting fetch size", e);
+        }
+        return _fetchSizeCalculator.getFetchSize(query);
+    }
+
+    /**
+     * Quietly closes any of the parameterized JDBC objects
+     * 
+     * @param connection
+     * 
+     * @param rs
+     * @param st
+     */
+    public void close(Connection connection, ResultSet rs, Statement st) {
+        closeIfNescesary(connection);
+        FileHelper.safeClose(rs, st);
+    }
+
+    /**
+     * Convenience method to get the available catalogNames using this
+     * connection.
+     * 
+     * @return a String-array with the names of the available catalogs.
+     */
+    public String[] getCatalogNames() {
+        Connection connection = getConnection();
+
+        // Retrieve metadata
+        DatabaseMetaData metaData = null;
+        ResultSet rs = null;
+        try {
+            metaData = connection.getMetaData();
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "retrieve metadata");
+        }
+
+        // Retrieve catalogs
+        logger.debug("Retrieving catalogs");
+        List<String> catalogs = new ArrayList<String>();
+        try {
+            rs = metaData.getCatalogs();
+            while (rs.next()) {
+                String catalogName = rs.getString(1);
+                logger.debug("Found catalogName: {}", catalogName);
+                catalogs.add(catalogName);
+            }
+        } catch (SQLException e) {
+            logger.error("Error retrieving catalog metadata", e);
+        } finally {
+            close(connection, rs, null);
+            logger.debug("Retrieved {} catalogs", catalogs.size());
+        }
+        return catalogs.toArray(new String[catalogs.size()]);
+    }
+
+    /**
+     * Gets the delegate from the JDBC API (ie. Connection or DataSource) that
+     * is being used to perform database interactions.
+     * 
+     * @return either a DataSource or a Connection, depending on the
+     *         configuration of the DataContext.
+     */
+    public Object getDelegate() {
+        if (_dataSource == null) {
+            return _connection;
+        }
+        return _dataSource;
+    }
+
+    /**
+     * Gets an appropriate connection object to use - either a dedicated
+     * connection or a new connection from the datasource object.
+     * 
+     * Hint: Use the {@link #close(Connection, ResultSet, Statement)} method to
+     * close the connection (and any ResultSet or Statements involved).
+     */
+    public Connection getConnection() {
+        if (_dataSource == null) {
+            return _connection;
+        }
+        try {
+            return _dataSource.getConnection();
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "establish connection");
+        }
+    }
+
+    private void closeIfNescesary(Connection con) {
+        if (con != null) {
+            if (_dataSource != null) {
+                // closing connections after individual usage is only nescesary
+                // when they are being pulled from a DataSource.
+                FileHelper.safeClose(con);
+            }
+        }
+    }
+
+    public String getDefaultSchemaName() {
+        // Use a boolean to check if the result has been
+        // found, because a schema name can actually be
+        // null (for example in the case of Firebird
+        // databases).
+        boolean found = false;
+        String result = null;
+        String[] schemaNames = getSchemaNames();
+
+        // First strategy: If there's only one schema available, that must
+        // be it
+        if (schemaNames.length == 1) {
+            result = schemaNames[0];
+            found = true;
+        }
+
+        if (!found) {
+            Connection connection = getConnection();
+            try {
+                DatabaseMetaData metaData = connection.getMetaData();
+
+                // Second strategy: Find default schema name by examining the
+                // URL
+                if (!found) {
+                    String url = metaData.getURL();
+                    if (url != null && url.length() > 0) {
+                        if (schemaNames.length > 0) {
+                            StringTokenizer st = new StringTokenizer(url, "/\\:");
+                            int tokenCount = st.countTokens();
+                            if (tokenCount > 0) {
+                                for (int i = 1; i < tokenCount; i++) {
+                                    st.nextToken();
+                                }
+                                String lastToken = st.nextToken();
+
+                                for (int i = 0; i < schemaNames.length && !found; i++) {
+                                    String schemaName = schemaNames[i];
+                                    if (lastToken.indexOf(schemaName) != -1) {
+                                        result = schemaName;
+                                        found = true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+
+                // Third strategy: Check for schema equal to username
+                if (!found) {
+                    String username = metaData.getUserName();
+                    if (username != null) {
+                        for (int i = 0; i < schemaNames.length && !found; i++) {
+                            if (username.equalsIgnoreCase(schemaNames[i])) {
+                                result = schemaNames[i];
+                                found = true;
+                            }
+                        }
+                    }
+                }
+
+            } catch (SQLException e) {
+                throw JdbcUtils.wrapException(e, "determine default schema name");
+            } finally {
+                closeIfNescesary(connection);
+            }
+
+            // Fourth strategy: Find default schema name by vendor-specific
+            // hacks
+            if (!found) {
+                if (DATABASE_PRODUCT_POSTGRESQL.equalsIgnoreCase(_databaseProductName)) {
+                    if (_catalogName == null) {
+                        result = "public";
+                    } else {
+                        result = _catalogName;
+                    }
+                    found = true;
+                }
+                if (DATABASE_PRODUCT_HSQLDB.equalsIgnoreCase(_databaseProductName)) {
+                    for (int i = 0; i < schemaNames.length && !found; i++) {
+                        String schemaName = schemaNames[i];
+                        if ("PUBLIC".equals(schemaName)) {
+                            result = schemaName;
+                            found = true;
+                            break;
+                        }
+                    }
+                }
+                if (DATABASE_PRODUCT_SQLSERVER.equals(_databaseProductName)) {
+                    for (int i = 0; i < schemaNames.length && !found; i++) {
+                        String schemaName = schemaNames[i];
+                        if ("dbo".equals(schemaName)) {
+                            result = schemaName;
+                            found = true;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Microsoft SQL Server returns users instead of schemas when calling
+     * metadata.getSchemas() This is a simple workaround.
+     * 
+     * @return
+     * @throws SQLException
+     */
+    private Set<String> getSchemaSQLServerNames(DatabaseMetaData metaData) throws SQLException {
+        // Distinct schema names. metaData.getTables() is a denormalized
+        // resultset
+        Set<String> schemas = new HashSet<String>();
+        ResultSet rs = metaData.getTables(_catalogName, null, null, JdbcUtils.getTableTypesAsStrings(_tableTypes));
+        while (rs.next()) {
+            schemas.add(rs.getString("TABLE_SCHEM"));
+        }
+        return schemas;
+    }
+
+    public JdbcDataContext setQueryRewriter(IQueryRewriter queryRewriter) {
+        if (queryRewriter == null) {
+            throw new IllegalArgumentException("Query rewriter cannot be null");
+        }
+        _queryRewriter = queryRewriter;
+        return this;
+    }
+
+    public IQueryRewriter getQueryRewriter() {
+        return _queryRewriter;
+    }
+
+    public String getIdentifierQuoteString() {
+        return _identifierQuoteString;
+    }
+
+    @Override
+    protected String[] getSchemaNamesInternal() {
+        Connection connection = getConnection();
+        try {
+            DatabaseMetaData metaData = connection.getMetaData();
+            Collection<String> result = new ArrayList<String>();
+
+            if (DATABASE_PRODUCT_SQLSERVER.equals(_databaseProductName)) {
+                result = getSchemaSQLServerNames(metaData);
+            } else if (_usesCatalogsAsSchemas) {
+                String[] catalogNames = getCatalogNames();
+                for (String name : catalogNames) {
+                    logger.debug("Found catalogName: {}", name);
+                    result.add(name);
+                }
+            } else {
+                ResultSet rs = metaData.getSchemas();
+                while (rs.next()) {
+                    String schemaName = rs.getString(1);
+                    logger.debug("Found schemaName: {}", schemaName);
+                    result.add(schemaName);
+                }
+                rs.close();
+            }
+
+            if (DATABASE_PRODUCT_MYSQL.equals(_databaseProductName)) {
+                result.remove("information_schema");
+            }
+
+            // If still no schemas are found, add a schema with a null-name
+            if (result.isEmpty()) {
+                logger.info("No schemas or catalogs found. Creating unnamed schema.");
+                result.add(null);
+            }
+            return result.toArray(new String[result.size()]);
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "get schema names");
+        } finally {
+            closeIfNescesary(connection);
+        }
+    }
+
+    @Override
+    protected Schema getSchemaByNameInternal(String name) {
+        JdbcSchema schema = new JdbcSchema(name, _metadataLoader);
+        _metadataLoader.loadTables(schema);
+        return schema;
+    }
+
+    public FetchSizeCalculator getFetchSizeCalculator() {
+        return _fetchSizeCalculator;
+    }
+
+    @Override
+    public void executeUpdate(final UpdateScript update) {
+        final JdbcUpdateCallback updateCallback;
+
+        if (_supportsBatchUpdates && update instanceof BatchUpdateScript) { 
+            updateCallback = new JdbcBatchUpdateCallback(this);
+        } else {
+            updateCallback = new JdbcSimpleUpdateCallback(this);
+        }
+
+        try {
+            if (isSingleConnection() && isDefaultAutoCommit()) {
+                // if auto-commit is going to be switched off and on during
+                // updates, then the update needs to be synchronized, to avoid
+                // race-conditions when switching off and on.
+                synchronized (_connection) {
+                    update.run(updateCallback);
+                }
+            } else {
+                update.run(updateCallback);
+            }
+            updateCallback.close(true);
+        } catch (RuntimeException e) {
+            updateCallback.close(false);
+            throw e;
+        }
+    }
+
+    protected boolean isSingleConnection() {
+        return _singleConnection;
+    }
+
+    protected boolean isDefaultAutoCommit() {
+        return _isDefaultAutoCommit;
+    }
+
+    @Override
+    protected boolean isQualifiedPathDelim(char c) {
+        if (_identifierQuoteString == null || _identifierQuoteString.length() == 0) {
+            return super.isQualifiedPathDelim(c);
+        }
+        return c == '.' || c == _identifierQuoteString.charAt(0);
+    }
+
+    public TableType[] getTableTypes() {
+        return _tableTypes;
+    }
+
+    public String getCatalogName() {
+        return _catalogName;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataSet.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataSet.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataSet.java
new file mode 100644
index 0000000..09b3689
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDataSet.java
@@ -0,0 +1,229 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.eobjects.metamodel.MetaModelException;
+import org.eobjects.metamodel.data.AbstractDataSet;
+import org.eobjects.metamodel.data.DefaultRow;
+import org.eobjects.metamodel.data.Row;
+import org.eobjects.metamodel.query.Query;
+import org.eobjects.metamodel.query.SelectItem;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.util.FileHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DataSet implementation that wraps a JDBC resultset.
+ * 
+ * @author Kasper Sørensen
+ */
+final class JdbcDataSet extends AbstractDataSet {
+
+	private static final Logger logger = LoggerFactory.getLogger(JdbcDataSet.class);
+
+	private final JdbcCompiledQuery _compiledQuery;
+	private final JdbcCompiledQueryLease _lease;
+	private final Statement _statement;
+	private final ResultSet _resultSet;
+	private final JdbcDataContext _jdbcDataContext;
+	private final Connection _connection;
+	private Row _row;
+	private boolean _closed;
+
+	/**
+	 * Constructor used for regular query execution.
+	 * 
+	 * @param query
+	 * @param jdbcDataContext
+	 * @param connection
+	 * @param statement
+	 * @param resultSet
+	 */
+	public JdbcDataSet(Query query, JdbcDataContext jdbcDataContext, Connection connection, Statement statement,
+			ResultSet resultSet) {
+		super(query.getSelectClause().getItems());
+		if (query == null || statement == null || resultSet == null) {
+			throw new IllegalArgumentException("Arguments cannot be null");
+		}
+		_jdbcDataContext = jdbcDataContext;
+		_connection = connection;
+		_statement = statement;
+		_resultSet = resultSet;
+		_closed = false;
+		_compiledQuery = null;
+		_lease = null;
+	}
+
+	/**
+	 * Constructor used for compiled query execution
+	 * 
+	 * @param query
+	 * @param jdbcDataContext
+	 * @param resultSet
+	 */
+	public JdbcDataSet(JdbcCompiledQuery compiledQuery, JdbcCompiledQueryLease lease, ResultSet resultSet) {
+		super(compiledQuery.getSelectItems());
+		if (compiledQuery == null || lease == null || resultSet == null) {
+			throw new IllegalArgumentException("Arguments cannot be null");
+		}
+
+		_compiledQuery = compiledQuery;
+		_lease = lease;
+
+		_jdbcDataContext = null;
+		_connection = null;
+		_statement = null;
+		_resultSet = resultSet;
+		_closed = false;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public Row getRow() {
+		return _row;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public boolean next() throws MetaModelException {
+		try {
+			boolean result = _resultSet.next();
+			if (result) {
+				Object[] values = new Object[getHeader().size()];
+				for (int i = 0; i < values.length; i++) {
+
+					values[i] = getValue(_resultSet, i);
+
+					try {
+						// some drivers return boxed primitive types in stead of
+						// nulls (such as false in stead of null for a Boolean
+						// column)
+						if (_resultSet.wasNull()) {
+							values[i] = null;
+						}
+					} catch (Exception e) {
+						logger.debug("Could not invoke wasNull() method on resultset, error message: {}",
+								e.getMessage());
+					}
+				}
+				_row = new DefaultRow(getHeader(), values);
+			} else {
+				_row = null;
+			}
+			return result;
+		} catch (SQLException e) {
+			throw JdbcUtils.wrapException(e, "get next record in resultset");
+		}
+	}
+
+	private Object getValue(ResultSet resultSet, int i) throws SQLException {
+		final SelectItem selectItem = getHeader().getSelectItem(i);
+		final int columnIndex = i + 1;
+		if (selectItem.getFunction() == null) {
+			Column column = selectItem.getColumn();
+			if (column != null) {
+				ColumnType type = column.getType();
+				try {
+					switch (type) {
+					case TIME:
+						return _resultSet.getTime(columnIndex);
+					case DATE:
+						return _resultSet.getDate(columnIndex);
+					case TIMESTAMP:
+						return _resultSet.getTimestamp(columnIndex);
+					case BLOB:
+						final Blob blob = _resultSet.getBlob(columnIndex);
+						if (isLobConversionEnabled()) {
+							final InputStream inputStream = blob.getBinaryStream();
+							final byte[] bytes = FileHelper.readAsBytes(inputStream);
+							return bytes;
+						}
+						return blob;
+					case BINARY:
+					case VARBINARY:
+					case LONGVARBINARY:
+						return _resultSet.getBytes(columnIndex);
+					case CLOB:
+					case NCLOB:
+						final Clob clob = _resultSet.getClob(columnIndex);
+						if (isLobConversionEnabled()) {
+							final Reader reader = clob.getCharacterStream();
+							final String result = FileHelper.readAsString(reader);
+							return result;
+						}
+						return clob;
+					case BIT:
+					case BOOLEAN:
+						return _resultSet.getBoolean(columnIndex);
+					}
+				} catch (Exception e) {
+					logger.warn("Failed to retrieve " + type
+							+ " value using type-specific getter, retrying with generic getObject(...) method", e);
+				}
+			}
+		}
+		return _resultSet.getObject(columnIndex);
+	}
+
+	private boolean isLobConversionEnabled() {
+		final String systemProperty = System.getProperty(JdbcDataContext.SYSTEM_PROPERTY_CONVERT_LOBS);
+		return "true".equals(systemProperty);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public void close() {
+		if (_closed) {
+			return;
+		}
+		if (_jdbcDataContext != null) {
+			_jdbcDataContext.close(_connection, _resultSet, _statement);
+		}
+		if (_compiledQuery != null) {
+			_compiledQuery.returnLease(_lease);
+		}
+		_closed = true;
+	}
+
+	@Override
+	protected void finalize() throws Throwable {
+		super.finalize();
+		if (!_closed) {
+			logger.warn("finalize() invoked, but DataSet is not closed. Invoking close() on {}", this);
+			close();
+		}
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDeleteBuilder.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDeleteBuilder.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDeleteBuilder.java
new file mode 100644
index 0000000..ef04b63
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDeleteBuilder.java
@@ -0,0 +1,98 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.eobjects.metamodel.MetaModelException;
+import org.eobjects.metamodel.delete.AbstractRowDeletionBuilder;
+import org.eobjects.metamodel.delete.RowDeletionBuilder;
+import org.eobjects.metamodel.jdbc.dialects.IQueryRewriter;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.schema.Table;
+import org.eobjects.metamodel.util.FileHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link RowDeletionBuilder} that issues an SQL DELETE FROM statement
+ * 
+ * @author Kasper Sørensen
+ */
+final class JdbcDeleteBuilder extends AbstractRowDeletionBuilder {
+
+    private static final Logger logger = LoggerFactory.getLogger(JdbcDeleteBuilder.class);
+
+    private final JdbcUpdateCallback _updateCallback;
+    private final IQueryRewriter _queryRewriter;
+    private final boolean _inlineValues;
+
+    public JdbcDeleteBuilder(JdbcUpdateCallback updateCallback, Table table, IQueryRewriter queryRewriter) {
+        this(updateCallback, table, queryRewriter, false);
+    }
+
+    public JdbcDeleteBuilder(JdbcUpdateCallback updateCallback, Table table, IQueryRewriter queryRewriter,
+            boolean inlineValues) {
+        super(table);
+        _updateCallback = updateCallback;
+        _queryRewriter = queryRewriter;
+        _inlineValues = inlineValues;
+    }
+
+    @Override
+    public void execute() throws MetaModelException {
+        String sql = createSqlStatement();
+
+        logger.debug("Delete statement created: {}", sql);
+        final boolean reuseStatement = !_inlineValues;
+        final PreparedStatement st = _updateCallback.getPreparedStatement(sql, reuseStatement);
+        try {
+            if (reuseStatement) {
+                int valueCounter = 1;
+                final List<FilterItem> whereItems = getWhereItems();
+                for (FilterItem whereItem : whereItems) {
+                    if (JdbcUtils.isPreparedParameterCandidate(whereItem)) {
+                        Object operand = whereItem.getOperand();
+                        st.setObject(valueCounter, operand);
+                        valueCounter++;
+                    }
+                }
+            }
+            _updateCallback.executePreparedStatement(st, reuseStatement);
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "execute delete statement: " + sql);
+        } finally {
+            if (_inlineValues) {
+                FileHelper.safeClose(st);
+            }
+        }
+    }
+
+    protected String createSqlStatement() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("DELETE FROM ");
+        sb.append(_queryRewriter.rewriteFromItem(new FromItem(getTable())));
+        sb.append(JdbcUtils.createWhereClause(getWhereItems(), _queryRewriter, _inlineValues));
+        return sb.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDropTableBuilder.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDropTableBuilder.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDropTableBuilder.java
new file mode 100644
index 0000000..e903726
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcDropTableBuilder.java
@@ -0,0 +1,72 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import org.eobjects.metamodel.drop.AbstractTableDropBuilder;
+import org.eobjects.metamodel.drop.TableDropBuilder;
+import org.eobjects.metamodel.jdbc.dialects.IQueryRewriter;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.schema.Schema;
+import org.eobjects.metamodel.schema.Table;
+
+/**
+ * {@link TableDropBuilder} that issues an SQL DROP TABLE statement
+ * 
+ * @author Kasper Sørensen
+ */
+final class JdbcDropTableBuilder extends AbstractTableDropBuilder implements TableDropBuilder {
+
+    private final JdbcUpdateCallback _updateCallback;
+    private final IQueryRewriter _queryRewriter;
+
+    public JdbcDropTableBuilder(JdbcUpdateCallback updateCallback, Table table, IQueryRewriter queryRewriter) {
+        super(table);
+        _updateCallback = updateCallback;
+        _queryRewriter = queryRewriter;
+    }
+
+    @Override
+    public void execute() {
+        String sql = createSqlStatement();
+
+        PreparedStatement statement = _updateCallback.getPreparedStatement(sql, false);
+        try {
+            _updateCallback.executePreparedStatement(statement, false);
+
+            // remove the table reference from the schema
+            final Schema schema = getTable().getSchema();
+            if (schema instanceof JdbcSchema) {
+                ((JdbcSchema) schema).refreshTables();
+            }
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "execute drop table statement: " + sql);
+        }
+    }
+
+    protected String createSqlStatement() {
+        FromItem fromItem = new FromItem(getTable());
+        String tableLabel = _queryRewriter.rewriteFromItem(fromItem);
+
+        return "DROP TABLE " + tableLabel;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcInsertBuilder.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcInsertBuilder.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcInsertBuilder.java
new file mode 100644
index 0000000..9cfd4bc
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcInsertBuilder.java
@@ -0,0 +1,151 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Arrays;
+
+import org.eobjects.metamodel.insert.AbstractRowInsertionBuilder;
+import org.eobjects.metamodel.insert.RowInsertionBuilder;
+import org.eobjects.metamodel.jdbc.dialects.IQueryRewriter;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.Table;
+import org.eobjects.metamodel.util.FileHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link RowInsertionBuilder} that issues an SQL INSERT statement
+ * 
+ * @author Kasper Sørensen
+ */
+final class JdbcInsertBuilder extends AbstractRowInsertionBuilder<JdbcUpdateCallback> {
+
+	private static final Logger logger = LoggerFactory.getLogger(JdbcInsertBuilder.class);
+
+	private final boolean _inlineValues;
+	private final IQueryRewriter _queryRewriter;
+
+	public JdbcInsertBuilder(JdbcUpdateCallback updateCallback, Table table, IQueryRewriter queryRewriter) {
+		this(updateCallback, table, false, queryRewriter);
+	}
+
+	public JdbcInsertBuilder(JdbcUpdateCallback updateCallback, Table table, boolean isInlineValues,
+			IQueryRewriter queryRewriter) {
+		super(updateCallback, table);
+		if (!(table instanceof JdbcTable)) {
+			throw new IllegalArgumentException("Not a valid JDBC table: " + table);
+		}
+
+		_inlineValues = isInlineValues;
+		_queryRewriter = queryRewriter;
+	}
+
+	@Override
+	public void execute() {
+		final String sql = createSqlStatement();
+		if (logger.isDebugEnabled()) {
+			logger.debug("Inserting: {}", Arrays.toString(getValues()));
+			logger.debug("Insert statement created: {}", sql);
+		}
+		final JdbcUpdateCallback updateCallback = getUpdateCallback();
+		final boolean reuseStatement = !_inlineValues;
+		final PreparedStatement st = updateCallback.getPreparedStatement(sql, reuseStatement);
+		try {
+			if (reuseStatement) {
+				Column[] columns = getColumns();
+				Object[] values = getValues();
+				boolean[] explicitNulls = getExplicitNulls();
+				int valueCounter = 1;
+				for (int i = 0; i < columns.length; i++) {
+					boolean explicitNull = explicitNulls[i];
+					if (values[i] != null || explicitNull) {
+						JdbcUtils.setStatementValue(st, valueCounter, columns[i], values[i]);
+						valueCounter++;
+					}
+				}
+			}
+			updateCallback.executePreparedStatement(st, reuseStatement);
+		} catch (SQLException e) {
+			throw JdbcUtils.wrapException(e, "execute insert statement: " + sql);
+		} finally {
+			if (_inlineValues) {
+				FileHelper.safeClose(st);
+			}
+		}
+	}
+	
+	protected String createSqlStatement() {
+	    return createSqlStatement(_inlineValues);
+	}
+
+	private String createSqlStatement(boolean inlineValues) {
+		final Object[] values = getValues();
+		final Table table = getTable();
+		final StringBuilder sb = new StringBuilder();
+
+		final String tableLabel = _queryRewriter.rewriteFromItem(new FromItem(table));
+
+		sb.append("INSERT INTO ");
+		sb.append(tableLabel);
+		sb.append(" (");
+		Column[] columns = getColumns();
+		boolean[] explicitNulls = getExplicitNulls();
+		boolean firstValue = true;
+		for (int i = 0; i < columns.length; i++) {
+			if (values[i] != null || explicitNulls[i]) {
+				if (firstValue) {
+					firstValue = false;
+				} else {
+					sb.append(',');
+				}
+				String columnName = columns[i].getName();
+				columnName = getUpdateCallback().quoteIfNescesary(columnName);
+				sb.append(columnName);
+			}
+		}
+
+		sb.append(") VALUES (");
+		firstValue = true;
+		for (int i = 0; i < columns.length; i++) {
+			if (values[i] != null || explicitNulls[i]) {
+				if (firstValue) {
+					firstValue = false;
+				} else {
+					sb.append(',');
+				}
+				if (inlineValues) {
+					sb.append(JdbcUtils.getValueAsSql(columns[i], values[i], _queryRewriter));
+				} else {
+					sb.append('?');
+				}
+			}
+		}
+		sb.append(")");
+		String sql = sb.toString();
+		return sql;
+	}
+
+	@Override
+	public String toSql() {
+	    return createSqlStatement(true);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcMetadataLoader.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcMetadataLoader.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcMetadataLoader.java
new file mode 100644
index 0000000..97753a1
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcMetadataLoader.java
@@ -0,0 +1,427 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eobjects.metamodel.MetaModelException;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.ColumnType;
+import org.eobjects.metamodel.schema.MutableColumn;
+import org.eobjects.metamodel.schema.MutableRelationship;
+import org.eobjects.metamodel.schema.Schema;
+import org.eobjects.metamodel.schema.Table;
+import org.eobjects.metamodel.schema.TableType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link MetadataLoader} for JDBC metadata loading.
+ */
+final class JdbcMetadataLoader implements MetadataLoader {
+
+	private static final Logger logger = LoggerFactory.getLogger(JdbcMetadataLoader.class);
+
+	private final JdbcDataContext _dataContext;
+	private final boolean _usesCatalogsAsSchemas;
+	private final String _identifierQuoteString;
+
+	// these three sets contains the system identifies of whether specific items
+	// have been loaded for tables/schemas. Using system identities avoid having
+	// to call equals(...) method etc. while doing lazy loading of these items.
+	// Invoking equals(...) would be prone to stack overflows ...
+	private final Set<Integer> _loadedRelations;
+	private final Set<Integer> _loadedColumns;
+	private final Set<Integer> _loadedIndexes;
+	private final Set<Integer> _loadedPrimaryKeys;
+
+	public JdbcMetadataLoader(JdbcDataContext dataContext, boolean usesCatalogsAsSchemas, String identifierQuoteString) {
+		_dataContext = dataContext;
+		_usesCatalogsAsSchemas = usesCatalogsAsSchemas;
+		_identifierQuoteString = identifierQuoteString;
+		_loadedRelations = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
+		_loadedColumns = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
+		_loadedIndexes = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
+		_loadedPrimaryKeys = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
+	}
+
+	@Override
+	public void loadTables(JdbcSchema schema) {
+		final Connection connection = _dataContext.getConnection();
+		try {
+			final DatabaseMetaData metaData = connection.getMetaData();
+
+			// Creates string array to represent the table types
+			final String[] types = JdbcUtils.getTableTypesAsStrings(_dataContext.getTableTypes());
+			loadTables(schema, metaData, types);
+		} catch (SQLException e) {
+			throw JdbcUtils.wrapException(e, "retrieve table metadata for " + schema.getName());
+		} finally {
+			_dataContext.close(connection, null, null);
+		}
+	}
+
+	private void loadTables(JdbcSchema schema, DatabaseMetaData metaData, String[] types) {
+		String catalogName = _dataContext.getCatalogName();
+
+		ResultSet rs = null;
+		try {
+			if (logger.isDebugEnabled()) {
+				logger.debug("Querying for table types " + Arrays.toString(types) + " in catalog: " + catalogName
+						+ ", schema: " + schema.getName());
+			}
+			if (_usesCatalogsAsSchemas) {
+				rs = metaData.getTables(schema.getName(), null, null, types);
+			} else {
+				rs = metaData.getTables(catalogName, schema.getName(), null, types);
+			}
+			schema.clearTables();
+			int tableNumber = -1;
+			while (rs.next()) {
+				tableNumber++;
+				String tableCatalog = rs.getString(1);
+				String tableSchema = rs.getString(2);
+				String tableName = rs.getString(3);
+				String tableTypeName = rs.getString(4);
+				TableType tableType = TableType.getTableType(tableTypeName);
+				String tableRemarks = rs.getString(5);
+
+				if (logger.isDebugEnabled()) {
+					logger.debug("Found table: tableCatalog=" + tableCatalog + ",tableSchema=" + tableSchema
+							+ ",tableName=" + tableName);
+				}
+
+				if (tableSchema == null) {
+					tableSchema = tableCatalog;
+				}
+
+				JdbcTable table = new JdbcTable(tableName, tableType, schema, this);
+				table.setRemarks(tableRemarks);
+				table.setQuote(_identifierQuoteString);
+				schema.addTable(table);
+			}
+
+			final int tablesReturned = tableNumber + 1;
+			if (tablesReturned == 0) {
+				logger.info("No table metadata records returned for schema '{}'", schema.getName());
+			} else {
+				logger.debug("Returned {} table metadata records for schema '{}'", new Object[] { tablesReturned,
+						schema.getName() });
+			}
+
+		} catch (SQLException e) {
+			throw JdbcUtils.wrapException(e, "retrieve table metadata for " + schema.getName());
+		} finally {
+			_dataContext.close(null, rs, null);
+		}
+	}
+
+	@Override
+	public void loadIndexes(JdbcTable table) {
+		final int identity = System.identityHashCode(table);
+		if (_loadedIndexes.contains(identity)) {
+			return;
+		}
+		synchronized (this) {
+			if (_loadedIndexes.contains(identity)) {
+				return;
+			}
+
+			final Connection connection = _dataContext.getConnection();
+			try {
+				DatabaseMetaData metaData = connection.getMetaData();
+				loadIndexes(table, metaData);
+				_loadedIndexes.add(identity);
+			} catch (SQLException e) {
+				throw JdbcUtils.wrapException(e, "load indexes");
+			} finally {
+				_dataContext.close(connection, null, null);
+			}
+		}
+	}
+
+	@Override
+	public void loadPrimaryKeys(JdbcTable table) {
+		final int identity = System.identityHashCode(table);
+		if (_loadedPrimaryKeys.contains(identity)) {
+			return;
+		}
+		synchronized (this) {
+			if (_loadedPrimaryKeys.contains(identity)) {
+				return;
+			}
+			final Connection connection = _dataContext.getConnection();
+			try {
+				DatabaseMetaData metaData = connection.getMetaData();
+				loadPrimaryKeys(table, metaData);
+				_loadedPrimaryKeys.add(identity);
+			} catch (SQLException e) {
+				throw JdbcUtils.wrapException(e, "load primary keys");
+			} finally {
+				_dataContext.close(connection, null, null);
+			}
+		}
+	}
+
+	private void loadPrimaryKeys(JdbcTable table, DatabaseMetaData metaData) throws MetaModelException {
+		Schema schema = table.getSchema();
+		ResultSet rs = null;
+
+		try {
+			if (_usesCatalogsAsSchemas) {
+				rs = metaData.getPrimaryKeys(schema.getName(), null, table.getName());
+			} else {
+				rs = metaData.getPrimaryKeys(_dataContext.getCatalogName(), schema.getName(), table.getName());
+			}
+			while (rs.next()) {
+				String columnName = rs.getString(4);
+				if (columnName != null) {
+					MutableColumn column = (MutableColumn) table.getColumnByName(columnName);
+					if (column != null) {
+						column.setPrimaryKey(true);
+					} else {
+						logger.error("Indexed column \"{}\" could not be found in table: {}", columnName, table);
+					}
+				}
+			}
+		} catch (SQLException e) {
+			throw JdbcUtils.wrapException(e, "retrieve primary keys for " + table.getName());
+		} finally {
+			_dataContext.close(null, rs, null);
+		}
+	}
+
+	private void loadIndexes(Table table, DatabaseMetaData metaData) throws MetaModelException {
+		Schema schema = table.getSchema();
+		ResultSet rs = null;
+		// Ticket #170: IndexInfo is nice-to-have, not need-to-have, so
+		// we will do a nice failover on SQLExceptions
+		try {
+			if (_usesCatalogsAsSchemas) {
+				rs = metaData.getIndexInfo(schema.getName(), null, table.getName(), false, true);
+			} else {
+				rs = metaData.getIndexInfo(_dataContext.getCatalogName(), schema.getName(), table.getName(), false,
+						true);
+			}
+			while (rs.next()) {
+				String columnName = rs.getString(9);
+				if (columnName != null) {
+					MutableColumn column = (MutableColumn) table.getColumnByName(columnName);
+					if (column != null) {
+						column.setIndexed(true);
+					} else {
+						logger.error("Indexed column \"{}\" could not be found in table: {}", columnName, table);
+					}
+				}
+			}
+		} catch (SQLException e) {
+			throw JdbcUtils.wrapException(e, "retrieve index information for " + table.getName());
+		} finally {
+			_dataContext.close(null, rs, null);
+		}
+	}
+
+	/**
+	 * Loads column metadata (no indexes though) for a table
+	 * 
+	 * @param table
+	 */
+	@Override
+	public void loadColumns(JdbcTable table) {
+		final int identity = System.identityHashCode(table);
+		if (_loadedColumns.contains(identity)) {
+			return;
+		}
+		synchronized (this) {
+			if (_loadedColumns.contains(identity)) {
+				return;
+			}
+
+			final Connection connection = _dataContext.getConnection();
+			try {
+				DatabaseMetaData metaData = connection.getMetaData();
+				loadColumns(table, metaData);
+				_loadedColumns.add(identity);
+			} catch (Exception e) {
+				logger.error("Could not load columns for table: " + table, e);
+			} finally {
+				_dataContext.close(connection, null, null);
+			}
+		}
+	}
+
+	private void loadColumns(JdbcTable table, DatabaseMetaData metaData) {
+		final Schema schema = table.getSchema();
+		ResultSet rs = null;
+		try {
+			if (logger.isDebugEnabled()) {
+				logger.debug("Querying for columns in table: " + table.getName());
+			}
+			int columnNumber = -1;
+			if (_usesCatalogsAsSchemas) {
+				rs = metaData.getColumns(schema.getName(), null, table.getName(), null);
+			} else {
+				rs = metaData.getColumns(_dataContext.getCatalogName(), schema.getName(), table.getName(), null);
+			}
+			while (rs.next()) {
+				columnNumber++;
+				final String columnName = rs.getString(4);
+				if (_identifierQuoteString == null && new StringTokenizer(columnName).countTokens() > 1) {
+					logger.warn("column name contains whitespace: \"" + columnName + "\".");
+				}
+
+				final int jdbcType = rs.getInt(5);
+				final String nativeType = rs.getString(6);
+				final Integer columnSize = rs.getInt(7);
+
+				if (logger.isDebugEnabled()) {
+					logger.debug("Found column: table=" + table.getName() + ",columnName=" + columnName
+							+ ",nativeType=" + nativeType + ",columnSize=" + columnSize);
+				}
+
+				final ColumnType columnType = _dataContext.getQueryRewriter().getColumnType(jdbcType, nativeType,
+						columnSize);
+
+				final int jdbcNullable = rs.getInt(11);
+				final Boolean nullable;
+				if (jdbcNullable == DatabaseMetaData.columnNullable) {
+					nullable = true;
+				} else if (jdbcNullable == DatabaseMetaData.columnNoNulls) {
+					nullable = false;
+				} else {
+					nullable = null;
+				}
+
+				final String remarks = rs.getString(12);
+
+				final JdbcColumn column = new JdbcColumn(columnName, columnType, table, columnNumber, nullable);
+				column.setRemarks(remarks);
+				column.setNativeType(nativeType);
+				column.setColumnSize(columnSize);
+				column.setQuote(_identifierQuoteString);
+				table.addColumn(column);
+			}
+
+			final int columnsReturned = columnNumber + 1;
+			if (columnsReturned == 0) {
+				logger.info("No column metadata records returned for table '{}' in schema '{}'", table.getName(),
+						schema.getName());
+			} else {
+				logger.debug("Returned {} column metadata records for table '{}' in schema '{}'", new Object[] {
+						columnsReturned, table.getName(), schema.getName() });
+			}
+
+		} catch (SQLException e) {
+			throw JdbcUtils.wrapException(e, "retrieve table metadata for " + table.getName());
+		} finally {
+			_dataContext.close(null, rs, null);
+		}
+	}
+
+	@Override
+	public void loadRelations(JdbcSchema schema) {
+		final int identity = System.identityHashCode(schema);
+		if (_loadedRelations.contains(identity)) {
+			return;
+		}
+		synchronized (this) {
+			if (_loadedRelations.contains(identity)) {
+				return;
+			}
+			final Connection connection = _dataContext.getConnection();
+			try {
+				final Table[] tables = schema.getTables();
+				final DatabaseMetaData metaData = connection.getMetaData();
+				for (Table table : tables) {
+					loadRelations(table, metaData);
+				}
+				_loadedRelations.add(identity);
+			} catch (Exception e) {
+				logger.error("Could not load relations for schema: " + schema, e);
+			} finally {
+				_dataContext.close(connection, null, null);
+			}
+		}
+	}
+
+	private void loadRelations(Table table, DatabaseMetaData metaData) {
+		Schema schema = table.getSchema();
+		ResultSet rs = null;
+		try {
+			if (_usesCatalogsAsSchemas) {
+				rs = metaData.getImportedKeys(schema.getName(), null, table.getName());
+			} else {
+				rs = metaData.getImportedKeys(_dataContext.getCatalogName(), schema.getName(), table.getName());
+			}
+			loadRelations(rs, schema);
+		} catch (SQLException e) {
+			throw JdbcUtils.wrapException(e, "retrieve imported keys for " + table.getName());
+		} finally {
+			_dataContext.close(null, rs, null);
+		}
+	}
+
+	private void loadRelations(ResultSet rs, Schema schema) throws SQLException {
+		while (rs.next()) {
+			String pkTableName = rs.getString(3);
+			String pkColumnName = rs.getString(4);
+
+			Column pkColumn = null;
+			Table pkTable = schema.getTableByName(pkTableName);
+			if (pkTable != null) {
+				pkColumn = pkTable.getColumnByName(pkColumnName);
+			}
+			if (logger.isDebugEnabled()) {
+				logger.debug("Found primary key relation: tableName=" + pkTableName + ",columnName=" + pkColumnName
+						+ ", matching column: " + pkColumn);
+			}
+
+			String fkTableName = rs.getString(7);
+			String fkColumnName = rs.getString(8);
+			Column fkColumn = null;
+			Table fkTable = schema.getTableByName(fkTableName);
+			if (fkTable != null) {
+				fkColumn = fkTable.getColumnByName(fkColumnName);
+			}
+			if (logger.isDebugEnabled()) {
+				logger.debug("Found foreign key relation: tableName=" + fkTableName + ",columnName=" + fkColumnName
+						+ ", matching column: " + fkColumn);
+			}
+
+			if (pkColumn == null || fkColumn == null) {
+				logger.error(
+						"Could not find relation columns: pkTableName={},pkColumnName={},fkTableName={},fkColumnName={}",
+						new Object[] { pkTableName, pkColumnName, fkTableName, fkColumnName });
+				logger.error("pkColumn={}", pkColumn);
+				logger.error("fkColumn={}", fkColumn);
+			} else {
+				MutableRelationship.createRelationship(new Column[] { pkColumn }, new Column[] { fkColumn });
+			}
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcSchema.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcSchema.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcSchema.java
new file mode 100644
index 0000000..28285f4
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcSchema.java
@@ -0,0 +1,71 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.io.ObjectStreamException;
+
+import org.eobjects.metamodel.schema.MutableSchema;
+import org.eobjects.metamodel.schema.MutableTable;
+import org.eobjects.metamodel.schema.Schema;
+
+/**
+ * Schema implementation for JDBC data contexts
+ * 
+ * @author Kasper Sørensen
+ */
+final class JdbcSchema extends MutableSchema {
+
+	private static final long serialVersionUID = 7543633400859277467L;
+	private transient MetadataLoader _metadataLoader;
+
+	public JdbcSchema(String name, MetadataLoader metadataLoader) {
+		super(name);
+		_metadataLoader = metadataLoader;
+	}
+
+	protected void refreshTables() {
+		if (_metadataLoader != null) {
+			_metadataLoader.loadTables(this);
+		}
+	}
+
+	public void loadRelations() {
+		if (_metadataLoader != null) {
+			_metadataLoader.loadRelations(this);
+		}
+	}
+
+	public Schema toSerializableForm() {
+		MutableTable[] tables = getTables();
+		for (MutableTable table : tables) {
+			table.getColumns();
+			table.getIndexedColumns();
+			table.getPrimaryKeys();
+		}
+		loadRelations();
+		return this;
+	}
+
+	/**
+	 * Called by the Java Serialization API to serialize the object.
+	 */
+	private Object writeReplace() throws ObjectStreamException {
+		return toSerializableForm();
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcSimpleUpdateCallback.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcSimpleUpdateCallback.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcSimpleUpdateCallback.java
new file mode 100644
index 0000000..2a02be8
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcSimpleUpdateCallback.java
@@ -0,0 +1,49 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import org.eobjects.metamodel.UpdateCallback;
+import org.eobjects.metamodel.UpdateScript;
+import org.eobjects.metamodel.util.FileHelper;
+
+/**
+ * Jdbc {@link UpdateCallback} for databases that do not support batch features.
+ * Instead we will use a single transaction for the {@link UpdateScript}.
+ * 
+ * @author Kasper Sørensen
+ */
+final class JdbcSimpleUpdateCallback extends JdbcUpdateCallback {
+
+    public JdbcSimpleUpdateCallback(JdbcDataContext dataContext) {
+        super(dataContext);
+    }
+    
+    @Override
+    protected void closePreparedStatement(PreparedStatement preparedStatement) {
+        FileHelper.safeClose(preparedStatement);
+    }
+
+    @Override
+    protected void executePreparedStatement(PreparedStatement st) throws SQLException {
+        st.executeUpdate();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcTable.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcTable.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcTable.java
new file mode 100644
index 0000000..4ab1419
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcTable.java
@@ -0,0 +1,84 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.io.ObjectStreamException;
+import java.util.List;
+
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.MutableTable;
+import org.eobjects.metamodel.schema.Relationship;
+import org.eobjects.metamodel.schema.Schema;
+import org.eobjects.metamodel.schema.TableType;
+
+/**
+ * Table implementation that is based on JDBC metadata.
+ * 
+ * @author Kasper Sørensen
+ */
+final class JdbcTable extends MutableTable {
+
+	private static final long serialVersionUID = 5952310469458880330L;
+
+	private final transient MetadataLoader _metadataLoader;
+
+	public JdbcTable(String name, TableType type, JdbcSchema schema, MetadataLoader metadataLoader) {
+		super(name, type, schema);
+		_metadataLoader = metadataLoader;
+	}
+
+	@Override
+	protected List<Column> getColumnsInternal() {
+		if (_metadataLoader != null) {
+			_metadataLoader.loadColumns(this);
+		}
+		return super.getColumnsInternal();
+	}
+
+	@Override
+	protected List<Relationship> getRelationshipsInternal() {
+		Schema schema = getSchema();
+		if (schema instanceof JdbcSchema) {
+			((JdbcSchema) schema).loadRelations();
+		}
+		return super.getRelationshipsInternal();
+	}
+	
+	protected void loadIndexes() {
+		if (_metadataLoader != null) {
+			_metadataLoader.loadIndexes(this);
+		}
+	}
+
+	/**
+	 * Called by the Java Serialization API to serialize the object.
+	 */
+	private Object writeReplace() throws ObjectStreamException {
+		getColumns();
+		loadIndexes();
+		loadPrimaryKeys();
+		return this;
+	}
+
+	public void loadPrimaryKeys() {
+		if (_metadataLoader != null) {
+			_metadataLoader.loadPrimaryKeys(this);
+		}
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUpdateBuilder.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUpdateBuilder.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUpdateBuilder.java
new file mode 100644
index 0000000..5860ec5
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUpdateBuilder.java
@@ -0,0 +1,153 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.eobjects.metamodel.MetaModelException;
+import org.eobjects.metamodel.jdbc.dialects.IQueryRewriter;
+import org.eobjects.metamodel.query.FilterItem;
+import org.eobjects.metamodel.query.FromItem;
+import org.eobjects.metamodel.schema.Column;
+import org.eobjects.metamodel.schema.Table;
+import org.eobjects.metamodel.update.AbstractRowUpdationBuilder;
+import org.eobjects.metamodel.update.RowUpdationBuilder;
+import org.eobjects.metamodel.util.FileHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link RowUpdationBuilder} that issues an SQL UPDATE statement
+ * 
+ * @author Kasper Sørensen
+ */
+final class JdbcUpdateBuilder extends AbstractRowUpdationBuilder {
+
+    private static final Logger logger = LoggerFactory.getLogger(JdbcUpdateBuilder.class);
+
+    private final boolean _inlineValues;
+    private final JdbcUpdateCallback _updateCallback;
+    private final IQueryRewriter _queryRewriter;
+
+    public JdbcUpdateBuilder(JdbcUpdateCallback updateCallback, Table table, IQueryRewriter queryRewriter) {
+        this(updateCallback, table, queryRewriter, false);
+    }
+
+    public JdbcUpdateBuilder(JdbcUpdateCallback updateCallback, Table table, IQueryRewriter queryRewriter,
+            boolean inlineValues) {
+        super(table);
+        _updateCallback = updateCallback;
+        _queryRewriter = queryRewriter;
+        _inlineValues = inlineValues;
+    }
+
+    @Override
+    public void execute() throws MetaModelException {
+        String sql = createSqlStatement();
+        logger.debug("Update statement created: {}", sql);
+        final boolean reuseStatement = !_inlineValues;
+        final PreparedStatement st = _updateCallback.getPreparedStatement(sql, reuseStatement);
+        try {
+            if (reuseStatement) {
+                Column[] columns = getColumns();
+                Object[] values = getValues();
+                boolean[] explicitNulls = getExplicitNulls();
+                int valueCounter = 1;
+                for (int i = 0; i < columns.length; i++) {
+                    boolean explicitNull = explicitNulls[i];
+                    if (values[i] != null || explicitNull) {
+                    	JdbcUtils.setStatementValue(st, valueCounter, columns[i], values[i]);
+                    	
+                        valueCounter++;
+                    }
+                }
+
+                List<FilterItem> whereItems = getWhereItems();
+                for (FilterItem whereItem : whereItems) {
+                    if (JdbcUtils.isPreparedParameterCandidate(whereItem)) {
+                        final Object operand = whereItem.getOperand();
+                        final Column column = whereItem.getSelectItem().getColumn();
+                        
+						JdbcUtils.setStatementValue(st, valueCounter, column, operand);
+                        
+                        valueCounter++;
+                    }
+                }
+            }
+            _updateCallback.executePreparedStatement(st, reuseStatement);
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "execute update statement: " + sql);
+        } finally {
+            if (_inlineValues) {
+                FileHelper.safeClose(st);
+            }
+        }
+    }
+    
+    protected String createSqlStatement() {
+        return createSqlStatement(_inlineValues);
+    }
+
+    private String createSqlStatement(boolean inlineValues) {
+        final Object[] values = getValues();
+        final Table table = getTable();
+        final StringBuilder sb = new StringBuilder();
+
+        final String tableLabel = _queryRewriter.rewriteFromItem(new FromItem(table));
+
+        sb.append("UPDATE ");
+        sb.append(tableLabel);
+        sb.append(" SET ");
+
+        Column[] columns = getColumns();
+        boolean[] explicitNulls = getExplicitNulls();
+        boolean firstValue = true;
+        for (int i = 0; i < columns.length; i++) {
+            if (values[i] != null || explicitNulls[i]) {
+                if (firstValue) {
+                    firstValue = false;
+                } else {
+                    sb.append(',');
+                }
+                String columnName = columns[i].getName();
+                columnName = _updateCallback.quoteIfNescesary(columnName);
+                sb.append(columnName);
+
+                sb.append('=');
+
+                if (inlineValues) {
+                    sb.append(JdbcUtils.getValueAsSql(columns[i], values[i], _queryRewriter));
+                } else {
+                    sb.append('?');
+                }
+            }
+        }
+
+        sb.append(JdbcUtils.createWhereClause(getWhereItems(), _queryRewriter, inlineValues));
+        String sql = sb.toString();
+        return sql;
+    }
+
+    @Override
+    public String toSql() {
+        return createSqlStatement(true);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metamodel/blob/e2e2b37a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUpdateCallback.java
----------------------------------------------------------------------
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUpdateCallback.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUpdateCallback.java
new file mode 100644
index 0000000..c0861fb
--- /dev/null
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcUpdateCallback.java
@@ -0,0 +1,216 @@
+/**
+ * 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.eobjects.metamodel.jdbc;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import org.eobjects.metamodel.AbstractUpdateCallback;
+import org.eobjects.metamodel.UpdateCallback;
+import org.eobjects.metamodel.create.TableCreationBuilder;
+import org.eobjects.metamodel.delete.RowDeletionBuilder;
+import org.eobjects.metamodel.drop.TableDropBuilder;
+import org.eobjects.metamodel.insert.RowInsertionBuilder;
+import org.eobjects.metamodel.schema.Schema;
+import org.eobjects.metamodel.schema.Table;
+import org.eobjects.metamodel.update.RowUpdationBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class JdbcUpdateCallback extends AbstractUpdateCallback implements UpdateCallback {
+
+    private static final Logger logger = LoggerFactory.getLogger(JdbcUpdateCallback.class);
+
+    private final JdbcDataContext _dataContext;
+    private Connection _connection;
+    private String _preparedStatementSql;
+    private PreparedStatement _preparedStatement;
+
+    public JdbcUpdateCallback(JdbcDataContext dataContext) {
+        super(dataContext);
+        _dataContext = dataContext;
+    }
+    
+    protected abstract void closePreparedStatement(PreparedStatement preparedStatement);
+
+    protected abstract void executePreparedStatement(PreparedStatement preparedStatement) throws SQLException;
+
+    public void executePreparedStatement(PreparedStatement preparedStatement, boolean reusedStatement)
+            throws SQLException {
+        executePreparedStatement(preparedStatement);
+        if (!reusedStatement) {
+            closePreparedStatement(preparedStatement);
+        }
+    }
+
+    protected final Connection getConnection() {
+        if (_connection == null) {
+            _connection = getDataContext().getConnection();
+            try {
+                _connection.setAutoCommit(false);
+            } catch (SQLException e) {
+                throw JdbcUtils.wrapException(e, "disable auto-commit");
+            }
+        }
+        return _connection;
+    }
+
+    public final void close(boolean success) {
+        if (_connection != null) {
+            if (success && _preparedStatement != null) {
+                closePreparedStatement(_preparedStatement);
+            }
+
+            try {
+                commitOrRollback(success);
+
+                if (_dataContext.isDefaultAutoCommit()) {
+                    try {
+                        getConnection().setAutoCommit(true);
+                    } catch (SQLException e) {
+                        throw JdbcUtils.wrapException(e, "enable auto-commit");
+                    }
+                }
+            } finally {
+                getDataContext().close(_connection, null, null);
+            }
+        }
+    }
+
+    private void commitOrRollback(boolean success) {
+        if (success) {
+            try {
+                getConnection().commit();
+            } catch (SQLException e) {
+                throw JdbcUtils.wrapException(e, "commit transaction");
+            }
+        } else {
+            try {
+                getConnection().rollback();
+            } catch (SQLException e) {
+                throw JdbcUtils.wrapException(e, "rollback transaction");
+            }
+        }
+    }
+
+    @Override
+    public final TableCreationBuilder createTable(Schema schema, String name) throws IllegalArgumentException,
+            IllegalStateException {
+        return new JdbcCreateTableBuilder(this, schema, name);
+    }
+
+    @Override
+    public final RowInsertionBuilder insertInto(Table table) throws IllegalArgumentException, IllegalStateException {
+        return new JdbcInsertBuilder(this, table, _dataContext.getQueryRewriter());
+    }
+
+    @Override
+    public final JdbcDataContext getDataContext() {
+        return _dataContext;
+    }
+
+    protected String quoteIfNescesary(String identifier) {
+        if (identifier == null) {
+            return null;
+        }
+        final String quote = _dataContext.getIdentifierQuoteString();
+        if (quote == null) {
+            return identifier;
+        }
+        boolean quotes = false;
+        if (identifier.indexOf(' ') != -1 || identifier.indexOf('-') != -1) {
+            quotes = true;
+        } else {
+            if (SqlKeywords.isKeyword(identifier)) {
+                quotes = true;
+            }
+        }
+
+        if (quotes) {
+            identifier = quote + identifier + quote;
+        }
+        return identifier;
+    }
+
+    public final PreparedStatement getPreparedStatement(String sql, boolean reuseStatement) {
+        final PreparedStatement preparedStatement;
+        if (reuseStatement) {
+            if (sql.equals(_preparedStatementSql)) {
+                preparedStatement = _preparedStatement;
+            } else {
+                if (_preparedStatement != null) {
+                    try {
+                        closePreparedStatement(_preparedStatement);
+                    } catch (RuntimeException e) {
+                        logger.error("Exception occurred while closing prepared statement: " + _preparedStatementSql);
+                        throw e;
+                    }
+                }
+                preparedStatement = createPreparedStatement(sql);
+                _preparedStatement = preparedStatement;
+                _preparedStatementSql = sql;
+            }
+        } else {
+            preparedStatement = createPreparedStatement(sql);
+        }
+        return preparedStatement;
+    }
+
+    private final PreparedStatement createPreparedStatement(String sql) {
+        try {
+            return getConnection().prepareStatement(sql);
+        } catch (SQLException e) {
+            throw JdbcUtils.wrapException(e, "create prepared statement for: " + sql);
+        }
+    }
+
+    @Override
+    public boolean isDeleteSupported() {
+        return true;
+    }
+
+    @Override
+    public RowDeletionBuilder deleteFrom(Table table) throws IllegalArgumentException, IllegalStateException,
+            UnsupportedOperationException {
+        return new JdbcDeleteBuilder(this, table, _dataContext.getQueryRewriter());
+    }
+
+    @Override
+    public boolean isDropTableSupported() {
+        return true;
+    }
+
+    @Override
+    public TableDropBuilder dropTable(Table table) throws IllegalArgumentException, IllegalStateException,
+            UnsupportedOperationException {
+        return new JdbcDropTableBuilder(this, table, _dataContext.getQueryRewriter());
+    }
+
+    @Override
+    public boolean isUpdateSupported() {
+        return true;
+    }
+
+    @Override
+    public RowUpdationBuilder update(Table table) throws IllegalArgumentException, IllegalStateException,
+            UnsupportedOperationException {
+        return new JdbcUpdateBuilder(this, table, _dataContext.getQueryRewriter());
+    }
+}