All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xerial.db.sql.DatabaseAccessBase Maven / Gradle / Ivy

The newest version!
/*--------------------------------------------------------------------------
 *  Copyright 2007 Taro L. Saito
 *
 *  Licensed 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.
 *--------------------------------------------------------------------------*/
//--------------------------------------
// xerial-storage Project
//
// DatabaseAccessBase.java
// Since: 2007/02/25
//
// $URL$ 
// $Author$
//--------------------------------------
package org.xerial.db.sql;

import java.io.IOException;
import java.io.Writer;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.xerial.core.XerialException;
import org.xerial.db.DBErrorCode;
import org.xerial.db.DBException;
import org.xerial.db.Relation;
import org.xerial.db.datatype.DataType;
import org.xerial.json.JSONObject;
import org.xerial.json.JSONUtil;
import org.xerial.json.JSONValue;
import org.xerial.lens.Lens;
import org.xerial.util.Predicate;
import org.xerial.util.StringUtil;
import org.xerial.util.bean.BeanHandler;
import org.xerial.util.bean.BeanUtil;
import org.xerial.util.log.Logger;

/**
 * A base implementation of the {@link DatabaseAccess} interface.
 * 
 * @author leo
 * 
 */
public class DatabaseAccessBase implements DatabaseAccess
{

    private ConnectionPool            _connectionPool;
    private static Logger             _logger              = Logger.getLogger(DatabaseAccessBase.class);

    private int                       queryTimeout         = 60;
    private boolean                   autoCommit           = true;

    private HashMap tableRelationCatalog = new HashMap();

    public DatabaseAccessBase(ConnectionPool connectionPool) throws DBException {
        _connectionPool = connectionPool;

        // validate connection
        Connection con = _connectionPool.getConnection();
        _connectionPool.returnConnection(con);
    }

    public void dispose() throws DBException {
        _connectionPool.closeAll();
    }

    protected Statement createStatement(Connection connection) throws SQLException {
        Statement statement = connection.createStatement();
        statement.setQueryTimeout(queryTimeout);
        return statement;
    }

    private static class BeanCollector implements BeanResultHandler
    {
        ArrayList result = new ArrayList();

        public void handle(T bean) throws Exception {
            if (bean != null)
                result.add(bean);
        }

        public void finish() {

        }

        public void init() {

        }

        public void handleException(Exception e) throws Exception {
            throw e;
        }
    }

    private static class BeanCollectorWithPredicate extends BeanCollector
    {
        private final Predicate pred;

        public BeanCollectorWithPredicate(Predicate pred) {
            this.pred = pred;
        }

        @Override
        public void handle(T bean) throws Exception {
            if (bean != null && pred.apply(bean))
                result.add(bean);
        }

        @Override
        public void finish() {

        }

        @Override
        public void init() {

        }
    }

    private static class Redirector implements BeanHandler
    {
        final BeanResultHandler handler;

        public Redirector(BeanResultHandler handler) {
            this.handler = handler;
        }

        public void handle(T bean) throws Exception {
            handler.handle(bean);
        }

        public void handleException(Exception e) throws Exception {
            handler.handleException(e);
        }

    }

