org.apache.sqoop.common.test.db.DatabaseProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sqoop.common.test.db.DatabaseProvider.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sqoop.common.test.db;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.sqoop.common.test.db.types.DatabaseType;
import org.apache.sqoop.common.test.db.types.DatabaseTypeList;
import org.apache.sqoop.common.test.db.types.EmptyTypeList;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;

/**
 * Database provider for testing purpose.
 *
 * Provider contains all methods needed to bootstrap and run the tests on remote
 * databases. This is abstract implementation that is database agnostic. Each
 * supported database server have it's own concrete implementation that fills
 * the gaps in database differences.
 */
abstract public class DatabaseProvider {

    private static final Logger LOG = Logger.getLogger(DatabaseProvider.class);

    /**
     * Internal connection to the database.
     */
    private Connection databaseConnection;

    /**
     * JDBC Url to the remote database system.
     *
     * This will be passed to the Sqoop2 server during tests.
     *
     * @return String
     */
    abstract public String getConnectionUrl();

    /**
     * Connection username.
     *
     * This will be passed to the Sqoop2 server during tests.
     *
     * @return String
     */
    abstract public String getConnectionUsername();

    /**
     * Connection password.
     *
     * This will be passed to the Sqoop2 server during tests.
     *
     * @return String
     */
    abstract public String getConnectionPassword();

    /**
     * Escape column name based on specific database requirements.
     *
     * @param columnName Column name
     * @return Escaped column name
     */
    abstract public String escapeColumnName(String columnName);

    /**
     * Escape table name based on specific database requirements.
     *
     * @param tableName Table name
     * @return Escaped table name
     */
    abstract public String escapeTableName(String tableName);

    /**
     * Escape schema name based on specific database requirements.
     *
     * @param schemaName Schema name
     * @return Escaped schemaname
     */
    public String escapeSchemaName(String schemaName) {
        if (!isSupportingScheme()) {
            throw new UnsupportedOperationException("Schema is not supported in this database");
        }

        return schemaName;
    }

    /**
     * Escape string value that can be safely used in the queries.
     *
     * @param value String value
     * @return Escaped string value
     */
    abstract public String escapeValueString(String value);

    /**
     * String constant that can be used to denote null (unknown) value.
     *
     * @return String encoding null value
     */
    public String nullConstant() {
        return "NULL";
    }

    /**
     * True if the underlying database supports custom schemes (namespaces).
     *
     * @return
     */
    public boolean isSupportingScheme() {
        return false;
    }

    /**
     * JDBC Driver class name.
     *
     * Fully qualified name of the driver class, so that Class.forName() or
     * similar facility can be used.
     *
     * @return
     */
    public String getJdbcDriver() {
        return null;
    }

    /**
     * Return type overview for this database.
     *
     * This method must work even in case that the provider hasn't been started.
     *
     * @return
     */
    public DatabaseTypeList getDatabaseTypes() {
        return new EmptyTypeList();
    }

    /**
     * Start the handler.
     */
    public void start() {
        if (getJdbcDriver() != null) {
            loadClass(getJdbcDriver());
        }

        // Create connection to the database server
        try {
            setConnection(DriverManager.getConnection(getConnectionUrl(), getConnectionUsername(),
                    getConnectionPassword()));
        } catch (SQLException e) {
            LOG.error("Can't create connection", e);
            throw new RuntimeException("Can't create connection", e);
        }
    }

    /**
     * Stop the handler.
     */
    public void stop() {
        // Close connection to the database server
        if (databaseConnection != null) {
            try {
                databaseConnection.close();
            } catch (SQLException e) {
                LOG.info("Ignored exception on closing connection", e);
            }
        }
    }

    /**
     * Return connection to the database.
     *
     * @return
     */
    public Connection getConnection() {
        return databaseConnection;
    }

    /**
     * Set connection to a new object.
     *
     * @param connection New connection object
     */
    protected void setConnection(Connection connection) {
        databaseConnection = connection;
    }

