| /** |
| * 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.DataInput; |
| import java.io.IOError; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.security.MessageDigest; |
| import java.util.Iterator; |
| |
| import com.google.common.collect.AbstractIterator; |
| import org.apache.cassandra.config.CFMetaData; |
| import org.apache.cassandra.db.composites.CellName; |
| import org.apache.cassandra.db.composites.CellNameType; |
| import org.apache.cassandra.db.context.CounterContext; |
| import org.apache.cassandra.db.marshal.AbstractType; |
| import org.apache.cassandra.io.sstable.format.Version; |
| import org.apache.cassandra.serializers.MarshalException; |
| import org.apache.cassandra.utils.FBUtilities; |
| |
| public abstract class AbstractCell implements Cell |
| { |
| public static Iterator<OnDiskAtom> onDiskIterator(final DataInput in, |
| final ColumnSerializer.Flag flag, |
| final int expireBefore, |
| final Version version, |
| final CellNameType type) |
| { |
| return new AbstractIterator<OnDiskAtom>() |
| { |
| protected OnDiskAtom computeNext() |
| { |
| OnDiskAtom atom; |
| try |
| { |
| atom = type.onDiskAtomSerializer().deserializeFromSSTable(in, flag, expireBefore, version); |
| } |
| catch (IOException e) |
| { |
| throw new IOError(e); |
| } |
| if (atom == null) |
| return endOfData(); |
| |
| return atom; |
| } |
| }; |
| } |
| |
| public boolean isLive() |
| { |
| return true; |
| } |
| |
| public boolean isLive(long now) |
| { |
| return true; |
| } |
| |
| public int cellDataSize() |
| { |
| return name().dataSize() + value().remaining() + TypeSizes.NATIVE.sizeof(timestamp()); |
| } |
| |
| public int serializedSize(CellNameType type, TypeSizes typeSizes) |
| { |
| /* |
| * Size of a column is = |
| * size of a name (short + length of the string) |
| * + 1 byte to indicate if the column has been deleted |
| * + 8 bytes for timestamp |
| * + 4 bytes which basically indicates the size of the byte array |
| * + entire byte array. |
| */ |
| int valueSize = value().remaining(); |
| return ((int)type.cellSerializer().serializedSize(name(), typeSizes)) + 1 + typeSizes.sizeof(timestamp()) + typeSizes.sizeof(valueSize) + valueSize; |
| } |
| |
| public int serializationFlags() |
| { |
| return 0; |
| } |
| |
| public Cell diff(Cell cell) |
| { |
| if (timestamp() < cell.timestamp()) |
| return cell; |
| return null; |
| } |
| |
| public void updateDigest(MessageDigest digest) |
| { |
| digest.update(name().toByteBuffer().duplicate()); |
| digest.update(value().duplicate()); |
| |
| FBUtilities.updateWithLong(digest, timestamp()); |
| FBUtilities.updateWithByte(digest, serializationFlags()); |
| } |
| |
| public int getLocalDeletionTime() |
| { |
| return Integer.MAX_VALUE; |
| } |
| |
| public Cell reconcile(Cell cell) |
| { |
| long ts1 = timestamp(), ts2 = cell.timestamp(); |
| if (ts1 != ts2) |
| return ts1 < ts2 ? cell : this; |
| if (isLive() != cell.isLive()) |
| return isLive() ? cell : this; |
| return value().compareTo(cell.value()) < 0 ? cell : this; |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| return this == o || (o instanceof Cell && equals((Cell) o)); |
| } |
| |
| public boolean equals(Cell cell) |
| { |
| return timestamp() == cell.timestamp() && name().equals(cell.name()) && value().equals(cell.value()) |
| && serializationFlags() == cell.serializationFlags(); |
| } |
| |
| public int hashCode() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public String getString(CellNameType comparator) |
| { |
| return String.format("%s:%b:%d@%d", |
| comparator.getString(name()), |
| !isLive(), |
| value().remaining(), |
| timestamp()); |
| } |
| |
| public void validateName(CFMetaData metadata) throws MarshalException |
| { |
| metadata.comparator.validate(name()); |
| } |
| |
| public void validateFields(CFMetaData metadata) throws MarshalException |
| { |
| validateName(metadata); |
| |
| AbstractType<?> valueValidator = metadata.getValueValidator(name()); |
| if (valueValidator != null) |
| valueValidator.validateCellValue(value()); |
| } |
| |
| public static Cell create(CellName name, ByteBuffer value, long timestamp, int ttl, CFMetaData metadata) |
| { |
| if (ttl <= 0) |
| ttl = metadata.getDefaultTimeToLive(); |
| |
| return ttl > 0 |
| ? new BufferExpiringCell(name, value, timestamp, ttl) |
| : new BufferCell(name, value, timestamp); |
| } |
| |
| public Cell diffCounter(Cell cell) |
| { |
| assert this instanceof CounterCell : "Wrong class type: " + getClass(); |
| |
| if (timestamp() < cell.timestamp()) |
| return cell; |
| |
| // Note that if at that point, cell can't be a tombstone. Indeed, |
| // cell is the result of merging us with other nodes results, and |
| // merging a CounterCell with a tombstone never return a tombstone |
| // unless that tombstone timestamp is greater that the CounterCell |
| // one. |
| assert cell instanceof CounterCell : "Wrong class type: " + cell.getClass(); |
| |
| if (((CounterCell) this).timestampOfLastDelete() < ((CounterCell) cell).timestampOfLastDelete()) |
| return cell; |
| |
| CounterContext.Relationship rel = CounterCell.contextManager.diff(cell.value(), value()); |
| return (rel == CounterContext.Relationship.GREATER_THAN || rel == CounterContext.Relationship.DISJOINT) ? cell : null; |
| } |
| |
| /** This is temporary until we start creating Cells of the different type (buffer vs. native) */ |
| public Cell reconcileCounter(Cell cell) |
| { |
| assert this instanceof CounterCell : "Wrong class type: " + getClass(); |
| |
| // No matter what the counter cell's timestamp is, a tombstone always takes precedence. See CASSANDRA-7346. |
| if (cell instanceof DeletedCell) |
| return cell; |
| |
| assert (cell instanceof CounterCell) : "Wrong class type: " + cell.getClass(); |
| |
| // live < live last delete |
| if (timestamp() < ((CounterCell) cell).timestampOfLastDelete()) |
| return cell; |
| |
| long timestampOfLastDelete = ((CounterCell) this).timestampOfLastDelete(); |
| |
| // live last delete > live |
| if (timestampOfLastDelete > cell.timestamp()) |
| return this; |
| |
| // live + live. return one of the cells if its context is a superset of the other's, or merge them otherwise |
| ByteBuffer context = CounterCell.contextManager.merge(value(), cell.value()); |
| if (context == value() && timestamp() >= cell.timestamp() && timestampOfLastDelete >= ((CounterCell) cell).timestampOfLastDelete()) |
| return this; |
| else if (context == cell.value() && cell.timestamp() >= timestamp() && ((CounterCell) cell).timestampOfLastDelete() >= timestampOfLastDelete) |
| return cell; |
| else // merge clocks and timestamps. |
| return new BufferCounterCell(name(), |
| context, |
| Math.max(timestamp(), cell.timestamp()), |
| Math.max(timestampOfLastDelete, ((CounterCell) cell).timestampOfLastDelete())); |
| } |
| |
| } |