/** * 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.cassandra.db; import java.io.ByteArrayOutputStream; import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.ExecutionException; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.cassandra.concurrent.StageManager; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.filter.QueryPath; import org.apache.cassandra.io.ICompactSerializer; import org.apache.cassandra.io.ICompactSerializer2; import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.net.Message; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.thrift.ColumnOrSuperColumn; import org.apache.cassandra.thrift.Deletion; import org.apache.cassandra.thrift.Mutation; import org.apache.cassandra.thrift.ThriftGlue; import org.apache.cassandra.utils.FBUtilities; public class RowMutation { private static ICompactSerializer<RowMutation> serializer_; private static ICompactSerializer2<String> tableNameSerializer_; public static final String HINT = "HINT"; static { serializer_ = new RowMutationSerializer(); tableNameSerializer_ = new ICompactSerializer2<String>() { @Override public String deserialize(DataInput dis) throws IOException { return dis.readUTF(); } @Override public void serialize(String t, DataOutput dos) throws IOException { dos.writeUTF(t); } }; } public static ICompactSerializer<RowMutation> serializer() { return serializer_; } /** * @return the tableNameserializer_ */ public static ICompactSerializer2<String> tableNameSerializer_() { return tableNameSerializer_; } private String table_; private String key_; protected Map<String, ColumnFamily> modifications_ = new HashMap<String, ColumnFamily>(); public RowMutation(String table, String key) { table_ = table; key_ = key; } public RowMutation(String table, Row row) { table_ = table; key_ = row.key; add(row.cf); } protected RowMutation(String table, String key, Map<String, ColumnFamily> modifications) { table_ = table; key_ = key; modifications_ = modifications; } public String getTable() { return table_; } public String key() { return key_; } public Set<String> columnFamilyNames() { return modifications_.keySet(); } public Collection<ColumnFamily> getColumnFamilies() { return modifications_.values(); } void addHints(String key, byte[] host) throws IOException { QueryPath path = new QueryPath(HintedHandOffManager.HINTS_CF, key.getBytes("UTF-8"), host); add(path, ArrayUtils.EMPTY_BYTE_ARRAY, System.currentTimeMillis()); } /* * Specify a column family name and the corresponding column * family object. * param @ cf - column family name * param @ columnFamily - the column family. */ public void add(ColumnFamily columnFamily) { assert columnFamily != null; if (modifications_.containsKey(columnFamily.name())) { throw new IllegalArgumentException("ColumnFamily " + columnFamily.name() + " is already being modified"); } modifications_.put(columnFamily.name(), columnFamily); } /** should only be called by commitlog replay code */ public void removeColumnFamily(ColumnFamily columnFamily) { modifications_.remove(columnFamily.name()); } /** * Clones this mutation with cfs removed * * @param cfsToRemove cfs to remove * @return */ public RowMutation cloneAndRemoveColumnFamilies(Set<String> cfsToRemove) { RowMutation clone = new RowMutation(table_, key_); for (Map.Entry<String, ColumnFamily> e : modifications_.entrySet()) { if (!cfsToRemove.contains(e.getKey())) { clone.modifications_.put(e.getKey(), e.getValue()); } } return clone; } public boolean isEmpty() { return modifications_.isEmpty(); } /* * Specify a column name and a corresponding value for * the column. Column name is specified as <column family>:column. * This will result in a ColumnFamily associated with * <column family> as name and a Column with <column> * as name. The column can be further broken up * as super column name : columnname in case of super columns * * param @ cf - column name as <column family>:<column> * param @ value - value associated with the column * param @ timestamp - timestamp associated with this data. */ public void add(QueryPath path, byte[] value, long timestamp) { ColumnFamily columnFamily = modifications_.get(path.columnFamilyName); if (columnFamily == null) { columnFamily = ColumnFamily.create(table_, path.columnFamilyName); } columnFamily.addColumn(path, value, timestamp); modifications_.put(path.columnFamilyName, columnFamily); } public void delete(QueryPath path, long timestamp) { assert path.columnFamilyName != null; String cfName = path.columnFamilyName; int localDeleteTime = (int) (System.currentTimeMillis() / 1000); ColumnFamily columnFamily = modifications_.get(cfName); if (columnFamily == null) columnFamily = ColumnFamily.create(table_, cfName); if (path.superColumnName == null && path.columnName == null) { columnFamily.delete(localDeleteTime, timestamp); } else if (path.columnName == null) { SuperColumn sc = new SuperColumn(path.superColumnName, DatabaseDescriptor.getSubComparator(table_, cfName)); sc.markForDeleteAt(localDeleteTime, timestamp); columnFamily.addColumn(sc); } else { ByteBuffer bytes = ByteBuffer.allocate(4); bytes.putInt(localDeleteTime); columnFamily.addColumn(path, bytes.array(), timestamp, true); } modifications_.put(cfName, columnFamily); } /* * This is equivalent to calling commit. Applies the changes to * to the table that is obtained by calling Table.open(). */ public void apply() throws IOException { Table.open(table_).apply(this, getSerializedBuffer(), true); } /* * This is equivalent to calling commit. Applies the changes to * to the table that is obtained by calling Table.open(). */ void applyBinary() throws IOException, ExecutionException, InterruptedException { Table.open(table_).load(this); } public Message makeRowMutationMessage() throws IOException { return makeRowMutationMessage(StorageService.Verb.MUTATION); } public Message makeRowMutationMessage(StorageService.Verb verb) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); serializer().serialize(this, dos); return new Message(FBUtilities.getLocalAddress(), StageManager.MUTATION_STAGE, verb, bos.toByteArray()); } public static Message makeRowMutationMessage(byte[] serializedMutation) throws IOException { return new Message(FBUtilities.getLocalAddress(), StageManager.MUTATION_STAGE, StorageService.Verb.MUTATION, serializedMutation); } public static RowMutation getRowMutationFromMutations(String keyspace, String key, Map<String, List<Mutation>> cfmap) { RowMutation rm = new RowMutation(keyspace, key); for (Map.Entry<String, List<Mutation>> entry : cfmap.entrySet()) { String cfName = entry.getKey(); for (Mutation mutation : entry.getValue()) { if (mutation.deletion != null) { deleteColumnOrSuperColumnToRowMutation(rm, cfName, mutation.deletion); } else { addColumnOrSuperColumnToRowMutation(rm, cfName, mutation.column_or_supercolumn); } } } return rm; } public static RowMutation getRowMutation(String table, String key, Map<String, List<ColumnOrSuperColumn>> cfmap) { RowMutation rm = new RowMutation(table, key); for (Map.Entry<String, List<ColumnOrSuperColumn>> entry : cfmap.entrySet()) { String cfName = entry.getKey(); for (ColumnOrSuperColumn cosc : entry.getValue()) { if (cosc.column == null) { assert cosc.super_column != null; for (org.apache.cassandra.thrift.Column column : cosc.super_column.columns) { rm.add(new QueryPath(cfName, cosc.super_column.getName(), column.getName()), column.getValue(), column.timestamp); } } else { assert cosc.super_column == null; rm.add(new QueryPath(cfName, null, cosc.column.getName()), cosc.column.getValue(), cosc.column.timestamp); } } } return rm; } public DataOutputBuffer getSerializedBuffer() throws IOException { DataOutputBuffer buffer = new DataOutputBuffer(); RowMutation.serializer().serialize(this, buffer); return buffer; } public String toString() { return "RowMutation(" + "table='" + table_ + '\'' + ", key='" + key_ + '\'' + ", modifications=[" + StringUtils.join(modifications_.values(), ", ") + "]" + ')'; } private static void addColumnOrSuperColumnToRowMutation(RowMutation rm, String cfName, ColumnOrSuperColumn cosc) { if (cosc.column == null) { for (org.apache.cassandra.thrift.Column column : cosc.super_column.columns) { rm.add(new QueryPath(cfName, cosc.super_column.getName(), column.getName()), column.getValue(), column.timestamp); } } else { rm.add(new QueryPath(cfName, null, cosc.column.getName()), cosc.column.getValue(), cosc.column.timestamp); } } private static void deleteColumnOrSuperColumnToRowMutation(RowMutation rm, String cfName, Deletion del) { if (del.predicate != null && del.predicate.column_names != null) { for(byte[] c : ThriftGlue.toBytes(del.predicate.column_names) ) { if (del.super_column == null && DatabaseDescriptor.getColumnFamilyType(rm.table_, cfName).equals("Super")) rm.delete(new QueryPath(cfName, c), del.timestamp); else rm.delete(new QueryPath(cfName, del.getSuper_column(), c), del.timestamp); } } else { rm.delete(new QueryPath(cfName, del.getSuper_column()), del.timestamp); } } } class RowMutationSerializer implements ICompactSerializer<RowMutation> { private void freezeTheMaps(Map<String, ColumnFamily> map, DataOutputStream dos) throws IOException { int size = map.size(); dos.writeInt(size); if (size > 0) { Set<String> keys = map.keySet(); for (String key : keys) { dos.writeUTF(key); ColumnFamily cf = map.get(key); if (cf != null) { ColumnFamily.serializer().serialize(cf, dos); } } } } public void serialize(RowMutation rm, DataOutputStream dos) throws IOException { dos.writeUTF(rm.getTable()); dos.writeUTF(rm.key()); /* serialize the modifications_ in the mutation */ freezeTheMaps(rm.modifications_, dos); } private Map<String, ColumnFamily> defreezeTheMaps(DataInputStream dis) throws IOException { Map<String, ColumnFamily> map = new HashMap<String, ColumnFamily>(); int size = dis.readInt(); for (int i = 0; i < size; ++i) { String key = dis.readUTF(); ColumnFamily cf = ColumnFamily.serializer().deserialize(dis); map.put(key, cf); } return map; } public RowMutation deserialize(DataInputStream dis) throws IOException { String table = dis.readUTF(); String key = dis.readUTF(); Map<String, ColumnFamily> modifications = defreezeTheMaps(dis); return new RowMutation(table, key, modifications); } }