    /**
     * Handle each row of the given SQL query result using the specified handler
     * 
     * @param 
     * @param sql
     * @param handler
     * @return
     * @throws DBException
     */
    public  void query(String sql, ResultSetHandler pullHandler) throws DBException {
        Connection connection = null;
        try {
            connection = getConnection(true);
            Statement statement = createStatement(connection);
            _logger.debug(sql);

            pullHandler.init();

            ResultSet rs = statement.executeQuery(sql);
            while (rs.next()) {
                pullHandler.handle(rs);
            }
            rs.close();
            statement.close();
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.QueryError, e);
        }
        finally {

            if (connection != null)
                _connectionPool.returnConnection(connection);

            pullHandler.finish();
        }
    }

    /**
     * perform an SQL query, then convert its result into a list of objects of
     * the specified type
     * 
     * @param 
     *            row type : Bean class type
     * @param sql
     *            sql statement
     * @param resultRowType
     *            it must be equal to the T
     * @param result
     * @throws DBException
     */
    public  List query(String sql, Class resultRowType) throws DBException {
        BeanCollector beanCollector = new BeanCollector();
        query(sql, resultRowType, beanCollector);
        return beanCollector.result;
    }

    public  List query(String sql, Class resultRowType, Predicate filter) throws DBException {
        BeanCollectorWithPredicate beanCollectorWithPredicate = new BeanCollectorWithPredicate(filter);
        query(sql, resultRowType, beanCollectorWithPredicate);
        return beanCollectorWithPredicate.result;
    }

    public  void query(String sql, Class resultRowType, BeanResultHandler beanResultHandler)
            throws DBException {

        Redirector r = new Redirector(beanResultHandler);
        Connection connection = null;
        ResultSet rs = null;
        Statement stat = null;
        try {
            try {
                connection = getConnection(true);
                stat = createStatement(connection);
                _logger.debug(sql);

                rs = stat.executeQuery(sql);
                r.handler.init();
                Lens.loadJDBCResultSet(resultRowType, rs, r);
                r.handler.finish();
            }
            catch (Exception e) {
                throw new DBException(DBErrorCode.QueryError, e);
            }
            finally {
                if (rs != null)
                    rs.close();
                if (stat != null)
                    stat.close();
                if (connection != null)
                    _connectionPool.returnConnection(connection);
            }
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.QueryError, e);
        }

    }

    /**
     * Accumulate the query result within the ResultSetHandler, then return the
     * result from the handler
     * 
     * @param 
     * @param sql
     * @param handler
     * @return
     * @throws DBException
     */
    public  T accumulate(String sql, ResultSetHandler handler) throws DBException {
        Connection connection = null;
        T result = null;
        try {
            connection = getConnection(true);
            Statement statement = createStatement(connection);
            _logger.debug(sql);

            handler.init();

            ResultSet rs = statement.executeQuery(sql);

            while (rs.next()) {
                result = handler.handle(rs);
            }

            rs.close();
            statement.close();

        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.QueryError, e);
        }
        finally {
            if (connection != null)
                _connectionPool.returnConnection(connection);

            handler.finish();
        }
        return result;
    }

    private PreparedStatement getPreparedStatement(Connection connection, String sql) throws SQLException {
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setQueryTimeout(queryTimeout);
        return preparedStatement;
    }

    public int updateWithPreparedStatement(String sqlForPreparedStatement, PreparedStatementHandler handler)
            throws DBException {
        Connection connection = null;
        try {
            connection = getConnection(false);
            _logger.debug(sqlForPreparedStatement);
            PreparedStatement preparedStatement = getPreparedStatement(connection, sqlForPreparedStatement);
            handler.setup(preparedStatement);
            int ret = preparedStatement.executeUpdate();

            preparedStatement.close();

            return ret;
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.UpdateError, e);
        }
        finally {
            if (connection != null) {
                _connectionPool.returnConnection(connection);
            }
        }

    }

    public int update(String sql) throws DBException {
        return update(sql, autoCommit);
    }

    public int update(String sql, boolean autoCommit) throws DBException {
        Connection connection = null;
        try {
            connection = getConnection(false);
            connection.setAutoCommit(autoCommit);
            Statement statement = createStatement(connection);

            _logger.debug(sql);
            int ret = statement.executeUpdate(sql);
            statement.close();
            return ret;
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.UpdateError, e);
        }
        finally {
            if (connection != null) {
                _connectionPool.returnConnection(connection);
            }
        }

    }

    public  int insertAndRetrieveKeys(String sql) throws DBException {
        Connection connection = null;
        try {
            connection = getConnection(false);
            connection.setAutoCommit(autoCommit);
            Statement statement = createStatement(connection);

            _logger.debug(sql);
            int ret = statement.executeUpdate(sql);

            ResultSet rs = statement.getGeneratedKeys();
            int id = (rs == null) ? -1 : rs.getInt(1);
            rs.close();
            statement.close();
            return id;
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.UpdateError, e);
        }
        finally {
            if (connection != null) {
                _connectionPool.returnConnection(connection);
            }
        }
    }

    public ConnectionPool getConnectionPool() {
        return _connectionPool;
    }

    private Connection getConnection(boolean readOnly) throws DBException, SQLException {
        Connection conn = _connectionPool.getConnection();
        conn.setAutoCommit(autoCommit);
        // conn.setReadOnly(readOnly);
        return conn;
    }

    public void setAutoCommit(boolean enableAutoCommit) {
        autoCommit = enableAutoCommit;
    }

    public void setQueryTimeout(int sec) {
        this.queryTimeout = sec;
    }

    public Set getPrimaryKeyColumns(String tableName) throws DBException {

        Connection connection = null;

        try {
            connection = getConnection(true);
            DatabaseMetaData metadata = connection.getMetaData();
            return getPrimaryKeyColumns(metadata, tableName);
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.QueryError, e);
        }
        finally {
            if (connection != null)
                _connectionPool.returnConnection(connection);
        }

    }

    public Set getPrimaryKeyColumns(DatabaseMetaData metadata, String tableName) throws SQLException {
        HashSet primaryKeyColumnSet = new HashSet();
        // retrieve primary key information
        ResultSet primaryKeyResult = metadata.getPrimaryKeys(null, null, tableName);
        for (; primaryKeyResult.next();) {
            String columnName = primaryKeyResult.getString("COLUMN_NAME");
            primaryKeyColumnSet.add(columnName);
        }
        primaryKeyResult.close();

        return primaryKeyColumnSet;
    }

    public Relation getRelation(String tableName) throws DBException {
        if (tableRelationCatalog.containsKey(tableName))
            return tableRelationCatalog.get(tableName);

        Relation relation = new Relation();

        Connection connection = null;
        try {
            connection = getConnection(true);
            DatabaseMetaData metadata = connection.getMetaData();

            Set primaryKeyColumnSet = getPrimaryKeyColumns(metadata, tableName);

            int column = 1;
            ResultSet resultSet = metadata.getColumns(null, null, tableName, null);
            for (; resultSet.next();) {
                String columnName = resultSet.getString("COLUMN_NAME");
                String typeName = resultSet.getString("TYPE_NAME");
                DataType dt = Relation.getDataType(columnName, typeName);

                ResultSetMetaData rmeta = resultSet.getMetaData();

                assert (dt != null);
                dt.setNullable(resultSet.getInt("NULLABLE") == ResultSetMetaData.columnNullable);
                dt.setPrimaryKey(primaryKeyColumnSet.contains(columnName));

                relation.add(dt);

                column++;
            }
            resultSet.close();
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.QueryError, e);
        }

        finally {
            if (connection != null)
                _connectionPool.returnConnection(connection);
        }

        tableRelationCatalog.put(tableName, relation);
        return relation;
    }

    public List getTableNameList() throws DBException {
        ArrayList tableNameList = new ArrayList();

        Connection connection = null;
        try {
            connection = getConnection(true);
            DatabaseMetaData metadata = connection.getMetaData();
            for (ResultSet resultSet = metadata.getTables(null, null, "%", new String[] { "TABLE", "VIEW" }); resultSet
                    .next();) {
                tableNameList.add(resultSet.getString("TABLE_NAME").toLowerCase());
            }
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.QueryError, e);
        }
        finally {
            if (connection != null)
                _connectionPool.returnConnection(connection);
        }

        return tableNameList;
    }

    public  List singleColumnQuery(String sql, String targetColumn, Class resultColumnType) throws DBException {
        return queryWithHandler(sql, new ColumnReader(targetColumn));
    }

    protected  List queryWithHandler(String sql, ResultSetHandler handler) throws DBException {
        Connection connection = null;
        ArrayList result = new ArrayList();
        try {
            connection = getConnection(true);
            Statement statement = createStatement(connection);
            _logger.debug(sql);

            handler.init();

            ResultSet rs = statement.executeQuery(sql);
            while (rs.next()) {
                T row = handler.handle(rs);
                if (row != null)
                    result.add(row);
                else
                    _logger.warn("null handler result is returned");
            }

            rs.close();
            statement.close();
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.QueryError, e);
        }
        finally {
            if (connection != null)
                _connectionPool.returnConnection(connection);

            handler.finish();
        }
        return result;
    }

    public  int insert(String tableName, T bean) throws DBException {
        String sql;
        try {
            sql = SQLExpression.fillTemplate("insert into $1 values($2)", tableName,
                    createValueTupleFromBean(tableName, bean));
            return update(sql);
        }
        catch (XerialException e) {
            throw new DBException(DBErrorCode.InvalidBeanClass, e);
        }
    }

    /**
     * Align the content of a bean object so that it matches with the
     * corresponding relation (table schema)
     * 
     * For example, give a bean class, e.g.
     * class Person { int id; String name; (getters are ommited) } 
     * and a table named person with a schema 'id, name', the
     * createValueTupleFromBean("person", (a Person object)) will give a tuple
     * representation of the Person object (id=1, name="leo"), that is
     * '1,"leo"'.
     * 
     * This returned string can be used as it is within an insert statement of
     * the SQL, i.e., insert into person values(1, "leo")
     * 
     * @param tableName
     * @param bean
     * @return
     * @throws DBException
     * @throws InvalidBeanException
     */
    protected String createValueTupleFromBean(String tableName, Object bean) throws DBException, XerialException {
        Relation r = getRelation(tableName);

        JSONObject json = new JSONObject(JSONUtil.toJSON(bean));

        ArrayList tupleValue = new ArrayList();
        for (DataType dt : r.getDataTypeList()) {
            JSONValue jsonValue = json.get(dt.getName());
            String value = (jsonValue == null) ? "" : jsonValue.toJSONString();
            tupleValue.add(value);
        }

        return StringUtil.join(tupleValue, ",");
    }

    public  void toJSON(String sql, Class beanClass, Writer writer) throws DBException, IOException {
        writer.append("{");
        writer.append(StringUtil.quote(beanClass.getSimpleName().toLowerCase(), StringUtil.DOUBLE_QUOTE));
        writer.append(":[\n");
        Connection connection = null;
        try {
            connection = getConnection(true);
            Statement statement = createStatement(connection);
            _logger.debug(sql);

            int rowCount = 0;
            ResultSet rs = statement.executeQuery(sql);
            while (rs.next()) {
                if (rowCount > 0)
                    writer.append(",\n");

                writer.append(BeanUtil.toJSONFromResultSet(rs));

                rowCount++;
            }
            rs.close();
            statement.close();
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.QueryError, e);
        }
        finally {
            if (connection != null)
                _connectionPool.returnConnection(connection);
            writer.append("]}");
        }

    }

    public boolean hasTable(String tableName) throws DBException {
        return getTableNameList().contains(tableName);
    }

    public boolean isAutoCommit() {
        return autoCommit;
    }

    public int insertAndRetrieveKeysWithPreparedStatement(String sqlForPreparedStatment,
            PreparedStatementHandler handler) throws DBException {
        Connection connection = null;
        try {
            connection = getConnection(false);
            connection.setAutoCommit(autoCommit);
            PreparedStatement preparedStatement = getPreparedStatement(connection, sqlForPreparedStatment);
            handler.setup(preparedStatement);
            int ret = preparedStatement.executeUpdate();

            ResultSet rs = preparedStatement.getGeneratedKeys();
            int id = -1;
            if (rs.next()) {
                id = rs.getInt(1);
            }
            rs.close();
            preparedStatement.close();
            return id;
        }
        catch (SQLException e) {
            throw new DBException(DBErrorCode.UpdateError, e);
        }
        finally {
            if (connection != null) {
                _connectionPool.returnConnection(connection);
            }
        }
    }

    public String createTableSQL(String tableName, Relation r) {
        LinkedList columnDefList = new LinkedList();
        for (DataType dt : r.getDataTypeList()) {
            StringBuilder columnDef = new StringBuilder();
            columnDef.append(String.format("%s %s", dt.getName(), dt.getTypeName()));

            if (dt.getName().equals("id")) {
                columnDef.append(" primary key autoincrement not null");
                // id attribute must be the first column
                columnDefList.addFirst(columnDef.toString());
            }
            else
                columnDefList.add(columnDef.toString());
        }

        String schema = StringUtil.join(columnDefList, ", ");
        String sql = String.format("create table if not exists %s (%s)", tableName, schema);
        return sql;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy