/**
* 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.hadoop.conf.Configuration;
import org.apache.tajo.catalog.CatalogUtil;
import org.apache.tajo.catalog.exception.CatalogException;
import org.apache.tajo.exception.InternalException;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
public class DerbyStore extends AbstractDBStore {
private static final String CATALOG_DRIVER="org.apache.derby.jdbc.EmbeddedDriver";
protected String getCatalogDriverName(){
return CATALOG_DRIVER;
}
public DerbyStore(final Configuration conf)
throws InternalException {
super(conf);
}
protected Connection createConnection(Configuration conf) throws SQLException {
return DriverManager.getConnection(getCatalogUri());
}
// TODO - DDL and index statements should be renamed
protected void createBaseTable() throws CatalogException {
Connection conn = null;
Statement stmt = null;
try {
conn = getConnection();
stmt = conn.createStatement();
StringBuilder sql = new StringBuilder();
//META
if (!baseTableMaps.get(TB_META)) {
sql.append("CREATE TABLE ");
sql.append(TB_META);
sql.append(" (version int NOT NULL)");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.executeUpdate(sql.toString());
LOG.info("Table '" + TB_META + " is created.");
baseTableMaps.put(TB_META, true);
}
// TABLES
if (!baseTableMaps.get(TB_TABLES)) {
sql.delete(0, sql.length());
sql.append("CREATE TABLE ");
sql.append(TB_TABLES);
sql.append(" (");
sql.append("TID int NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),");
sql.append(C_TABLE_ID);
sql.append(" VARCHAR(255) NOT NULL CONSTRAINT TABLE_ID_UNIQ UNIQUE, ");
sql.append("path VARCHAR(1024), ");
sql.append("store_type CHAR(16), ");
sql.append("CONSTRAINT TABLES_PK PRIMARY KEY (TID)");
sql.append( ")");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append("CREATE UNIQUE INDEX idx_tables_tid on ");
sql.append(TB_TABLES);
sql.append(" (TID)");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append("CREATE UNIQUE INDEX idx_tables_name on ");
sql.append(TB_TABLES);
sql.append("(");
sql.append(C_TABLE_ID);
sql.append(")");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
stmt.executeBatch();
LOG.info("Table '" + TB_TABLES + "' is created.");
baseTableMaps.put(TB_TABLES, true);
}
// COLUMNS
if (!baseTableMaps.get(TB_COLUMNS)) {
sql.delete(0, sql.length());
sql.append("CREATE TABLE ");
sql.append(TB_COLUMNS);
sql.append(" (");
sql.append("TID INT NOT NULL REFERENCES ");
sql.append(TB_TABLES);
sql.append(" (TID) ON DELETE CASCADE, ");
sql.append(C_TABLE_ID);
sql.append( " VARCHAR(255) NOT NULL REFERENCES ");
sql.append(TB_TABLES);
sql.append("(");
sql.append(C_TABLE_ID);
sql.append(") ON DELETE CASCADE, ");
sql.append("column_id INT NOT NULL,");
sql.append("column_name VARCHAR(255) NOT NULL, ");
sql.append("data_type CHAR(16), type_length INTEGER, ");
sql.append("CONSTRAINT C_COLUMN_ID UNIQUE (");
sql.append(C_TABLE_ID);
sql.append(", column_name))");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append( "CREATE UNIQUE INDEX idx_fk_columns_table_name on ");
sql.append(TB_COLUMNS);
sql.append("(");
sql.append(C_TABLE_ID);
sql.append(", column_name)");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
stmt.executeBatch();
LOG.info("Table '" + TB_COLUMNS + " is created.");
baseTableMaps.put(TB_COLUMNS, true);
}
// OPTIONS
if (!baseTableMaps.get(TB_OPTIONS)) {
sql.delete(0, sql.length());
sql.append( "CREATE TABLE ");
sql.append(TB_OPTIONS);
sql.append(" (").append(C_TABLE_ID);
sql.append(" VARCHAR(255) NOT NULL REFERENCES TABLES (");
sql.append(C_TABLE_ID).append(") ON DELETE CASCADE, ");
sql.append("key_ VARCHAR(255) NOT NULL, value_ VARCHAR(255) NOT NULL)");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append("CREATE INDEX idx_options_key on ");
sql.append(TB_OPTIONS).append( " (").append(C_TABLE_ID).append(")");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append("CREATE INDEX idx_options_table_name on ").append(TB_OPTIONS);
sql.append("(" ).append(C_TABLE_ID).append(")");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
stmt.executeBatch();
LOG.info("Table '" + TB_OPTIONS + " is created.");
baseTableMaps.put(TB_OPTIONS, true);
}
// INDEXES
if (!baseTableMaps.get(TB_INDEXES)) {
sql.delete(0, sql.length());
sql.append("CREATE TABLE ").append(TB_INDEXES).append("(");
sql.append( "index_name VARCHAR(255) NOT NULL PRIMARY KEY, ");
sql.append(C_TABLE_ID).append(" VARCHAR(255) NOT NULL REFERENCES TABLES (");
sql.append(C_TABLE_ID).append(") ");
sql.append("ON DELETE CASCADE, ");
sql.append("column_name VARCHAR(255) NOT NULL, ");
sql.append("data_type VARCHAR(255) NOT NULL, ");
sql.append("index_type CHAR(32) NOT NULL, ");
sql.append("is_unique BOOLEAN NOT NULL, ");
sql.append("is_clustered BOOLEAN NOT NULL, ");
sql.append("is_ascending BOOLEAN NOT NULL)");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append("CREATE UNIQUE INDEX idx_indexes_key ON ");
sql.append(TB_INDEXES).append(" (index_name)");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append("CREATE INDEX idx_indexes_columns ON ");
sql.append(TB_INDEXES).append(" (").append(C_TABLE_ID).append(", column_name)");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
stmt.executeBatch();
LOG.info("Table '" + TB_INDEXES + "' is created.");
baseTableMaps.put(TB_INDEXES, true);
}
if (!baseTableMaps.get(TB_STATISTICS)) {
sql.delete(0, sql.length());
sql.append("CREATE TABLE ").append(TB_STATISTICS).append( "(");
sql.append(C_TABLE_ID).append(" VARCHAR(255) NOT NULL REFERENCES TABLES (");
sql.append(C_TABLE_ID).append(") ");
sql.append("ON DELETE CASCADE, ");
sql.append("num_rows BIGINT, ");
sql.append("num_bytes BIGINT)");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append("CREATE INDEX idx_stats_table_name ON ");
sql.append(TB_STATISTICS).append(" (").append(C_TABLE_ID).append(")");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
stmt.executeBatch();
LOG.info("Table '" + TB_STATISTICS + "' is created.");
baseTableMaps.put(TB_STATISTICS, true);
}
// PARTITION_METHODS
if (!baseTableMaps.get(TB_PARTITION_METHODS)) {
sql.delete(0, sql.length());
sql.append("CREATE TABLE ").append(TB_PARTITION_METHODS).append(" (");
sql.append(C_TABLE_ID).append(" VARCHAR(255) NOT NULL REFERENCES TABLES (");
sql.append(C_TABLE_ID).append(") ");
sql.append("ON DELETE CASCADE, ");
sql.append("partition_type VARCHAR(10) NOT NULL,");
sql.append("expression VARCHAR(1024) NOT NULL,");
sql.append("expression_schema VARCHAR(1024) FOR BIT DATA NOT NULL)");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append("CREATE INDEX idx_partition_methods_table_name ON ");
sql.append(TB_PARTITION_METHODS).append(" (").append(C_TABLE_ID).append(")");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
stmt.executeBatch();
LOG.info("Table '" + TB_PARTITION_METHODS + "' is created.");
baseTableMaps.put(TB_PARTITION_METHODS, true);
}
// PARTITIONS
if (!baseTableMaps.get(TB_PARTTIONS)) {
sql.delete(0, sql.length());
sql.append("CREATE TABLE ").append(TB_PARTTIONS).append("(");
sql.append("PID INT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),");
sql.append(C_TABLE_ID).append(" VARCHAR(255) NOT NULL REFERENCES TABLES (");
sql.append(C_TABLE_ID).append(")");
sql.append("ON DELETE CASCADE, ");
sql.append("partition_name VARCHAR(255), ");
sql.append("ordinal_position INT NOT NULL,");
sql.append("partition_value VARCHAR(1024),");
sql.append("path VARCHAR(1024),");
sql.append("cache_nodes VARCHAR(255), ");
sql.append(" CONSTRAINT PARTITION_PK PRIMARY KEY (PID))");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
sql.delete(0, sql.length());
sql.append("CREATE INDEX idx_partitions_table_name ON ");
sql.append(TB_PARTTIONS).append(" (").append(C_TABLE_ID).append(")");
if (LOG.isDebugEnabled()) {
LOG.debug(sql.toString());
}
stmt.addBatch(sql.toString());
stmt.executeBatch();
LOG.info("Table '" + TB_PARTTIONS + "' is created.");
baseTableMaps.put(TB_PARTTIONS, true);
}
} catch (SQLException se) {
throw new CatalogException(se);
} finally {
CatalogUtil.closeQuietly(conn, stmt);
}
}
@Override
protected void dropBaseTable() throws CatalogException {
Connection conn = null;
Statement stmt = null;
Map<String, Boolean> droppedTable = new HashMap<String, Boolean>();
try {
conn = getConnection();
stmt = conn.createStatement();
StringBuilder sql = new StringBuilder();
for(Map.Entry<String, Boolean> entry : baseTableMaps.entrySet()) {
if(entry.getValue() && !entry.getKey().equals(TB_TABLES)) {
sql.delete(0, sql.length());
sql.append("DROP TABLE ").append(entry.getKey());
stmt.addBatch(sql.toString());
droppedTable.put(entry.getKey(), true);
}
}
if(baseTableMaps.get(TB_TABLES)) {
sql.delete(0, sql.length());
sql.append("DROP TABLE ").append(TB_TABLES);
stmt.addBatch(sql.toString());
droppedTable.put(TB_TABLES, true);
}
stmt.executeBatch();
for(String tableName : droppedTable.keySet()) {
LOG.info("Table '" + tableName + "' is dropped");
}
} catch (SQLException se) {
throw new CatalogException(se);
} finally {
CatalogUtil.closeQuietly(conn, stmt);
}
}
@Override
protected boolean isInitialized() throws CatalogException {
Connection conn = null;
ResultSet res = null;
int foundCount = 0;
try {
conn = getConnection();
res = conn.getMetaData().getTables(null, null, null,
new String [] {"TABLE"});
baseTableMaps.put(TB_META, false);
baseTableMaps.put(TB_TABLES, false);
baseTableMaps.put(TB_COLUMNS, false);
baseTableMaps.put(TB_OPTIONS, false);
baseTableMaps.put(TB_STATISTICS, false);
baseTableMaps.put(TB_INDEXES, false);
baseTableMaps.put(TB_PARTITION_METHODS, false);
baseTableMaps.put(TB_PARTTIONS, false);
while (res.next()) {
baseTableMaps.put(res.getString("TABLE_NAME"), true);
}
} catch (SQLException se){
throw new CatalogException(se);
} finally {
CatalogUtil.closeQuietly(conn, res);
}
for(Map.Entry<String, Boolean> entry : baseTableMaps.entrySet()) {
if (!entry.getValue()) {
return false;
}
}
return true;
}
final boolean checkInternalTable(final String tableName) throws CatalogException {
Connection conn = null;
ResultSet res = null;
boolean found = false;
try {
conn = getConnection();
res = conn.getMetaData().getTables(null, null, null,
new String [] {"TABLE"});
while(res.next() && !found) {
if (tableName.equals(res.getString("TABLE_NAME")))
found = true;
}
} catch (SQLException se) {
throw new CatalogException(se);
} finally {
CatalogUtil.closeQuietly(conn, res);
}
return found;
}
@Override
public final void close() {
Connection conn = null;
// shutdown embedded database.
try {
// the shutdown=true attribute shuts down Derby.
conn = DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException se) {
if ( (se.getErrorCode() == 50000)
&& (se.getSQLState().equals("XJ015"))) {
// tajo got the expected exception
LOG.info("Derby shutdown complete normally.");
} else {
LOG.info("Derby shutdown complete abnormally. - message:" + se.getMessage());
}
} finally {
CatalogUtil.closeQuietly(conn);
}
LOG.info("Shutdown database (" + catalogUri + ")");
}
}