package org.apache.cassandra.avro; /* * * 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. * */ import java.io.IOException; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.avro.Schema; import org.apache.avro.generic.GenericArray; import org.apache.avro.generic.GenericData; import org.apache.avro.ipc.AvroRemoteException; import org.apache.avro.util.Utf8; import org.apache.cassandra.auth.AllowAllAuthenticator; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.concurrent.StageManager; import org.apache.cassandra.config.*; import org.apache.cassandra.db.*; import org.apache.cassandra.db.filter.QueryPath; import org.apache.cassandra.db.marshal.MarshalException; import org.apache.cassandra.db.migration.*; import org.apache.cassandra.dht.*; import org.apache.cassandra.locator.AbstractReplicationStrategy; import org.apache.cassandra.scheduler.IRequestScheduler; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageService; import static org.apache.cassandra.avro.AvroRecordFactory.*; import static org.apache.cassandra.avro.ErrorFactory.*; public class CassandraServer implements Cassandra { private static Logger logger = LoggerFactory.getLogger(CassandraServer.class); private final static GenericArray<Column> EMPTY_SUBCOLUMNS = new GenericData.Array<Column>(0, Schema.createArray(Column.SCHEMA$)); private final static GenericArray<ColumnOrSuperColumn> EMPTY_COLUMNS = new GenericData.Array<ColumnOrSuperColumn>(0, Schema.createArray(ColumnOrSuperColumn.SCHEMA$)); private final static Utf8 API_VERSION = new Utf8("0.0.0"); // CfDef default values private final static String D_CF_CFTYPE = "Standard"; private final static String D_CF_CFCLOCKTYPE = "Timestamp"; private final static String D_CF_COMPTYPE = "BytesType"; private final static String D_CF_SUBCOMPTYPE = ""; private final static String D_CF_RECONCILER = null; //ColumnDef default values public final static String D_COLDEF_INDEXTYPE = "KEYS"; public final static String D_COLDEF_INDEXNAME = null; // thread local state containing session information public final ThreadLocal<ClientState> clientState = new ThreadLocal<ClientState>() { @Override public ClientState initialValue() { return new ClientState(); } }; /* * RequestScheduler to perform the scheduling of incoming requests */ private final IRequestScheduler requestScheduler; public CassandraServer() { requestScheduler = DatabaseDescriptor.getRequestScheduler(); } public Void login(AuthenticationRequest auth_request) throws AuthenticationException, AuthorizationException { try { state().login(auth_request.credentials); } catch (org.apache.cassandra.thrift.AuthenticationException thriftE) { throw new AuthenticationException(); } return null; } public ClientState state() { return clientState.get(); } @Override public ColumnOrSuperColumn get(ByteBuffer key, ColumnPath columnPath, ConsistencyLevel consistencyLevel) throws AvroRemoteException, InvalidRequestException, NotFoundException, UnavailableException, TimedOutException { if (logger.isDebugEnabled()) logger.debug("get"); AvroValidation.validateColumnPath(state().getKeyspace(), columnPath); // FIXME: This is repetitive. ByteBuffer column, super_column; column = columnPath.column == null ? null : columnPath.column; super_column = columnPath.super_column == null ? null : columnPath.super_column; QueryPath path = new QueryPath(columnPath.column_family.toString(), column == null ? null : super_column); List<ByteBuffer> nameAsList = Arrays.asList(column == null ? super_column : column); AvroValidation.validateKey(key); ReadCommand command = new SliceByNamesReadCommand(state().getKeyspace(), key, path, nameAsList); Map<DecoratedKey<?>, ColumnFamily> cfamilies = readColumnFamily(Arrays.asList(command), consistencyLevel); ColumnFamily cf = cfamilies.get(StorageService.getPartitioner().decorateKey(command.key)); if (cf == null) throw newNotFoundException(); GenericArray<ColumnOrSuperColumn> avroColumns = avronateColumnFamily(cf, command.queryPath.superColumnName != null, false); if (avroColumns.size() == 0) throw newNotFoundException(); assert avroColumns.size() == 1; return avroColumns.iterator().next(); } protected Map<DecoratedKey<?>, ColumnFamily> readColumnFamily(List<ReadCommand> commands, ConsistencyLevel consistency) throws InvalidRequestException, UnavailableException, TimedOutException { // TODO - Support multiple column families per row, right now row only contains 1 column family Map<DecoratedKey<?>, ColumnFamily> columnFamilyKeyMap = new HashMap<DecoratedKey<?>, ColumnFamily>(); List<Row> rows; try { schedule(); rows = StorageProxy.read(commands, thriftConsistencyLevel(consistency)); } catch (TimeoutException e) { throw new TimedOutException(); } catch (IOException e) { throw new RuntimeException(e); } // FIXME: This suckage brought to you by StorageService and StorageProxy // which throw Thrift exceptions directly. catch (org.apache.cassandra.thrift.UnavailableException e) { throw newUnavailableException(e); } catch (org.apache.cassandra.thrift.InvalidRequestException e) { throw newInvalidRequestException(e); } finally { release(); } for (Row row: rows) { columnFamilyKeyMap.put(row.key, row.cf); } return columnFamilyKeyMap; } // Don't playa hate, avronate. private List<Column> avronateSubColumns(Collection<IColumn> columns) { if (columns == null || columns.isEmpty()) return EMPTY_SUBCOLUMNS; List<Column> avroColumns = new ArrayList<Column>(columns.size()); for (IColumn column : columns) { if (column.isMarkedForDelete()) continue; Column avroColumn = newColumn(column.name(), column.value(), column.timestamp()); avroColumns.add(avroColumn); } return avroColumns; } private GenericArray<ColumnOrSuperColumn> avronateColumns(Collection<IColumn> columns, boolean reverseOrder) { ArrayList<ColumnOrSuperColumn> avroColumns = new ArrayList<ColumnOrSuperColumn>(columns.size()); for (IColumn column : columns) { if (column.isMarkedForDelete()) continue; Column avroColumn = newColumn(column.name(), column.value(), column.timestamp()); if (column instanceof ExpiringColumn) avroColumn.ttl = ((ExpiringColumn)column).getTimeToLive(); avroColumns.add(newColumnOrSuperColumn(avroColumn)); } if (reverseOrder) Collections.reverse(avroColumns); // FIXME: update for AVRO-540 when upgrading to Avro 1.4.0 GenericArray<ColumnOrSuperColumn> avroArray = new GenericData.Array<ColumnOrSuperColumn>(avroColumns.size(), Schema.createArray(ColumnOrSuperColumn.SCHEMA$)); for (ColumnOrSuperColumn cosc : avroColumns) avroArray.add(cosc); return avroArray; } private GenericArray<ColumnOrSuperColumn> avronateSuperColumns(Collection<IColumn> columns, boolean reverseOrder) { ArrayList<ColumnOrSuperColumn> avroSuperColumns = new ArrayList<ColumnOrSuperColumn>(columns.size()); for (IColumn column: columns) { List<Column> subColumns = avronateSubColumns(column.getSubColumns()); if (subColumns.size() == 0) continue; SuperColumn superColumn = newSuperColumn(column.name(), subColumns); avroSuperColumns.add(newColumnOrSuperColumn(superColumn)); } if (reverseOrder) Collections.reverse(avroSuperColumns); // FIXME: update for AVRO-540 when upgrading to Avro 1.4.0 GenericArray<ColumnOrSuperColumn> avroArray = new GenericData.Array<ColumnOrSuperColumn>(avroSuperColumns.size(), Schema.createArray(ColumnOrSuperColumn.SCHEMA$)); for (ColumnOrSuperColumn cosc : avroSuperColumns) avroArray.add(cosc); return avroArray; } private GenericArray<ColumnOrSuperColumn> avronateColumnFamily(ColumnFamily cf, boolean subColumnsOnly, boolean reverseOrder) { if (cf == null || cf.getColumnsMap().size() == 0) return EMPTY_COLUMNS; if (subColumnsOnly) { IColumn column = cf.getColumnsMap().values().iterator().next(); Collection<IColumn> subColumns = column.getSubColumns(); if (subColumns == null || subColumns.isEmpty()) return EMPTY_COLUMNS; else return avronateColumns(subColumns, reverseOrder); } if (cf.isSuper()) return avronateSuperColumns(cf.getSortedColumns(), reverseOrder); else return avronateColumns(cf.getSortedColumns(), reverseOrder); } public List<ColumnOrSuperColumn> get_slice(ByteBuffer key, ColumnParent columnParent, SlicePredicate predicate, ConsistencyLevel consistencyLevel) throws AvroRemoteException, InvalidRequestException, UnavailableException, TimedOutException { if (logger.isDebugEnabled()) logger.debug("get_slice"); Schema bytesArray = Schema.createArray(Schema.parse("{\"type\": \"bytes\"}")); GenericArray<ByteBuffer> keys = new GenericData.Array<ByteBuffer>(1, bytesArray); keys.add(key); return multigetSliceInternal(state().getKeyspace(), keys, columnParent, predicate, consistencyLevel).iterator().next().columns; } private List<CoscsMapEntry> multigetSliceInternal(String keyspace, List<ByteBuffer> keys, ColumnParent columnParent, SlicePredicate predicate, ConsistencyLevel consistencyLevel) throws InvalidRequestException, UnavailableException, TimedOutException { AvroValidation.validateColumnParent(keyspace, columnParent); AvroValidation.validatePredicate(keyspace, columnParent, predicate); QueryPath queryPath = new QueryPath(columnParent.column_family.toString(), columnParent.super_column); List<ReadCommand> commands = new ArrayList<ReadCommand>(); if (predicate.column_names != null) { for (ByteBuffer key : keys) { AvroValidation.validateKey(key); commands.add(new SliceByNamesReadCommand(keyspace, key, queryPath, predicate.column_names)); } } else { SliceRange range = predicate.slice_range; for (ByteBuffer key : keys) { AvroValidation.validateKey(key); commands.add(new SliceFromReadCommand(keyspace, key, queryPath, range.start, range.finish, range.reversed, range.count)); } } return getSlice(commands, consistencyLevel); } private List<CoscsMapEntry> getSlice(List<ReadCommand> commands, ConsistencyLevel consistencyLevel) throws InvalidRequestException, UnavailableException, TimedOutException { Map<DecoratedKey<?>, ColumnFamily> columnFamilies = readColumnFamily(commands, consistencyLevel); Schema sch = Schema.createArray(CoscsMapEntry.SCHEMA$); List<CoscsMapEntry> columnFamiliesList = new GenericData.Array<CoscsMapEntry>(commands.size(), sch); for (ReadCommand cmd : commands) { ColumnFamily cf = columnFamilies.get(StorageService.getPartitioner().decorateKey(cmd.key)); boolean reverseOrder = cmd instanceof SliceFromReadCommand && ((SliceFromReadCommand)cmd).reversed; GenericArray<ColumnOrSuperColumn> avroColumns = avronateColumnFamily(cf, cmd.queryPath.superColumnName != null, reverseOrder); columnFamiliesList.add(newCoscsMapEntry(cmd.key, avroColumns)); } return columnFamiliesList; } public int get_count(ByteBuffer key, ColumnParent columnParent, SlicePredicate predicate, ConsistencyLevel consistencyLevel) throws AvroRemoteException, InvalidRequestException, UnavailableException, TimedOutException { if (logger.isDebugEnabled()) logger.debug("get_count"); return (int)get_slice(key, columnParent, predicate, consistencyLevel).size(); } public List<CoscsMapEntry> multiget_slice(List<ByteBuffer> keys, ColumnParent columnParent, SlicePredicate predicate, ConsistencyLevel consistencyLevel) throws AvroRemoteException, InvalidRequestException, UnavailableException, TimedOutException { if (logger.isDebugEnabled()) logger.debug("multiget_slice"); return multigetSliceInternal(state().getKeyspace(), keys, columnParent, predicate, consistencyLevel); } public Void insert(ByteBuffer key, ColumnParent parent, Column column, ConsistencyLevel consistencyLevel) throws AvroRemoteException, InvalidRequestException, UnavailableException, TimedOutException { if (logger.isDebugEnabled()) logger.debug("insert"); AvroValidation.validateKey(key); AvroValidation.validateColumnParent(state().getKeyspace(), parent); AvroValidation.validateColumn(state().getKeyspace(), parent, column); RowMutation rm = new RowMutation(state().getKeyspace(), key); try { rm.add(new QueryPath(parent.column_family.toString(), parent.super_column, column.name), column.value, column.timestamp, column.ttl == null ? 0 : column.ttl); } catch (MarshalException e) { throw newInvalidRequestException(e.getMessage()); } doInsert(consistencyLevel, rm); return null; } public Void remove(ByteBuffer key, ColumnPath columnPath, long timestamp, ConsistencyLevel consistencyLevel) throws AvroRemoteException, InvalidRequestException, UnavailableException, TimedOutException { if (logger.isDebugEnabled()) logger.debug("remove"); AvroValidation.validateKey(key); AvroValidation.validateColumnPath(state().getKeyspace(), columnPath); RowMutation rm = new RowMutation(state().getKeyspace(), key); rm.delete(new QueryPath(columnPath.column_family.toString(), columnPath.super_column), timestamp); doInsert(consistencyLevel, rm); return null; } private void doInsert(ConsistencyLevel consistency, RowMutation rm) throws UnavailableException, TimedOutException { try { schedule(); StorageProxy.mutate(Arrays.asList(rm), thriftConsistencyLevel(consistency)); } catch (TimeoutException e) { throw new TimedOutException(); } catch (org.apache.cassandra.thrift.UnavailableException thriftE) { throw newUnavailableException(thriftE); } finally { release(); } } public Void batch_mutate(List<MutationsMapEntry> mutationMap, ConsistencyLevel consistencyLevel) throws AvroRemoteException, InvalidRequestException, UnavailableException, TimedOutException { if (logger.isDebugEnabled()) logger.debug("batch_mutate"); List<RowMutation> rowMutations = new ArrayList<RowMutation>(); for (MutationsMapEntry pair: mutationMap) { AvroValidation.validateKey(pair.key); Map<CharSequence, List<Mutation>> cfToMutations = pair.mutations; for (Map.Entry<CharSequence, List<Mutation>> cfMutations : cfToMutations.entrySet()) { String cfName = cfMutations.getKey().toString(); for (Mutation mutation : cfMutations.getValue()) AvroValidation.validateMutation(state().getKeyspace(), cfName, mutation); } rowMutations.add(getRowMutationFromMutations(state().getKeyspace(), pair.key, cfToMutations)); } try { schedule(); StorageProxy.mutate(rowMutations, thriftConsistencyLevel(consistencyLevel)); } catch (TimeoutException te) { throw newTimedOutException(); } // FIXME: StorageProxy.mutate throws Thrift's UnavailableException catch (org.apache.cassandra.thrift.UnavailableException ue) { throw newUnavailableException(); } finally { release(); } return null; } // FIXME: This is copypasta from o.a.c.db.RowMutation, (RowMutation.getRowMutation uses Thrift types directly). private static RowMutation getRowMutationFromMutations(String keyspace, ByteBuffer key, Map<CharSequence, List<Mutation>> cfMap) { RowMutation rm = new RowMutation(keyspace, key); for (Map.Entry<CharSequence, List<Mutation>> entry : cfMap.entrySet()) { String cfName = entry.getKey().toString(); for (Mutation mutation : entry.getValue()) { if (mutation.deletion != null) deleteColumnOrSuperColumnToRowMutation(rm, cfName, mutation.deletion); else addColumnOrSuperColumnToRowMutation(rm, cfName, mutation.column_or_supercolumn); } } return rm; } // FIXME: This is copypasta from o.a.c.db.RowMutation, (RowMutation.getRowMutation uses Thrift types directly). private static void addColumnOrSuperColumnToRowMutation(RowMutation rm, String cfName, ColumnOrSuperColumn cosc) { if (cosc.column == null) { for (Column column : cosc.super_column.columns) rm.add(new QueryPath(cfName, cosc.super_column.name, column.name), column.value, column.timestamp); } else { rm.add(new QueryPath(cfName, null, cosc.column.name), cosc.column.value, cosc.column.timestamp); } } // FIXME: This is copypasta from o.a.c.db.RowMutation, (RowMutation.getRowMutation uses Thrift types directly). private static void deleteColumnOrSuperColumnToRowMutation(RowMutation rm, String cfName, Deletion del) { if (del.predicate != null && del.predicate.column_names != null) { for (ByteBuffer col : del.predicate.column_names) { if (del.super_column == null && DatabaseDescriptor.getColumnFamilyType(rm.getTable(), cfName) == ColumnFamilyType.Super) rm.delete(new QueryPath(cfName, col), del.timestamp); else rm.delete(new QueryPath(cfName, del.super_column, col), del.timestamp); } } else { rm.delete(new QueryPath(cfName, del.super_column), del.timestamp); } } // Copy-pasted from the thrift CassandraServer, using the factory methods to create exceptions. // helper method to apply migration on the migration stage. typical migration failures will throw an // InvalidRequestException. atypical failures will throw a RuntimeException. private static void applyMigrationOnStage(final Migration m) throws InvalidRequestException { Future f = StageManager.getStage(Stage.MIGRATION).submit(new Callable() { public Object call() throws Exception { m.apply(); m.announce(); return null; } }); try { f.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { // this means call() threw an exception. deal with it directly. if (e.getCause() != null) { throw newInvalidRequestException(e.getCause()); } else { throw newInvalidRequestException(e); } } } private org.apache.cassandra.thrift.ConsistencyLevel thriftConsistencyLevel(ConsistencyLevel consistency) { switch (consistency) { case ONE: return org.apache.cassandra.thrift.ConsistencyLevel.ONE; case QUORUM: return org.apache.cassandra.thrift.ConsistencyLevel.QUORUM; case LOCAL_QUORUM: return org.apache.cassandra.thrift.ConsistencyLevel.LOCAL_QUORUM; case EACH_QUORUM: return org.apache.cassandra.thrift.ConsistencyLevel.EACH_QUORUM; case ALL: return org.apache.cassandra.thrift.ConsistencyLevel.ALL; } return null; } public Void set_keyspace(CharSequence keyspace) throws InvalidRequestException { String keyspaceStr = keyspace.toString(); if (DatabaseDescriptor.getTableDefinition(keyspaceStr) == null) { throw newInvalidRequestException("Keyspace does not exist"); } state().setKeyspace(keyspaceStr); return null; } public CharSequence system_add_keyspace(KsDef ksDef) throws AvroRemoteException, InvalidRequestException { if (!(DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator)) throw newInvalidRequestException("Unable to create new keyspace while authentication is enabled."); //generate a meaningful error if the user setup keyspace and/or column definition incorrectly for (CfDef cf : ksDef.cf_defs) { if (!cf.keyspace.equals(ksDef.name)) { throw newInvalidRequestException("CsDef (" + cf.name +") had a keyspace definition that did not match KsDef"); } } try { Collection<CFMetaData> cfDefs = new ArrayList<CFMetaData>((int)ksDef.cf_defs.size()); for (CfDef cfDef : ksDef.cf_defs) { cfDefs.add(convertToCFMetaData(cfDef)); } // convert Map<CharSequence, CharSequence> to Map<String, String> Map<String, String> strategyOptions = null; if (ksDef.strategy_options != null && !ksDef.strategy_options.isEmpty()) { strategyOptions = new HashMap<String, String>(); for (Map.Entry<CharSequence, CharSequence> option : ksDef.strategy_options.entrySet()) { strategyOptions.put(option.getKey().toString(), option.getValue().toString()); } } KSMetaData ksmeta = new KSMetaData( ksDef.name.toString(), AbstractReplicationStrategy.getClass(ksDef.strategy_class.toString()), strategyOptions, ksDef.replication_factor, cfDefs.toArray(new CFMetaData[cfDefs.size()])); applyMigrationOnStage(new AddKeyspace(ksmeta)); return DatabaseDescriptor.getDefsVersion().toString(); } catch (ConfigurationException e) { throw newInvalidRequestException(e); } catch (IOException e) { throw newInvalidRequestException(e); } } public CharSequence system_add_column_family(CfDef cfDef) throws AvroRemoteException, InvalidRequestException { checkKeyspaceAndLoginAuthorized(Permission.WRITE); try { applyMigrationOnStage(new AddColumnFamily(convertToCFMetaData(cfDef))); return DatabaseDescriptor.getDefsVersion().toString(); } catch (ConfigurationException e) { throw newInvalidRequestException(e); } catch (IOException e) { throw newInvalidRequestException(e); } } public CharSequence system_update_column_family(CfDef cf_def) throws AvroRemoteException, InvalidRequestException { checkKeyspaceAndLoginAuthorized(Permission.WRITE); if (cf_def.keyspace == null || cf_def.name == null) throw newInvalidRequestException("Keyspace and CF name must be set."); CFMetaData oldCfm = DatabaseDescriptor.getCFMetaData(CFMetaData.getId(cf_def.keyspace.toString(), cf_def.name.toString())); if (oldCfm == null) throw newInvalidRequestException("Could not find column family definition to modify."); try { CFMetaData.applyImplicitDefaults(cf_def); UpdateColumnFamily update = new UpdateColumnFamily(cf_def); applyMigrationOnStage(update); return DatabaseDescriptor.getDefsVersion().toString(); } catch (ConfigurationException e) { InvalidRequestException ex = newInvalidRequestException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InvalidRequestException ex = newInvalidRequestException(e.getMessage()); ex.initCause(e); throw ex; } } public CharSequence system_update_keyspace(KsDef ks_def) throws AvroRemoteException, InvalidRequestException { checkKeyspaceAndLoginAuthorized(Permission.WRITE); if (ks_def.cf_defs != null && ks_def.cf_defs.size() > 0) throw newInvalidRequestException("Keyspace update must not contain any column family definitions."); if (DatabaseDescriptor.getTableDefinition(ks_def.name.toString()) == null) throw newInvalidRequestException("Keyspace does not exist."); try { // convert Map<CharSequence, CharSequence> to Map<String, String> Map<String, String> strategyOptions = null; if (ks_def.strategy_options != null && !ks_def.strategy_options.isEmpty()) { strategyOptions = new HashMap<String, String>(); for (Map.Entry<CharSequence, CharSequence> option : ks_def.strategy_options.entrySet()) { strategyOptions.put(option.getKey().toString(), option.getValue().toString()); } } KSMetaData ksm = new KSMetaData( ks_def.name.toString(), AbstractReplicationStrategy.getClass(ks_def.strategy_class.toString()), strategyOptions, ks_def.replication_factor); applyMigrationOnStage(new UpdateKeyspace(ksm)); return DatabaseDescriptor.getDefsVersion().toString(); } catch (ConfigurationException e) { InvalidRequestException ex = newInvalidRequestException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InvalidRequestException ex = newInvalidRequestException(e.getMessage()); ex.initCause(e); throw ex; } } public GenericArray<CharSequence> describe_keyspaces() throws AvroRemoteException { Set<String> keyspaces = DatabaseDescriptor.getTables(); Schema schema = Schema.createArray(Schema.create(Schema.Type.STRING)); GenericArray<CharSequence> avroResults = new GenericData.Array<CharSequence>(keyspaces.size(), schema); for (String ksp : keyspaces) avroResults.add(new Utf8(ksp)); return avroResults; } public Utf8 describe_cluster_name() throws AvroRemoteException { return new Utf8(DatabaseDescriptor.getClusterName()); } public Utf8 describe_version() throws AvroRemoteException { return API_VERSION; } public Map<CharSequence, List<CharSequence>> check_schema_agreement() { logger.debug("checking schema agreement"); return (Map) StorageProxy.describeSchemaVersions(); } protected void checkKeyspaceAndLoginAuthorized(Permission perm) throws InvalidRequestException { try { state().hasColumnFamilyListAccess(perm); } catch (org.apache.cassandra.thrift.InvalidRequestException e) { throw newInvalidRequestException(e.getWhy()); } } /** * Schedule the current thread for access to the required services */ private void schedule() { requestScheduler.queue(Thread.currentThread(), state().getSchedulingValue()); } /** * Release a count of resources used to the request scheduler */ private void release() { requestScheduler.release(); } private CFMetaData convertToCFMetaData(CfDef cf_def) throws InvalidRequestException, ConfigurationException { String cfType = cf_def.column_type == null ? D_CF_CFTYPE : cf_def.column_type.toString(); String compare = cf_def.comparator_type == null ? D_CF_COMPTYPE : cf_def.comparator_type.toString(); String validate = cf_def.default_validation_class == null ? D_CF_COMPTYPE : cf_def.default_validation_class.toString(); String subCompare = cf_def.subcomparator_type == null ? D_CF_SUBCOMPTYPE : cf_def.subcomparator_type.toString(); CFMetaData.validateMinMaxCompactionThresholds(cf_def); CFMetaData.validateMemtableSettings(cf_def); return new CFMetaData(cf_def.keyspace.toString(), cf_def.name.toString(), ColumnFamilyType.create(cfType), DatabaseDescriptor.getComparator(compare), subCompare.length() == 0 ? null : DatabaseDescriptor.getComparator(subCompare), cf_def.comment == null ? "" : cf_def.comment.toString(), cf_def.row_cache_size == null ? CFMetaData.DEFAULT_ROW_CACHE_SIZE : cf_def.row_cache_size, cf_def.key_cache_size == null ? CFMetaData.DEFAULT_KEY_CACHE_SIZE : cf_def.key_cache_size, cf_def.read_repair_chance == null ? CFMetaData.DEFAULT_READ_REPAIR_CHANCE : cf_def.read_repair_chance, cf_def.gc_grace_seconds != null ? cf_def.gc_grace_seconds : CFMetaData.DEFAULT_GC_GRACE_SECONDS, DatabaseDescriptor.getComparator(validate), cf_def.min_compaction_threshold == null ? CFMetaData.DEFAULT_MIN_COMPACTION_THRESHOLD : cf_def.min_compaction_threshold, cf_def.max_compaction_threshold == null ? CFMetaData.DEFAULT_MAX_COMPACTION_THRESHOLD : cf_def.max_compaction_threshold, cf_def.row_cache_save_period_in_seconds == null ? CFMetaData.DEFAULT_ROW_CACHE_SAVE_PERIOD_IN_SECONDS : cf_def.row_cache_save_period_in_seconds, cf_def.key_cache_save_period_in_seconds == null ? CFMetaData.DEFAULT_KEY_CACHE_SAVE_PERIOD_IN_SECONDS : cf_def.key_cache_save_period_in_seconds, cf_def.memtable_flush_after_mins == null ? CFMetaData.DEFAULT_MEMTABLE_LIFETIME_IN_MINS : cf_def.memtable_flush_after_mins, cf_def.memtable_throughput_in_mb == null ? CFMetaData.DEFAULT_MEMTABLE_THROUGHPUT_IN_MB : cf_def.memtable_throughput_in_mb, cf_def.memtable_operations_in_millions == null ? CFMetaData.DEFAULT_MEMTABLE_OPERATIONS_IN_MILLIONS : cf_def.memtable_operations_in_millions, ColumnDefinition.fromColumnDefs((Iterable<ColumnDef>) cf_def.column_metadata)); } public KsDef describe_keyspace(CharSequence keyspace) throws AvroRemoteException, NotFoundException { KSMetaData ksMetadata = DatabaseDescriptor.getTableDefinition(keyspace.toString()); if (ksMetadata == null) throw new NotFoundException(); KsDef ksDef = new KsDef(); ksDef.name = keyspace; ksDef.replication_factor = ksMetadata.replicationFactor; ksDef.strategy_class = ksMetadata.strategyClass.getName(); if (ksMetadata.strategyOptions != null) { ksDef.strategy_options = new HashMap<CharSequence, CharSequence>(); ksDef.strategy_options.putAll(ksMetadata.strategyOptions); } GenericArray<CfDef> cfDefs = new GenericData.Array<CfDef>(ksMetadata.cfMetaData().size(), Schema.createArray(CfDef.SCHEMA$)); for (CFMetaData cfm : ksMetadata.cfMetaData().values()) { cfDefs.add(CFMetaData.convertToAvro(cfm)); } ksDef.cf_defs = cfDefs; return ksDef; } public CharSequence system_drop_column_family(CharSequence column_family) throws AvroRemoteException, InvalidRequestException { checkKeyspaceAndLoginAuthorized(Permission.WRITE); try { applyMigrationOnStage(new DropColumnFamily(state().getKeyspace(), column_family.toString())); return DatabaseDescriptor.getDefsVersion().toString(); } catch (ConfigurationException e) { throw newInvalidRequestException(e); } catch (IOException e) { throw newInvalidRequestException(e); } } public CharSequence system_drop_keyspace(CharSequence keyspace) throws AvroRemoteException, InvalidRequestException { if (!(DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator)) throw newInvalidRequestException("Unable to create new keyspace while authentication is enabled."); try { applyMigrationOnStage(new DropKeyspace(keyspace.toString())); return DatabaseDescriptor.getDefsVersion().toString(); } catch (ConfigurationException e) { throw newInvalidRequestException(e); } catch (IOException e) { throw newInvalidRequestException(e); } } public CharSequence describe_partitioner() throws AvroRemoteException { return StorageService.getPartitioner().getClass().getName(); } public List<CharSequence> describe_splits(CharSequence cfName, CharSequence start_token, CharSequence end_token, int keys_per_split) { Token.TokenFactory<?> tf = StorageService.getPartitioner().getTokenFactory(); List<Token> tokens = StorageService.instance.getSplits(state().getKeyspace(), cfName.toString(), new Range(tf.fromString(start_token.toString()), tf.fromString(end_token.toString())), keys_per_split); List<CharSequence> splits = new ArrayList<CharSequence>(tokens.size()); for (Token token : tokens) { splits.add(tf.toString(token)); } return splits; } public List<KeyCountMapEntry> multiget_count(List<ByteBuffer> keys, ColumnParent columnParent, SlicePredicate predicate, ConsistencyLevel consistencyLevel) throws AvroRemoteException, InvalidRequestException, UnavailableException, TimedOutException { if (logger.isDebugEnabled()) logger.debug("multiget_count"); checkKeyspaceAndLoginAuthorized(Permission.READ); String keyspace = state().getKeyspace(); List<KeyCountMapEntry> counts = new ArrayList<KeyCountMapEntry>(); List<CoscsMapEntry> columnFamiliesMap = multigetSliceInternal(keyspace, keys, columnParent, predicate, consistencyLevel); for (CoscsMapEntry cf : columnFamiliesMap) { KeyCountMapEntry countEntry = new KeyCountMapEntry(); countEntry.key = cf.key; countEntry.count = cf.columns.size(); counts.add(countEntry); } return counts; } public List<TokenRange> describe_ring(CharSequence keyspace) throws AvroRemoteException, InvalidRequestException { if (keyspace == null || keyspace.toString().equals(Table.SYSTEM_TABLE)) throw newInvalidRequestException("There is no ring for the keyspace: " + keyspace); List<TokenRange> ranges = new ArrayList<TokenRange>(); Token.TokenFactory<?> tf = StorageService.getPartitioner().getTokenFactory(); for (Map.Entry<Range, List<String>> entry : StorageService.instance.getRangeToEndpointMap(keyspace.toString()).entrySet()) { Range range = entry.getKey(); List<String> endpoints = entry.getValue(); ranges.add(newTokenRange(tf.toString(range.left), tf.toString(range.right), endpoints)); } return ranges; } public Void truncate(CharSequence columnFamily) throws AvroRemoteException, InvalidRequestException, UnavailableException { if (logger.isDebugEnabled()) logger.debug("truncating {} in {}", columnFamily, state().getKeyspace()); try { state().hasColumnFamilyAccess(columnFamily.toString(), Permission.WRITE); schedule(); StorageProxy.truncateBlocking(state().getKeyspace(), columnFamily.toString()); } catch (org.apache.cassandra.thrift.InvalidRequestException e) { throw newInvalidRequestException(e); } catch (org.apache.cassandra.thrift.UnavailableException e) { throw newUnavailableException(e); } catch (TimeoutException e) { throw newUnavailableException(e); } catch (IOException e) { throw newUnavailableException(e); } finally { release(); } return null; } public List<KeySlice> get_range_slices(ColumnParent column_parent, SlicePredicate slice_predicate, KeyRange range, ConsistencyLevel consistency_level) throws InvalidRequestException, TimedOutException { String keyspace = state().getKeyspace(); try { state().hasColumnFamilyAccess(column_parent.column_family.toString(), Permission.READ); } catch (org.apache.cassandra.thrift.InvalidRequestException thriftE) { throw newInvalidRequestException(thriftE); } AvroValidation.validateColumnParent(keyspace, column_parent); AvroValidation.validatePredicate(keyspace, column_parent, slice_predicate); AvroValidation.validateKeyRange(range); List<Row> rows; try { IPartitioner p = StorageService.getPartitioner(); AbstractBounds bounds; if (range.start_key == null) { Token.TokenFactory tokenFactory = p.getTokenFactory(); Token left = tokenFactory.fromString(range.start_token.toString()); Token right = tokenFactory.fromString(range.end_token.toString()); bounds = new Range(left, right); } else { bounds = new Bounds(p.getToken(range.start_key), p.getToken(range.end_key)); } try { schedule(); rows = StorageProxy.getRangeSlice(new RangeSliceCommand(keyspace, thriftColumnParent(column_parent), thriftSlicePredicate(slice_predicate), bounds, range.count), thriftConsistencyLevel(consistency_level)); } catch (org.apache.cassandra.thrift.UnavailableException thriftE) { throw newUnavailableException(thriftE); } finally { release(); } assert rows != null; } catch (TimeoutException e) { throw new TimedOutException(); } catch (IOException e) { throw new RuntimeException(e); } return avronateKeySlices(rows, column_parent, slice_predicate); } public List<KeySlice> get_indexed_slices(ColumnParent column_parent, IndexClause index_clause, SlicePredicate column_predicate, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException { if (logger.isDebugEnabled()) logger.debug("scan"); try { state().hasColumnFamilyAccess(column_parent.column_family.toString(), Permission.READ); } catch (org.apache.cassandra.thrift.InvalidRequestException thriftE) { throw newInvalidRequestException(thriftE); } String keyspace = state().getKeyspace(); AvroValidation.validateColumnParent(keyspace, column_parent); AvroValidation.validatePredicate(keyspace, column_parent, column_predicate); AvroValidation.validateIndexClauses(keyspace, column_parent.column_family.toString(), index_clause); List<Row> rows; try { rows = StorageProxy.scan(keyspace.toString(), column_parent.column_family.toString(), thriftIndexClause(index_clause), thriftSlicePredicate(column_predicate), thriftConsistencyLevel(consistency_level)); } catch (IOException e) { throw new RuntimeException(e); } catch (TimeoutException e) { throw new TimedOutException(); } catch (org.apache.cassandra.thrift.UnavailableException e) { throw newUnavailableException(); } return avronateKeySlices(rows, column_parent, column_predicate); } private List<KeySlice> avronateKeySlices(List<Row> rows, ColumnParent column_parent, SlicePredicate predicate) { List<KeySlice> keySlices = new ArrayList<KeySlice>(rows.size()); boolean reversed = predicate.slice_range != null && predicate.slice_range.reversed; for (Row row : rows) { List<ColumnOrSuperColumn> avronatedColumns = avronateColumnFamily(row.cf, column_parent.super_column != null, reversed); keySlices.add(newKeySlice(row.key.key, avronatedColumns)); } return keySlices; } private org.apache.cassandra.thrift.ColumnParent thriftColumnParent(ColumnParent avro_column_parent) { org.apache.cassandra.thrift.ColumnParent cp = new org.apache.cassandra.thrift.ColumnParent(avro_column_parent.column_family.toString()); if (avro_column_parent.super_column != null) cp.super_column = avro_column_parent.super_column; return cp; } private org.apache.cassandra.thrift.SlicePredicate thriftSlicePredicate(SlicePredicate avro_pred) { // One or the other are set, so check for nulls of either org.apache.cassandra.thrift.SliceRange slice_range = (avro_pred.slice_range != null) ? thriftSliceRange(avro_pred.slice_range) : null; return new org.apache.cassandra.thrift.SlicePredicate().setColumn_names(avro_pred.column_names).setSlice_range(slice_range); } private org.apache.cassandra.thrift.SliceRange thriftSliceRange(SliceRange avro_range) { return new org.apache.cassandra.thrift.SliceRange(avro_range.start, avro_range.finish, avro_range.reversed, avro_range.count); } private org.apache.cassandra.thrift.IndexClause thriftIndexClause(IndexClause avro_clause) { List<org.apache.cassandra.thrift.IndexExpression> expressions = new ArrayList<org.apache.cassandra.thrift.IndexExpression>(); for(IndexExpression exp : avro_clause.expressions) expressions.add(thriftIndexExpression(exp)); return new org.apache.cassandra.thrift.IndexClause(expressions, avro_clause.start_key, avro_clause.count); } private org.apache.cassandra.thrift.IndexExpression thriftIndexExpression(IndexExpression avro_exp) { return new org.apache.cassandra.thrift.IndexExpression(avro_exp.column_name, thriftIndexOperator(avro_exp.op), avro_exp.value); } private org.apache.cassandra.thrift.IndexOperator thriftIndexOperator(IndexOperator avro_op) { switch (avro_op) { case EQ: return org.apache.cassandra.thrift.IndexOperator.EQ; case GTE: return org.apache.cassandra.thrift.IndexOperator.GTE; case GT: return org.apache.cassandra.thrift.IndexOperator.GT; case LTE: return org.apache.cassandra.thrift.IndexOperator.LTE; case LT: return org.apache.cassandra.thrift.IndexOperator.LT; } return null; } }