org.apache.tajo.catalog.store.DBStore.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.tajo.catalog.store.DBStore.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.tajo.catalog.store;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.tajo.catalog.*;
import org.apache.tajo.catalog.proto.CatalogProtos.*;
import org.apache.tajo.catalog.statistics.TableStat;
import org.apache.tajo.common.TajoDataTypes.Type;
import org.apache.tajo.conf.TajoConf;
import org.apache.tajo.exception.InternalException;

import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class DBStore implements CatalogStore {
    private final Log LOG = LogFactory.getLog(DBStore.class);
    private final Configuration conf;
    private final String driver;
    private final String jdbcUri;
    private Connection conn;

    private static final int VERSION = 1;

    private static final String TB_META = "META";
    private static final String TB_TABLES = "TABLES";
    private static final String TB_COLUMNS = "COLUMNS";
    private static final String TB_OPTIONS = "OPTIONS";
    private static final String TB_INDEXES = "INDEXES";
    private static final String TB_STATISTICS = "STATS";

    private static final String C_TABLE_ID = "TABLE_ID";

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private Lock rlock = lock.readLock();
    private Lock wlock = lock.writeLock();

    public DBStore(final Configuration conf) throws InternalException {
        this.conf = conf;

        this.driver = this.conf.get(CatalogConstants.JDBC_DRIVER, CatalogConstants.DEFAULT_JDBC_DRIVER);
        this.jdbcUri = this.conf.get(CatalogConstants.JDBC_URI);

        try {
            Class.forName(driver).newInstance();
            LOG.info("Loaded the JDBC driver (" + driver + ")");
        } catch (Exception e) {
            throw new InternalException("Cannot load JDBC driver " + driver, e);
        }

        try {
            LOG.info("Trying to connect database (" + jdbcUri + ")");
            conn = DriverManager.getConnection(jdbcUri + ";create=true");
            LOG.info("Connected to database (" + jdbcUri + ")");
        } catch (SQLException e) {
            throw new InternalException("Cannot connect to database (" + jdbcUri + ")", e);
        }

        try {
            if (!isInitialized()) {
                LOG.info("The base tables of CatalogServer are created.");
                createBaseTable();
            } else {
                LOG.info("The base tables of CatalogServer already is initialized.");
            }
        } catch (SQLException se) {
            throw new InternalException("Cannot initialize the persistent storage of Catalog", se);
        }

        int dbVersion = 0;
        try {
            dbVersion = needUpgrade();
        } catch (SQLException e) {
            throw new InternalException("Cannot check if the DB need to be upgraded", e);
        }

        //    if (dbVersion < VERSION) {
        //      LOG.info("DB Upgrade is needed");
        //      try {
        //        upgrade(dbVersion, VERSION);
        //      } catch (SQLException e) {
        //        LOG.error(e.getMessage());
        //        throw new InternalException("DB upgrade is failed.", e);
        //      }
        //    }
    }

    private int needUpgrade() throws SQLException {
        String sql = "SELECT VERSION FROM " + TB_META;
        Statement stmt = conn.createStatement();
        ResultSet res = stmt.executeQuery(sql);

        if (res.next() == false) { // if this db version is 0
            insertVersion();
            return 0;
        } else {
            return res.getInt(1);
        }
    }

    private void insertVersion() throws SQLException {
        String sql = "INSERT INTO " + TB_META + " values (0)";
        Statement stmt = conn.createStatement();
        stmt.executeUpdate(sql);
        stmt.close();
    }

    private void upgrade(int from, int to) throws SQLException {
        String sql;
        Statement stmt;
        if (from == 0) {
            if (to == 1) {
                sql = "DROP INDEX idx_options_key";
                LOG.info(sql);

                stmt = conn.createStatement();
                stmt.addBatch(sql);

                sql = "CREATE INDEX idx_options_key on " + TB_OPTIONS + " (" + C_TABLE_ID + ")";
                stmt.addBatch(sql);
                LOG.info(sql);
                stmt.executeBatch();
                stmt.close();

                LOG.info("DB Upgraded from " + from + " to " + to);
            } else {
                LOG.info("DB Upgraded from " + from + " to " + to);
            }
        }
    }

    // TODO - DDL and index statements should be renamed
    private void createBaseTable() throws SQLException {
        wlock.lock();
        try {
            // META
            Statement stmt = conn.createStatement();
            String meta_ddl = "CREATE TABLE " + TB_META + " (version int NOT NULL)";
            if (LOG.isDebugEnabled()) {
                LOG.debug(meta_ddl);
            }
            stmt.executeUpdate(meta_ddl);
            LOG.info("Table '" + TB_META + " is created.");

            // TABLES
            stmt = conn.createStatement();
            String tables_ddl = "CREATE TABLE " + TB_TABLES + " ("
                    + "TID int NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1), " + C_TABLE_ID
                    + " VARCHAR(256) NOT NULL CONSTRAINT TABLE_ID_UNIQ UNIQUE, " + "path VARCHAR(1024), "
                    + "store_type CHAR(16), " + "options VARCHAR(32672), "
                    + "CONSTRAINT TABLES_PK PRIMARY KEY (TID)" + ")";
            if (LOG.isDebugEnabled()) {
                LOG.debug(tables_ddl);
            }
            stmt.addBatch(tables_ddl);
            String idx_tables_tid = "CREATE UNIQUE INDEX idx_tables_tid on " + TB_TABLES + " (TID)";
            if (LOG.isDebugEnabled()) {
                LOG.debug(idx_tables_tid);
            }
            stmt.addBatch(idx_tables_tid);

            String idx_tables_name = "CREATE UNIQUE INDEX idx_tables_name on " + TB_TABLES + "(" + C_TABLE_ID + ")";
            if (LOG.isDebugEnabled()) {
                LOG.debug(idx_tables_name);
            }
            stmt.addBatch(idx_tables_name);
            stmt.executeBatch();
            LOG.info("Table '" + TB_TABLES + "' is created.");

            // COLUMNS
            stmt = conn.createStatement();
            String columns_ddl = "CREATE TABLE " + TB_COLUMNS + " (" + "TID INT NOT NULL REFERENCES " + TB_TABLES
                    + " (TID) ON DELETE CASCADE, " + C_TABLE_ID + " VARCHAR(256) NOT NULL REFERENCES " + TB_TABLES
                    + "(" + C_TABLE_ID + ") ON DELETE CASCADE, " + "column_id INT NOT NULL,"
                    + "column_name VARCHAR(256) NOT NULL, " + "data_type CHAR(16), "
                    + "CONSTRAINT C_COLUMN_ID UNIQUE (" + C_TABLE_ID + ", column_name))";
            if (LOG.isDebugEnabled()) {
                LOG.debug(columns_ddl);
            }
            stmt.addBatch(columns_ddl);

            String idx_fk_columns_table_name = "CREATE UNIQUE INDEX idx_fk_columns_table_name on " + TB_COLUMNS
                    + "(" + C_TABLE_ID + ", column_name)";
            if (LOG.isDebugEnabled()) {
                LOG.debug(idx_fk_columns_table_name);
            }
            stmt.addBatch(idx_fk_columns_table_name);
            stmt.executeBatch();
            LOG.info("Table '" + TB_COLUMNS + " is created.");

            // OPTIONS
            stmt = conn.createStatement();
            String options_ddl = "CREATE TABLE " + TB_OPTIONS + " (" + C_TABLE_ID
                    + " VARCHAR(256) NOT NULL REFERENCES TABLES (" + C_TABLE_ID + ") " + "ON DELETE CASCADE, "
                    + "key_ VARCHAR(256) NOT NULL, value_ VARCHAR(256) NOT NULL)";
            if (LOG.isDebugEnabled()) {
                LOG.debug(options_ddl);
            }
            stmt.addBatch(options_ddl);

            String idx_options_key = "CREATE INDEX idx_options_key on " + TB_OPTIONS + " (" + C_TABLE_ID + ")";
            if (LOG.isDebugEnabled()) {
                LOG.debug(idx_options_key);
            }
            stmt.addBatch(idx_options_key);
            String idx_options_table_name = "CREATE INDEX idx_options_table_name on " + TB_OPTIONS + "("
                    + C_TABLE_ID + ")";
            if (LOG.isDebugEnabled()) {
                LOG.debug(idx_options_table_name);
            }
            stmt.addBatch(idx_options_table_name);
            stmt.executeBatch();
            LOG.info("Table '" + TB_OPTIONS + " is created.");

            // INDEXES
            stmt = conn.createStatement();
            String indexes_ddl = "CREATE TABLE " + TB_INDEXES + "("
                    + "index_name VARCHAR(256) NOT NULL PRIMARY KEY, " + C_TABLE_ID
                    + " VARCHAR(256) NOT NULL REFERENCES TABLES (" + C_TABLE_ID + ") " + "ON DELETE CASCADE, "
                    + "column_name VARCHAR(256) NOT NULL, " + "data_type VARCHAR(256) NOT NULL, "
                    + "index_type CHAR(32) NOT NULL, " + "is_unique BOOLEAN NOT NULL, "
                    + "is_clustered BOOLEAN NOT NULL, " + "is_ascending BOOLEAN NOT NULL)";
            if (LOG.isDebugEnabled()) {
                LOG.debug(indexes_ddl);
            }
            stmt.addBatch(indexes_ddl);

            String idx_indexes_key = "CREATE UNIQUE INDEX idx_indexes_key ON " + TB_INDEXES + " (index_name)";
            if (LOG.isDebugEnabled()) {
                LOG.debug(idx_indexes_key);
            }
            stmt.addBatch(idx_indexes_key);

            String idx_indexes_columns = "CREATE INDEX idx_indexes_columns ON " + TB_INDEXES + " (" + C_TABLE_ID
                    + ", column_name)";
            if (LOG.isDebugEnabled()) {
                LOG.debug(idx_indexes_columns);
            }
            stmt.addBatch(idx_indexes_columns);
            stmt.executeBatch();
            LOG.info("Table '" + TB_INDEXES + "' is created.");

            String stats_ddl = "CREATE TABLE " + TB_STATISTICS + "(" + C_TABLE_ID
                    + " VARCHAR(256) NOT NULL REFERENCES TABLES (" + C_TABLE_ID + ") " + "ON DELETE CASCADE, "
                    + "num_rows BIGINT, " + "num_bytes BIGINT)";
            if (LOG.isDebugEnabled()) {
                LOG.debug(stats_ddl);
            }
            stmt.addBatch(stats_ddl);

            String idx_stats_fk_table_name = "CREATE INDEX idx_stats_table_name ON " + TB_STATISTICS + " ("
                    + C_TABLE_ID + ")";
            if (LOG.isDebugEnabled()) {
                LOG.debug(idx_stats_fk_table_name);
            }
            stmt.addBatch(idx_stats_fk_table_name);
            stmt.executeBatch();
            LOG.info("Table '" + TB_STATISTICS + "' is created.");

        } finally {
            wlock.unlock();
        }
    }

    private boolean isInitialized() throws SQLException {
        wlock.lock();
        try {
            boolean found = false;
            ResultSet res = conn.getMetaData().getTables(null, null, null, new String[] { "TABLE" });

            String resName;
            while (res.next() && !found) {
                resName = res.getString("TABLE_NAME");
                if (TB_META.equals(resName) || TB_TABLES.equals(resName) || TB_COLUMNS.equals(resName)
                        || TB_OPTIONS.equals(resName)) {
                    return true;
                }
            }
        } finally {
            wlock.unlock();
        }
        return false;
    }

    final boolean checkInternalTable(final String tableName) throws SQLException {
        rlock.lock();
        try {
            boolean found = false;
            ResultSet res = conn.getMetaData().getTables(null, null, null, new String[] { "TABLE" });
            while (res.next() && !found) {
                if (tableName.equals(res.getString("TABLE_NAME")))
                    found = true;
            }

            return found;
        } finally {
            rlock.unlock();
        }
    }

    @Override
    public final void addTable(final TableDesc table) throws IOException {
        Statement stmt = null;
        ResultSet res;

        String sql = "INSERT INTO " + TB_TABLES + " (" + C_TABLE_ID + ", path, store_type) " + "VALUES('"
                + table.getId() + "', " + "'" + table.getPath() + "', " + "'" + table.getMeta().getStoreType() + "'"
                + ")";

        wlock.lock();
        try {
            stmt = conn.createStatement();
            if (LOG.isDebugEnabled()) {
                LOG.debug(sql);
            }
            stmt.addBatch(sql);
            stmt.executeBatch();

            stmt = conn.createStatement();
            sql = "SELECT TID from " + TB_TABLES + " WHERE " + C_TABLE_ID + " = '" + table.getId() + "'";
            if (LOG.isDebugEnabled()) {
                LOG.debug(sql);
            }
            res = stmt.executeQuery(sql);
            if (!res.next()) {
                throw new IOException("ERROR: there is no tid matched to " + table.getId());
            }
            int tid = res.getInt("TID");

            String colSql;
            int columnId = 0;
            for (Column col : table.getMeta().getSchema().getColumns()) {
                colSql = columnToSQL(tid, table, columnId, col);
                if (LOG.isDebugEnabled()) {
                    LOG.debug(colSql);
                }
                stmt.addBatch(colSql);
                columnId++;
            }

            Iterator<Entry<String, String>> it = table.getMeta().getOptions();
            String optSql;
            while (it.hasNext()) {
                optSql = keyvalToSQL(table, it.next());
                if (LOG.isDebugEnabled()) {
                    LOG.debug(optSql);
                }
                stmt.addBatch(optSql);
            }
            stmt.executeBatch();
            if (table.getMeta().getStat() != null) {
                sql = "INSERT INTO " + TB_STATISTICS + " (" + C_TABLE_ID + ", num_rows, num_bytes) " + "VALUES ('"
                        + table.getId() + "', " + table.getMeta().getStat().getNumRows() + ","
                        + table.getMeta().getStat().getNumBytes() + ")";
                if (LOG.isDebugEnabled()) {
                    LOG.debug(sql);
                }
                stmt.addBatch(sql);
                stmt.executeBatch();
            }
        } catch (SQLException se) {
            throw new IOException(se.getMessage(), se);
        } finally {
            wlock.unlock();
            try {
                stmt.close();
            } catch (SQLException e) {
            }
        }
    }

    private String columnToSQL(final int tid, final TableDesc desc, final int columnId, final Column col) {
        String sql = "INSERT INTO " + TB_COLUMNS + " (tid, " + C_TABLE_ID + ", column_id, column_name, data_type) "
                + "VALUES(" + tid + "," + "'" + desc.getId() + "'," + columnId + ", " + "'" + col.getColumnName()
                + "'," + "'" + col.getDataType().getType().name() + "'" + ")";

        return sql;
    }

    private String keyvalToSQL(final TableDesc desc, final Entry<String, String> keyVal) {
        String sql = "INSERT INTO " + TB_OPTIONS + " (" + C_TABLE_ID + ", key_, value_) " + "VALUES(" + "'"
                + desc.getId() + "'," + "'" + keyVal.getKey() + "'," + "'" + keyVal.getValue() + "'" + ")";

        return sql;
    }

    @Override
    public final boolean existTable(final String name) throws IOException {
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT " + C_TABLE_ID + " from ").append(TB_TABLES).append(" WHERE " + C_TABLE_ID + " = '")
                .append(name).append("'");

        Statement stmt = null;
        boolean exist = false;
        rlock.lock();
        try {
            stmt = conn.createStatement();
            if (LOG.isDebugEnabled()) {
                LOG.debug(sql.toString());
            }
            ResultSet res = stmt.executeQuery(sql.toString());
            exist = res.next();
        } catch (SQLException se) {
            throw new IOException(se);
        } finally {
            rlock.unlock();
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException e) {
            }
        }

        return exist;
    }

    @Override
    public final void deleteTable(final String name) throws IOException {
        Statement stmt = null;
        String sql = null;
        try {
            wlock.lock();
            try {
                stmt = conn.createStatement();
                sql = "DELETE FROM " + TB_COLUMNS + " WHERE " + C_TABLE_ID + " = '" + name + "'";
                LOG.info(sql);
                stmt.execute(sql);
            } catch (SQLException se) {
                throw new IOException(se);
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (SQLException e) {
                }
            }

            try {
                sql = "DELETE FROM " + TB_OPTIONS + " WHERE " + C_TABLE_ID + " = '" + name + "'";
                LOG.info(sql);
                stmt = conn.createStatement();
                stmt.execute(sql);
            } catch (SQLException se) {
                throw new IOException(se);
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (SQLException e) {
                }
            }

            try {
                sql = "DELETE FROM " + TB_STATISTICS + " WHERE " + C_TABLE_ID + " = '" + name + "'";
                LOG.info(sql);
                stmt = conn.createStatement();
                stmt.execute(sql);
            } catch (SQLException se) {
                throw new IOException(se);
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (SQLException e) {
                }
            }

            try {
                sql = "DELETE FROM " + TB_TABLES + " WHERE " + C_TABLE_ID + " = '" + name + "'";
                LOG.info(sql);
                stmt = conn.createStatement();
                stmt.execute(sql);
            } catch (SQLException se) {
                throw new IOException(se);
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (SQLException e) {
                }
            }

        } finally {
            wlock.unlock();
        }
    }

    @Override
    public final TableDesc getTable(final String name) throws IOException {
        ResultSet res = null;
        Statement stmt = null;

        String tableName = null;
        Path path = null;
        StoreType storeType = null;
        Options options;
        TableStat stat = null;

        rlock.lock();
        try {
            try {
                String sql = "SELECT " + C_TABLE_ID + ", path, store_type from " + TB_TABLES + " WHERE "
                        + C_TABLE_ID + "='" + name + "'";
                if (LOG.isDebugEnabled()) {
                    LOG.debug(sql);
                }
                stmt = conn.createStatement();
                res = stmt.executeQuery(sql);
                if (!res.next()) { // there is no table of the given name.
                    return null;
                }
                tableName = res.getString(C_TABLE_ID).trim();
                path = new Path(res.getString("path").trim());
                storeType = CatalogUtil.getStoreType(res.getString("store_type").trim());
            } catch (SQLException se) {
                throw new IOException(se);
            } finally {
                stmt.close();
                res.close();
            }

            Schema schema = null;
            try {
                String sql = "SELECT column_name, data_type from " + TB_COLUMNS + " WHERE " + C_TABLE_ID + "='"
                        + name + "' ORDER by column_id asc";

                stmt = conn.createStatement();
                if (LOG.isDebugEnabled()) {
                    LOG.debug(sql);
                }
                res = stmt.executeQuery(sql);

                schema = new Schema();
                while (res.next()) {
                    String columnName = tableName + "." + res.getString("column_name").trim();
                    Type dataType = getDataType(res.getString("data_type").trim());
                    schema.addColumn(columnName, dataType);
                }
            } catch (SQLException se) {
                throw new IOException(se);
            } finally {
                stmt.close();
                res.close();
            }

            options = Options.create();
            try {
                String sql = "SELECT key_, value_ from " + TB_OPTIONS + " WHERE " + C_TABLE_ID + "='" + name + "'";
                stmt = conn.createStatement();
                if (LOG.isDebugEnabled()) {
                    LOG.debug(sql);
                }
                res = stmt.executeQuery(sql);

                while (res.next()) {
                    options.put(res.getString("key_"), res.getString("value_"));
                }
            } catch (SQLException se) {
                throw new IOException(se);
            } finally {
                stmt.close();
                res.close();
            }

            try {
                String sql = "SELECT num_rows, num_bytes from " + TB_STATISTICS + " WHERE " + C_TABLE_ID + "='"
                        + name + "'";
                if (LOG.isDebugEnabled()) {
                    LOG.debug(sql);
                }
                stmt = conn.createStatement();
                res = stmt.executeQuery(sql);

                if (res.next()) {
                    stat = new TableStat();
                    stat.setNumRows(res.getLong("num_rows"));
                    stat.setNumBytes(res.getLong("num_bytes"));
                }
            } catch (SQLException se) {
                throw new IOException(se);
            } finally {
                stmt.close();
                res.close();
            }

            TableMeta meta = new TableMetaImpl(schema, storeType, options);
            if (stat != null) {
                meta.setStat(stat);
            }
            TableDesc table = new TableDescImpl(tableName, meta, path);

            return table;
        } catch (SQLException se) {
            throw new IOException(se);
        } finally {
            rlock.unlock();
        }
    }

    private Type getDataType(final String typeStr) {
        try {
            return Enum.valueOf(Type.class, typeStr);
        } catch (IllegalArgumentException iae) {
            LOG.error("Cannot find a matched type aginst from '" + typeStr + "'");
            return null;
        }
    }

    @Override
    public final List<String> getAllTableNames() throws IOException {
        String sql = "SELECT " + C_TABLE_ID + " from " + TB_TABLES;

        Statement stmt = null;
        ResultSet res;

        List<String> tables = new ArrayList<String>();
        rlock.lock();
        try {
            stmt = conn.createStatement();
            if (LOG.isDebugEnabled()) {
                LOG.debug(sql);
            }
            res = stmt.executeQuery(sql);
            while (res.next()) {
                tables.add(res.getString(C_TABLE_ID).trim());
            }
        } catch (SQLException se) {
            throw new IOException(se);
        } finally {
            rlock.unlock();
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException e) {
            }
        }
        return tables;
    }

    public final void addIndex(final IndexDescProto proto) throws IOException {
        String sql = "INSERT INTO indexes (index_name, " + C_TABLE_ID + ", column_name, "
                + "data_type, index_type, is_unique, is_clustered, is_ascending) VALUES " + "(?,?,?,?,?,?,?,?)";

        PreparedStatement stmt = null;

        wlock.lock();
        try {
            stmt = conn.prepareStatement(sql);
            stmt.setString(1, proto.getName());
            stmt.setString(2, proto.getTableId());
            stmt.setString(3, proto.getColumn().getColumnName());
            stmt.setString(4, proto.getColumn().getDataType().getType().name());
            stmt.setString(5, proto.getIndexMethod().toString());
            stmt.setBoolean(6, proto.hasIsUnique() && proto.getIsUnique());
            stmt.setBoolean(7, proto.hasIsClustered() && proto.getIsClustered());
            stmt.setBoolean(8, proto.hasIsAscending() && proto.getIsAscending());
            stmt.executeUpdate();
            if (LOG.isDebugEnabled()) {
                LOG.debug(stmt.toString());
            }
        } catch (SQLException se) {
            throw new IOException(se);
        } finally {
            wlock.unlock();
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException e) {
            }
        }
    }

    public final void delIndex(final String indexName) throws IOException {
        String sql = "DELETE FROM " + TB_INDEXES + " WHERE index_name='" + indexName + "'";
        if (LOG.isDebugEnabled()) {
            LOG.debug(sql);
        }

        Statement stmt = null;
        wlock.lock();
        try {
            stmt = conn.createStatement();
            if (LOG.isDebugEnabled()) {
                LOG.debug(sql);
            }
            stmt.executeUpdate(sql);
        } catch (SQLException se) {
            throw new IOException(se);
        } finally {
            wlock.unlock();
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException e) {
            }
        }
    }

    public final IndexDescProto getIndex(final String indexName) throws IOException {
        ResultSet res;
        PreparedStatement stmt;

        IndexDescProto proto = null;

        rlock.lock();
        try {
            String sql = "SELECT index_name, " + C_TABLE_ID + ", column_name, data_type, "
                    + "index_type, is_unique, is_clustered, is_ascending FROM indexes " + "where index_name = ?";
            stmt = conn.prepareStatement(sql);
            stmt.setString(1, indexName);
            if (LOG.isDebugEnabled()) {
                LOG.debug(stmt.toString());
            }
            res = stmt.executeQuery();
            if (!res.next()) {
                throw new IOException("ERROR: there is no index matched to " + indexName);
            }
            proto = resultToProto(res);
        } catch (SQLException se) {
        } finally {
            rlock.unlock();
        }

        return proto;
    }

    public final IndexDescProto getIndex(final String tableName, final String columnName) throws IOException {
        ResultSet res;
        PreparedStatement stmt;

        IndexDescProto proto = null;

        rlock.lock();
        try {
            String sql = "SELECT index_name, " + C_TABLE_ID + ", column_name, data_type, "
                    + "index_type, is_unique, is_clustered, is_ascending FROM indexes " + "where " + C_TABLE_ID
                    + " = ? AND column_name = ?";
            stmt = conn.prepareStatement(sql);
            stmt.setString(1, tableName);
            stmt.setString(2, columnName);
            if (LOG.isDebugEnabled()) {
                LOG.debug(sql);
            }
            res = stmt.executeQuery();
            if (!res.next()) {
                throw new IOException("ERROR: there is no index matched to " + tableName + "." + columnName);
            }
            proto = resultToProto(res);
        } catch (SQLException se) {
            new IOException(se);
        } finally {
            rlock.unlock();
        }

        return proto;
    }

    public final boolean existIndex(final String indexName) throws IOException {
        String sql = "SELECT index_name from " + TB_INDEXES + " WHERE index_name = ?";
        if (LOG.isDebugEnabled()) {
            LOG.debug(sql);
        }

        PreparedStatement stmt = null;
        boolean exist = false;
        rlock.lock();
        try {
            stmt = conn.prepareStatement(sql);
            stmt.setString(1, indexName);
            if (LOG.isDebugEnabled()) {
                LOG.debug(sql);
            }
            ResultSet res = stmt.executeQuery();
            exist = res.next();
        } catch (SQLException se) {

        } finally {
            rlock.unlock();
            try {
                stmt.close();
            } catch (SQLException e) {
            }
        }

        return exist;
    }

    @Override
    public boolean existIndex(String tableName, String columnName) throws IOException {
        String sql = "SELECT index_name from " + TB_INDEXES + " WHERE " + C_TABLE_ID + " = ? AND COLUMN_NAME = ?";
        if (LOG.isDebugEnabled()) {
            LOG.debug(sql);
        }

        PreparedStatement stmt = null;
        boolean exist = false;
        rlock.lock();
        try {
            stmt = conn.prepareStatement(sql);
            stmt.setString(1, tableName);
            stmt.setString(2, columnName);
            if (LOG.isDebugEnabled()) {
                LOG.debug(sql);
            }
            ResultSet res = stmt.executeQuery();
            exist = res.next();
        } catch (SQLException se) {

        } finally {
            rlock.unlock();
            try {
                stmt.close();
            } catch (SQLException e) {
            }
        }

        return exist;
    }

    public final IndexDescProto[] getIndexes(final String tableName) throws IOException {
        ResultSet res;
        PreparedStatement stmt;

        List<IndexDescProto> protos = new ArrayList<IndexDescProto>();

        rlock.lock();
        try {
            String sql = "SELECT index_name, " + C_TABLE_ID + ", column_name, data_type, "
                    + "index_type, is_unique, is_clustered, is_ascending FROM indexes " + "where " + C_TABLE_ID
                    + "= ?";
            stmt = conn.prepareStatement(sql);
            stmt.setString(1, tableName);
            if (LOG.isDebugEnabled()) {
                LOG.debug(sql);
            }
            res = stmt.executeQuery();
            while (res.next()) {
                protos.add(resultToProto(res));
            }
        } catch (SQLException se) {
        } finally {
            rlock.unlock();
        }

        return protos.toArray(new IndexDescProto[protos.size()]);
    }

    private IndexDescProto resultToProto(final ResultSet res) throws SQLException {
        IndexDescProto.Builder builder = IndexDescProto.newBuilder();
        builder.setName(res.getString("index_name"));
        builder.setTableId(res.getString(C_TABLE_ID));
        builder.setColumn(resultToColumnProto(res));
        builder.setIndexMethod(getIndexMethod(res.getString("index_type").trim()));
        builder.setIsUnique(res.getBoolean("is_unique"));
        builder.setIsClustered(res.getBoolean("is_clustered"));
        builder.setIsAscending(res.getBoolean("is_ascending"));
        return builder.build();
    }

    private ColumnProto resultToColumnProto(final ResultSet res) throws SQLException {
        ColumnProto.Builder builder = ColumnProto.newBuilder();
        builder.setColumnName(res.getString("column_name"));
        builder.setDataType(CatalogUtil.newDataTypeWithoutLen(getDataType(res.getString("data_type").trim())));
        return builder.build();
    }

    private IndexMethod getIndexMethod(final String typeStr) {
        if (typeStr.equals(IndexMethod.TWO_LEVEL_BIN_TREE.toString())) {
            return IndexMethod.TWO_LEVEL_BIN_TREE;
        } else {
            LOG.error("Cannot find a matched type aginst from '" + typeStr + "'");
            // TODO - needs exception handling
            return null;
        }
    }

    @Override
    public final void addFunction(final FunctionDesc func) throws IOException {
        // TODO - not implemented yet    
    }

    @Override
    public final void deleteFunction(final FunctionDesc func) throws IOException {
        // TODO - not implemented yet    
    }

    @Override
    public final void existFunction(final FunctionDesc func) throws IOException {
        // TODO - not implemented yet    
    }

    @Override
    public final List<String> getAllFunctionNames() throws IOException {
        // TODO - not implemented yet
        return null;
    }

    @Override
    public final void close() {
        try {
            DriverManager.getConnection("jdbc:derby:;shutdown=true");
        } catch (SQLException e) {
            // TODO - to be fixed
            //LOG.error(e.getMessage(), e);
        }

        LOG.info("Shutdown database (" + jdbcUri + ")");
    }

    public static void main(final String[] args) throws IOException {
        @SuppressWarnings("unused")
        DBStore store = new DBStore(new TajoConf());
    }
}