package me.prettyprint.cassandra.model; import java.nio.ByteBuffer; import java.util.Arrays; import me.prettyprint.cassandra.model.thrift.ThriftConverter; import me.prettyprint.cassandra.model.thrift.ThriftFactory; import me.prettyprint.cassandra.serializers.TypeInferringSerializer; import me.prettyprint.cassandra.service.*; import me.prettyprint.hector.api.HConsistencyLevel; import me.prettyprint.hector.api.Keyspace; import me.prettyprint.hector.api.Serializer; import me.prettyprint.hector.api.beans.HColumn; import me.prettyprint.hector.api.beans.HCounterColumn; import me.prettyprint.hector.api.beans.HCounterSuperColumn; import me.prettyprint.hector.api.beans.HSuperColumn; import me.prettyprint.hector.api.exceptions.HectorException; import me.prettyprint.hector.api.mutation.MutationResult; import me.prettyprint.hector.api.mutation.Mutator; import org.apache.cassandra.thrift.Cassandra; import org.apache.cassandra.thrift.Deletion; import org.apache.cassandra.thrift.SlicePredicate; import org.apache.cassandra.thrift.SliceRange; /** * A Mutator inserts or deletes values from the cluster. * There are two main ways to use a mutator: * 1. Use the insert/delete methods to immediately insert of delete values. * or 2. Use the addInsertion/addDeletion methods to schedule batch operations and then execute() * all of them in batch. * * The class is not thread-safe. * * @author Ran Tavory * @author zznate * @author patricioe */ public final class MutatorImpl<K> implements Mutator<K> { private final ExecutingKeyspace keyspace; protected final Serializer<K> keySerializer; private BatchMutation<K> pendingMutations; private BatchSizeHint sizeHint; private HConsistencyLevel consistency; public MutatorImpl(Keyspace keyspace, Serializer<K> keySerializer, BatchSizeHint sizeHint) { this.keyspace = (ExecutingKeyspace) keyspace; this.keySerializer = keySerializer; this.sizeHint = sizeHint; } public MutatorImpl(Keyspace keyspace, Serializer<K> keySerializer) { this(keyspace, keySerializer, null); } public MutatorImpl(Keyspace keyspace) { this(keyspace, TypeInferringSerializer.<K> get()); } public MutatorImpl(Keyspace keyspace, BatchSizeHint sizeHint) { this(keyspace, TypeInferringSerializer.<K> get(), sizeHint); } @Override public HConsistencyLevel getConsistencyLevel() { return this.consistency; } @Override public void setConsistencyLevel(HConsistencyLevel level) { this.consistency = level; } // Simple and immediate insertion of a column @Override public <N,V> MutationResult insert(final K key, final String cf, final HColumn<N,V> c) { addInsertion(key, cf, c); return execute(); } // overloaded insert-super @Override public <SN,N,V> MutationResult insert(final K key, final String cf, final HSuperColumn<SN,N,V> superColumn) { addInsertion(key, cf, superColumn); return execute(); } @Override public <N> MutationResult delete(final K key, final String cf, final N columnName, final Serializer<N> nameSerializer) { addDeletion(key, cf, columnName, nameSerializer); return execute(); } @Override public <N> MutationResult delete(K key, String cf, N columnName, Serializer<N> nameSerializer, long clock) { addDeletion(key, cf, columnName, nameSerializer, clock); return execute(); } /** * Deletes a subcolumn of a supercolumn * @param <SN> super column type * @param <N> subcolumn type */ @Override public <SN,N> MutationResult subDelete(final K key, final String cf, final SN supercolumnName, final N columnName, final Serializer<SN> sNameSerializer, final Serializer<N> nameSerializer) { return new MutationResultImpl(keyspace.doExecute(new KeyspaceOperationCallback<Void>() { @Override public Void doInKeyspace(KeyspaceService ks) throws HectorException { ks.remove(keySerializer.toByteBuffer(key), ThriftFactory.createSuperColumnPath(cf, supercolumnName, columnName, sNameSerializer, nameSerializer)); return null; } }, consistency)); } @Override public <SN> MutationResult superDelete(final K key, final String cf, final SN supercolumnName, final Serializer<SN> sNameSerializer) { return new MutationResultImpl(keyspace.doExecute(new KeyspaceOperationCallback<Void>() { @Override public Void doInKeyspace(KeyspaceService ks) throws HectorException { // Remove a Super Column. ks.remove( keySerializer.toByteBuffer(key), ThriftFactory.createSuperColumnPath(cf, supercolumnName, sNameSerializer)); return null; } }, consistency)); } /** * Deletes the columns defined in the HSuperColumn. If there are no HColumns attached, * we delete the whole thing. * */ public <SN,N,V> Mutator<K> addSubDelete(K key, String cf, HSuperColumn<SN,N,V> sc) { return addSubDelete(key, cf, sc, keyspace.createClock()); } public <SN,N,V> Mutator<K> addSubDelete(K key, String cf, HSuperColumn<SN,N,V> sc, long clock) { Deletion d = new Deletion().setTimestamp(clock); if ( sc.getColumns() != null ) { SlicePredicate pred = new SlicePredicate(); for (HColumn<N, V> col : sc.getColumns()) { pred.addToColumn_names(col.getNameSerializer().toByteBuffer(col.getName())); } d.setPredicate(pred); } d.setSuper_column(sc.getNameByteBuffer()); getPendingMutations().addDeletion(key, Arrays.asList(cf), d); return this; } // schedule an insertion to be executed in batch by the execute method // CAVEAT: a large number of calls with a typo in one of them will leave things in an // indeterminant state if we dont validate against LIVE (but cached of course) // keyspaces and CFs on each add/delete call // also, should throw a typed StatementValidationException or similar perhaps? @Override public <N,V> Mutator<K> addInsertion(K key, String cf, HColumn<N,V> c) { getPendingMutations().addInsertion(key, Arrays.asList(cf), ((HColumnImpl<N, V>) c).toThrift()); return this; } /** * Schedule an insertion of a supercolumn to be inserted in batch mode by {@link #execute()} */ @Override public <SN,N,V> Mutator<K> addInsertion(K key, String cf, HSuperColumn<SN,N,V> sc) { getPendingMutations().addSuperInsertion(key, Arrays.asList(cf), ((HSuperColumnImpl<SN,N,V>) sc).toThrift()); return this; } /** * {@inheritDoc} */ @Override public <N> Mutator<K> addDeletion(K key, String cf, N columnName, Serializer<N> nameSerializer) { addDeletion(key, cf, columnName, nameSerializer, keyspace.createClock()); return this; } /** * {@inheritDoc} */ @Override public <N> Mutator<K> addDeletion(K key, String cf) { addDeletion(key, cf, null, null, keyspace.createClock()); return this; } /** * {@inheritDoc} */ @Override public <N> Mutator<K> addDeletion(K key, String cf, long clock) { addDeletion(key, cf, null, null, clock); return this; } @Override public <N> Mutator<K> addDeletion(Iterable<K> keys, String cf) { return addDeletion(keys, cf, keyspace.createClock()); } @Override public <N> Mutator<K> addDeletion(Iterable<K> keys, String cf, long clock) { for (K key : keys) { addDeletion(key, cf, null, null, clock); } return this; } /** * {@inheritDoc} */ @Override public <N> Mutator<K> addDeletion(K key, String cf, N columnName, Serializer<N> nameSerializer, long clock) { Deletion d; if ( columnName != null ) { SlicePredicate sp = new SlicePredicate(); sp.addToColumn_names(nameSerializer.toByteBuffer(columnName)); d = new Deletion().setTimestamp(clock).setPredicate(sp); } else { d = new Deletion().setTimestamp(clock); } getPendingMutations().addDeletion(key, Arrays.asList(cf), d); return this; } /** * {@inheritDoc} */ @Override public <N> Mutator<K> addDeletion(K key, String cf, N columnNameStart, N columnNameFinish, Serializer<N> nameSerializer) { return addDeletion(key, cf, columnNameStart, columnNameFinish, nameSerializer, keyspace.createClock()); } /** * {@inheritDoc} */ @Override public <N> Mutator<K> addDeletion(K key, String cf, N columnNameStart, N columnNameFinish, Serializer<N> nameSerializer, long clock) { SlicePredicate sp = new SlicePredicate(); sp.setSlice_range(new SliceRange(nameSerializer.toByteBuffer(columnNameStart), nameSerializer.toByteBuffer(columnNameFinish), false, Integer.MAX_VALUE)); Deletion d = new Deletion().setTimestamp(clock).setPredicate(sp); getPendingMutations().addDeletion(key, Arrays.asList(cf), d); return this; } /** * Batch executes all mutations scheduled to this Mutator instance by addInsertion, addDeletion etc. * May throw a HectorException which is a RuntimeException. * @return A MutationResult holds the status. */ @Override public MutationResult execute() { if (pendingMutations == null || pendingMutations.isEmpty()) { return new MutationResultImpl(true, 0, null); } final BatchMutation<K> mutations = pendingMutations.makeCopy(); pendingMutations = null; return new MutationResultImpl(keyspace.doExecuteOperation(new Operation<Void>(OperationType.WRITE) { @Override public Void execute(Cassandra.Client cassandra) throws Exception { cassandra.batch_mutate(mutations.getMutationMap(), ThriftConverter.consistencyLevel(consistencyLevelPolicy.get(operationType))); return null; } })); } /** * Discards all pending mutations. */ @Override public Mutator<K> discardPendingMutations() { pendingMutations = null; return this; } @Override public String toString() { return "Mutator(" + keyspace.toString() + ")"; } @Override public int getPendingMutationCount() { return getPendingMutations().getSize(); } private BatchMutation<K> getPendingMutations() { if (pendingMutations == null) { pendingMutations = new BatchMutation<K>(keySerializer, sizeHint); } return pendingMutations; } // Counters support. @Override public <N> MutationResult insertCounter(final K key, final String cf, final HCounterColumn<N> c) { addCounter(key,cf, c); return execute(); } @Override public <N> MutationResult incrementCounter(final K key, final String cf, final N columnName, final long increment) { return insertCounter(key, cf, new HCounterColumnImpl<N>(columnName, increment, TypeInferringSerializer.<N> get())); } @Override public <N> MutationResult decrementCounter(final K key, final String cf, final N columnName, final long increment) { return incrementCounter(key, cf, columnName, increment * -1L); } @Override public <SN, N> MutationResult insertCounter(K key, String cf, HCounterSuperColumn<SN, N> superColumn) { addCounter(key, cf, superColumn); return execute(); } @Override public <N> MutationResult deleteCounter(final K key, final String cf, final N counterColumnName, final Serializer<N> nameSerializer) { addCounterDeletion(key,cf,counterColumnName,nameSerializer); return execute(); } @Override public <SN, N> MutationResult subDeleteCounter(final K key, final String cf, final SN supercolumnName, final N columnName, final Serializer<SN> sNameSerializer, final Serializer<N> nameSerializer) { addCounterSubDeletion(key,cf, new HCounterSuperColumnImpl<SN,N>(sNameSerializer,nameSerializer) .setName(supercolumnName) .addSubCounterColumn(new HCounterColumnImpl<N>(nameSerializer))); return execute(); } @Override public <N> Mutator<K> addCounter(K key, String cf, HCounterColumn<N> c) { getPendingMutations().addCounterInsertion(key, Arrays.asList(cf), ((HCounterColumnImpl<N>) c).toThrift()); return this; } @Override public <SN, N> Mutator<K> addCounter(K key, String cf, HCounterSuperColumn<SN, N> sc) { getPendingMutations().addSuperCounterInsertion(key, Arrays.asList(cf), ((HCounterSuperColumnImpl<SN, N>) sc).toThrift()); return this; } @Override public <N> Mutator<K> addCounterDeletion(K key, String cf, N counterColumnName, Serializer<N> nameSerializer) { Deletion d; if ( counterColumnName != null ) { SlicePredicate sp = new SlicePredicate(); sp.addToColumn_names(nameSerializer.toByteBuffer(counterColumnName)); d = new Deletion().setPredicate(sp); } else { d = new Deletion(); } getPendingMutations().addDeletion(key, Arrays.asList(cf), d); return this; } @Override public <N> Mutator<K> addCounterDeletion(K key, String cf) { getPendingMutations().addDeletion(key, Arrays.asList(cf), new Deletion()); return this; } @Override public <SN, N> Mutator<K> addCounterSubDeletion(K key, String cf, HCounterSuperColumn<SN, N> sc) { Deletion d = new Deletion(); if ( sc.getColumns() != null ) { SlicePredicate pred = new SlicePredicate(); for (HCounterColumn<N> col : sc.getColumns()) { pred.addToColumn_names(col.getNameSerializer().toByteBuffer(col.getName())); } d.setPredicate(pred); } d.setSuper_column(sc.getNameByteBuffer()); getPendingMutations().addDeletion(key, Arrays.asList(cf), d); return this; } @Override public <SN, N> Mutator<K> addSubDelete(K key, String cf, SN sColumnName, N columnName, Serializer<SN> sNameSerializer, Serializer<N> nameSerializer) { return addSubDelete(key, cf, sColumnName, columnName, sNameSerializer, nameSerializer, keyspace.createClock()); } @Override public <SN, N> Mutator<K> addSubDelete(K key, String cf, SN sColumnName, N columnName, Serializer<SN> sNameSerializer, Serializer<N> nameSerializer, long clock) { Deletion d = new Deletion().setTimestamp(clock); SlicePredicate predicate = new SlicePredicate(); predicate.addToColumn_names(nameSerializer.toByteBuffer(columnName)); d.setPredicate(predicate); d.setSuper_column(sNameSerializer.toByteBuffer(sColumnName)); getPendingMutations().addDeletion(key, Arrays.asList(cf), d); return this; } @Override public <SN> Mutator<K> addSuperDelete(K key, String cf, SN sColumnName, Serializer<SN> sNameSerializer) { Deletion d = new Deletion().setTimestamp(keyspace.createClock()); d.setSuper_column(sNameSerializer.toByteBuffer(sColumnName)); getPendingMutations().addDeletion(key, Arrays.asList(cf), d); return this; } }