    /**
     * Execute DDL or DML query.
     *
     * This method will throw RuntimeException on failure.
     *
     * @param query DDL or DML query.
     */
    public void executeUpdate(String query) {
        LOG.info("Executing query: " + query);
        Statement stmt = null;

        try {
            stmt = databaseConnection.createStatement();
            stmt.executeUpdate(query);
        } catch (SQLException e) {
            LOG.error("Error in executing query", e);
            throw new RuntimeException("Error in executing query", e);
        } finally {
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException e) {
                LOG.info("Cant' close statement", e);
            }
        }
    }

    /**
     * Execute given query in a new statement object and return corresponding
     * result set. Caller is responsible for closing both ResultSet and Statement
     * object!
     *
     * @param query Query to execute
     * @return Generated ResultSet
     */
    public ResultSet executeQuery(String query) {
        LOG.info("Executing query: " + query);
        Statement stmt = null;

        try {
            stmt = databaseConnection.createStatement();
            return stmt.executeQuery(query);
        } catch (SQLException e) {
            LOG.error("Error in executing query", e);
            throw new RuntimeException("Error in executing query", e);
        }
    }

    /**
     * Execute given insert query in a new statement object and return
     * generated IDs.
     *
     * @param query Query to execute
     * @return Generated ID.
     */
    public Long executeInsertQuery(String query, Object... args) {
        LOG.info("Executing query: " + query);
        ResultSet rs = null;

        try {
            PreparedStatement stmt = databaseConnection.prepareStatement(query,
                    PreparedStatement.RETURN_GENERATED_KEYS);
            for (int i = 0; i < args.length; ++i) {
                if (args[i] instanceof String) {
                    stmt.setString(i + 1, (String) args[i]);
                } else if (args[i] instanceof Long) {
                    stmt.setLong(i + 1, (Long) args[i]);
                } else if (args[i] instanceof Boolean) {
                    stmt.setBoolean(i + 1, (Boolean) args[i]);
                } else {
                    stmt.setObject(i + 1, args[i]);
                }
            }

            stmt.execute();
            rs = stmt.getGeneratedKeys();
            if (rs.next()) {
                return rs.getLong(1);
            }
        } catch (SQLException e) {
            LOG.error("Error in executing query", e);
            throw new RuntimeException("Error in executing query", e);
        } finally {
            closeResultSetWithStatement(rs);
        }

        return -1L;
    }

    /**
     * Create new table.
     *
     * @param name Table name
     * @param primaryKey Primary key column(0) or null if table should not have any
     * @param columns List of double values column name and value for example ... "id", "varchar(50)"...
     */
    public void createTable(TableName name, String primaryKey, String... columns) {
        // Columns are in form of two strings - name and type
        if (columns.length == 0 || columns.length % 2 != 0) {
            throw new RuntimeException("Incorrect number of parameters.");
        }

        // Drop the table in case that it already exists
        dropTable(name);

        StringBuilder sb = new StringBuilder("CREATE TABLE ");
        sb.append(getTableFragment(name)).append("(");

        // Column list
        List<String> columnList = new LinkedList<String>();
        for (int i = 0; i < columns.length; i += 2) {
            String column = escapeColumnName(columns[i]) + " " + columns[i + 1];
            columnList.add(column);
        }
        sb.append(StringUtils.join(columnList, ", "));

        if (primaryKey != null) {
            sb.append(", PRIMARY KEY(").append(escapeColumnName(primaryKey)).append(")");
        }

        sb.append(")");

        executeUpdate(sb.toString());
    }

    /**
     * Insert new row into the table.
     *
     * @param tableName Table name
     * @param values List of objects that should be inserted
     */
    public void insertRow(TableName tableName, Object... values) {
        insertRow(tableName, true, values);
    }

    /**
     * Insert new row into the table.
     *
     * @param tableName Table name
     * @param escapeValues Should the values be escaped based on their type or not
     * @param values List of objects that should be inserted
     */
    public void insertRow(TableName tableName, boolean escapeValues, Object... values) {
        StringBuilder sb = new StringBuilder("INSERT INTO ");
        sb.append(getTableFragment(tableName));
        sb.append(" VALUES (");

        List<String> valueList = new LinkedList<String>();
        for (Object value : values) {
            valueList.add(escapeValues ? convertObjectToQueryString(value) : value.toString());
        }

        sb.append(StringUtils.join(valueList, ", "));
        sb.append(")");

        executeUpdate(sb.toString());
    }

    /**
     * Return rows that match given conditions.
     *
     * @param tableName Table name
     * @param conditions Conditions in form of double values - column name and value, for example: "id", 1 or "last_update_date", null
     * @return ResultSet with given criteria
     */
    public ResultSet getRows(TableName tableName, Object[] conditions) {
        return getRows(tableName, true, conditions);
    }

    /**
     * Return rows that match given conditions.
     *
     * @param tableName Table name
     * @param escapeValues Should the values be escaped based on their type or not
     * @param conditions Conditions in form of double values - column name and value, for example: "id", 1 or "last_update_date", null
     * @return ResultSet with given criteria
     */
    public ResultSet getRows(TableName tableName, boolean escapeValues, Object[] conditions) {
        // Columns are in form of two strings - name and value
        if (conditions.length % 2 != 0) {
            throw new RuntimeException("Incorrect number of parameters.");
        }

        StringBuilder sb = new StringBuilder("SELECT * FROM ");
        sb.append(getTableFragment(tableName));

        List<String> conditionList = new LinkedList<String>();
        for (int i = 0; i < conditions.length; i += 2) {
            Object columnName = conditions[i];
            Object value = conditions[i + 1];

            if (!(columnName instanceof String)) {
                throw new RuntimeException("Each odd item should be a string with column name.");
            }

            if (value == null) {
                conditionList.add(escapeColumnName((String) columnName) + " IS NULL");
            } else {
                conditionList.add(escapeColumnName((String) columnName) + " = "
                        + (escapeValues ? convertObjectToQueryString(value) : value));
            }
        }

        if (conditionList.size() != 0) {
            sb.append(" WHERE ").append(StringUtils.join(conditionList, " AND "));
        }

        return executeQuery(sb.toString());
    }

    /**
     * Convert given object to it's representation that can be safely used inside
     * query.
     *
     * @param value Value to convert
     * @return Query safe string representation
     */
    public String convertObjectToQueryString(Object value) {
        if (value == null) {
            return nullConstant();
        } else if (value.getClass() == String.class) {
            return escapeValueString((String) value);
        } else {
            return value.toString();
        }
    }

    /**
     * Drop table.
     *
     * Any exceptions will be ignored.
     *
     * @param tableName
     */
    public void dropTable(TableName tableName) {
        StringBuilder sb = new StringBuilder("DROP TABLE ");
        sb.append(getTableFragment(tableName));

        try {
            executeUpdate(sb.toString());
        } catch (RuntimeException e) {
            LOG.info("Ignoring exception: " + e);
        }
    }

    /**
     * Drop schema.
     *
     * Any exceptions will be ignored.
     *
     * @param schemaName
     */
    public void dropSchema(String schemaName) {
        StringBuilder sb = new StringBuilder("DROP SCHEMA ");
        sb.append(escapeSchemaName(schemaName));
        sb.append(" CASCADE");

        try {
            executeUpdate(sb.toString());
        } catch (RuntimeException e) {
            LOG.info("Ignoring exception: " + e);
        }
    }

    /**
     * Return number of rows from given table.
     *
     * @param tableName Table name
     * @return Number of rows
     */
    public long rowCount(TableName tableName) {
        StringBuilder sb = new StringBuilder("SELECT COUNT(*) FROM ");
        sb.append(getTableFragment(tableName));

        ResultSet rs = null;
        try {
            rs = executeQuery(sb.toString());
            if (!rs.next()) {
                throw new RuntimeException("Row count query did not returned any rows.");
            }

            return rs.getLong(1);
        } catch (SQLException e) {
            LOG.error("Can't get number of rows: ", e);
            throw new RuntimeException("Can't get number of rows: ", e);
        } finally {
            closeResultSetWithStatement(rs);
        }
    }

    /**
     * Close given result set (if not null) and associated statement.
     *
     * @param rs ResultSet to close.
     */
    public void closeResultSetWithStatement(ResultSet rs) {
        if (rs != null) {
            try {
                Statement stmt = rs.getStatement();
                rs.close();
                stmt.close();
            } catch (SQLException e) {
                LOG.info("Ignoring exception: ", e);
            }
        }
    }

    /**
     * Load class.
     *
     * @param className Class name
     */
    public void loadClass(String className) {
        try {
            Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class not found: " + className, e);
        }
    }

    /**
     * Dump content of given table to log.
     *
     * @param tableName Name of the table
     */
    public void dumpTable(TableName tableName) {
        String query = "SELECT * FROM " + getTableFragment(tableName);
        List<String> list = new LinkedList<String>();
        ResultSet rs = null;

        try {
            rs = executeQuery(query);

            // Header with column names
            ResultSetMetaData md = rs.getMetaData();
            for (int i = 0; i < md.getColumnCount(); i++) {
                list.add(md.getColumnName(i + 1));
            }
            LOG.info("Dumping table " + tableName);
            LOG.info("|" + StringUtils.join(list, "|") + "|");

            // Table rows
            while (rs.next()) {
                list.clear();
                for (int i = 0; i < md.getColumnCount(); i++) {
                    list.add(rs.getObject(i + 1).toString());
                }
                LOG.info("|" + StringUtils.join(list, "|") + "|");
            }

        } catch (SQLException e) {
            LOG.info("Ignoring exception: ", e);
        } finally {
            closeResultSetWithStatement(rs);
        }
    }

    /**
     * Generated properly escaped table name fragment that can be used
     * in SQL query.
     *
     * @param tableName Full table name
     * @return
     */
    public String getTableFragment(TableName tableName) {
        if (tableName.getSchemaName() == null) {
            return escapeTableName(tableName.getTableName());
        }

        return escapeSchemaName(tableName.getSchemaName()) + "." + escapeTableName(tableName.getTableName());
    }
}