package org.apache.hadoop.hive.cassandra; import java.util.Arrays; import java.util.List; import java.util.Map; import org.apache.cassandra.thrift.CfDef; import org.apache.cassandra.thrift.InvalidRequestException; import org.apache.cassandra.thrift.KsDef; import org.apache.cassandra.thrift.NotFoundException; import org.apache.cassandra.thrift.SchemaDisagreementException; import org.apache.hadoop.hive.cassandra.serde.AbstractColumnSerDe; import org.apache.hadoop.hive.metastore.api.FieldSchema; import org.apache.hadoop.hive.metastore.api.MetaException; import org.apache.hadoop.hive.metastore.api.Table; import org.apache.thrift.TException; /** * A class to handle the transaction to cassandra backend database. * */ public class CassandraManager { final static public int DEFAULT_REPLICATION_FACTOR = 1; final static public String DEFAULT_STRATEGY = "org.apache.cassandra.locator.SimpleStrategy"; //Cassandra Host Name private final String host; //Cassandra Host Port private final int port; //Cassandra proxy client private CassandraProxyClient clientHolder; //Whether or not use framed connection private boolean framedConnection; //table property private final Table tbl; //key space name private String keyspace; //column family name private String columnFamilyName; /** * Construct a cassandra manager object from meta table object. */ public CassandraManager(Table tbl) throws MetaException { Map<String, String> serdeParam = tbl.getSd().getSerdeInfo().getParameters(); String cassandraHost = serdeParam.get(AbstractColumnSerDe.CASSANDRA_HOST); if (cassandraHost == null) { cassandraHost = AbstractColumnSerDe.DEFAULT_CASSANDRA_HOST; } this.host = cassandraHost; String cassandraPortStr = serdeParam.get(AbstractColumnSerDe.CASSANDRA_PORT); if (cassandraPortStr == null) { cassandraPortStr = AbstractColumnSerDe.DEFAULT_CASSANDRA_PORT; } try { port = Integer.parseInt(cassandraPortStr); } catch (NumberFormatException e) { throw new MetaException(AbstractColumnSerDe.CASSANDRA_PORT + " must be a number"); } this.tbl = tbl; init(); } private void init() { this.keyspace = getCassandraKeyspace(); this.columnFamilyName = getCassandraColumnFamily(); this.framedConnection = true; } /** * Open connection to the cassandra server. * * @throws MetaException */ public void openConnection() throws MetaException { try { clientHolder = new CassandraProxyClient(host, port, framedConnection, true); } catch (CassandraException e) { throw new MetaException("Unable to connect to the server " + e.getMessage()); } } /** * Close connection to the cassandra server. * */ public void closeConnection() { if (clientHolder != null) { clientHolder.close(); } } /** * Return a keyspace description for the given keyspace name from the cassandra host. * * @param keyspace keyspace name * @return keyspace description */ public KsDef getKeyspaceDesc() throws NotFoundException, MetaException { try { return clientHolder.getProxyConnection().describe_keyspace(keyspace); } catch (TException e) { throw new MetaException("An internal exception prevented this action from taking place." + e.getMessage()); } catch (InvalidRequestException e) { throw new MetaException("An internal exception prevented this action from taking place." + e.getMessage()); } } /** * Get Column family based on the configuration in the table. If nothing is found, return null. */ private CfDef getColumnFamily(KsDef ks) { for (CfDef cf : ks.getCf_defs()) { if (cf.getName().equalsIgnoreCase(columnFamilyName)) { return cf; } } return null; } /** * Get CfDef based on the configuration in the table. */ private CfDef getCfDef() throws MetaException { CfDef cf = new CfDef(); cf.setKeyspace(keyspace); cf.setName(columnFamilyName); cf.setColumn_type(getColumnType()); return cf; } /** * Create a keyspace with columns defined in the table. */ public KsDef createKeyspaceWithColumns() throws MetaException { try { KsDef ks = new KsDef(); ks.setName(getCassandraKeyspace()); ks.setReplication_factor(getReplicationFactor()); ks.setStrategy_class(getStrategy()); ks.addToCf_defs(getCfDef()); clientHolder.getProxyConnection().system_add_keyspace(ks); clientHolder.getProxyConnection().set_keyspace(keyspace); return ks; } catch (TException e) { throw new MetaException("Unable to create key space '" + keyspace + "'. Error:" + e.getMessage()); } catch (InvalidRequestException e) { throw new MetaException("Unable to create key space '" + keyspace + "'. Error:" + e.getMessage()); } catch (SchemaDisagreementException e) { throw new MetaException("Unable to create key space '" + keyspace + "'. Error:" + e.getMessage()); } } /** * Create the column family if it doesn't exist. * @param ks * @return * @throws MetaException */ public CfDef createCFIfNotFound(KsDef ks) throws MetaException { CfDef cf = getColumnFamily(ks); if (cf == null) { return createColumnFamily(); } else { return cf; } } /** * Create column family based on the configuration in the table. */ public CfDef createColumnFamily() throws MetaException { CfDef cf = getCfDef(); try { clientHolder.getProxyConnection().set_keyspace(keyspace); clientHolder.getProxyConnection().system_add_column_family(cf); return cf; } catch (TException e) { throw new MetaException("Unable to create column family '" + columnFamilyName + "'. Error:" + e.getMessage()); } catch (InvalidRequestException e) { throw new MetaException("Unable to create column family '" + columnFamilyName + "'. Error:" + e.getMessage()); } catch (SchemaDisagreementException e) { throw new MetaException("Unable to create column family '" + columnFamilyName + "'. Error:" + e.getMessage()); } } private String getColumnType() throws MetaException { String prop = getPropertyFromTable(AbstractColumnSerDe.CASSANDRA_COL_MAPPING); List<String> mapping; if (prop != null) { mapping = AbstractColumnSerDe.parseColumnMapping(prop); } else { List<FieldSchema> schema = tbl.getSd().getCols(); if (schema.size() ==0) { throw new MetaException("Can't find table column definitions"); } String[] colNames = new String[schema.size()]; for (int i = 0; i < schema.size(); i++) { colNames[i] = schema.get(i).getName(); } String mappingStr = AbstractColumnSerDe.createColumnMappingString(colNames); mapping = Arrays.asList(mappingStr.split(",")); } boolean hasKey = false; boolean hasColumn = false; boolean hasValue = false; boolean hasSubColumn = false; for (String column : mapping) { if (column.equalsIgnoreCase(AbstractColumnSerDe.CASSANDRA_KEY_COLUMN)) { hasKey = true; } else if (column.equalsIgnoreCase(AbstractColumnSerDe.CASSANDRA_COLUMN_COLUMN)) { hasColumn = true; } else if (column.equalsIgnoreCase(AbstractColumnSerDe.CASSANDRA_SUBCOLUMN_COLUMN)) { hasSubColumn = true; } else if (column.equalsIgnoreCase(AbstractColumnSerDe.CASSANDRA_VALUE_COLUMN)) { hasValue = true; } else { return "Standard"; } } if (hasKey && hasColumn && hasValue) { if (hasSubColumn) { return "Super"; } else { return "Standard"; } } else { return "Standard"; } } /** * Get replication factor from the table property. * @return replication factor * @throws MetaException error */ private int getReplicationFactor() throws MetaException { String prop = getPropertyFromTable(AbstractColumnSerDe.CASSANDRA_KEYSPACE_REPFACTOR); if (prop == null) { return DEFAULT_REPLICATION_FACTOR; } else { try { return Integer.parseInt(prop); } catch (NumberFormatException e) { throw new MetaException(AbstractColumnSerDe.CASSANDRA_KEYSPACE_REPFACTOR + " must be a number"); } } } /** * Get replication strategy from the table property. * * @return strategy */ private String getStrategy() { String prop = getPropertyFromTable(AbstractColumnSerDe.CASSANDRA_KEYSPACE_STRATEGY); if (prop == null) { return DEFAULT_STRATEGY; } else { return prop; } } /** * Get keyspace name from the table property. * * @return keyspace name */ private String getCassandraKeyspace() { String tableName = getPropertyFromTable(AbstractColumnSerDe.CASSANDRA_KEYSPACE_NAME); if (tableName == null) { tableName = tbl.getDbName(); } tbl.getParameters().put(AbstractColumnSerDe.CASSANDRA_KEYSPACE_NAME, tableName); return tableName; } /** * Get cassandra column family from table property. * * @return cassandra column family name */ private String getCassandraColumnFamily() { String tableName = getPropertyFromTable(AbstractColumnSerDe.CASSANDRA_CF_NAME); if (tableName == null) { tableName = tbl.getTableName(); } tbl.getParameters().put(AbstractColumnSerDe.CASSANDRA_CF_NAME, tableName); return tableName; } /** * Get the value for a given name from the table. * It first checks the table property. If it is not there, it checks the serde properties. * * @param columnName given name * @return value */ private String getPropertyFromTable(String columnName) { String prop = tbl.getParameters().get(columnName); if (prop == null) { prop = tbl.getSd().getSerdeInfo().getParameters().get(columnName); } return prop; } /** * Drop the table defined in the query. */ public void dropTable() throws MetaException { try { clientHolder.getProxyConnection().system_drop_column_family(columnFamilyName); } catch (TException e) { throw new MetaException("Unable to drop column family '" + columnFamilyName + "'. Error:" + e.getMessage()); } catch (InvalidRequestException e) { throw new MetaException("Unable to drop column family '" + columnFamilyName + "'. Error:" + e.getMessage()); } catch (SchemaDisagreementException e) { throw new MetaException("Unable to drop column family '" + columnFamilyName + "'. Error:" + e.getMessage()); } } }