blob: aa683eafc84f73797bfa986352635342c4591ea3 [file] [log] [blame]
/**
* 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 static org.apache.cassandra.db.DBConstants.*;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.cassandra.cache.IRowCacheEntry;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.MarshalException;
import org.apache.cassandra.io.IColumnSerializer;
import org.apache.cassandra.utils.Allocator;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.HeapAllocator;
public class ColumnFamily extends AbstractColumnContainer implements IRowCacheEntry
{
public static final ColumnFamilySerializer serializer = new ColumnFamilySerializer();
private final CFMetaData cfm;
public static ColumnFamilySerializer serializer()
{
return serializer;
}
public static ColumnFamily create(Integer cfId)
{
return create(Schema.instance.getCFMetaData(cfId));
}
public static ColumnFamily create(Integer cfId, ISortedColumns.Factory factory)
{
return create(Schema.instance.getCFMetaData(cfId), factory);
}
public static ColumnFamily create(String tableName, String cfName)
{
return create(Schema.instance.getCFMetaData(tableName, cfName));
}
public static ColumnFamily create(CFMetaData cfm)
{
return create(cfm, TreeMapBackedSortedColumns.factory());
}
public static ColumnFamily create(CFMetaData cfm, ISortedColumns.Factory factory)
{
return create(cfm, factory, false);
}
public static ColumnFamily create(CFMetaData cfm, ISortedColumns.Factory factory, boolean reversedInsertOrder)
{
return new ColumnFamily(cfm, factory.create(cfm.comparator, reversedInsertOrder));
}
protected ColumnFamily(CFMetaData cfm, ISortedColumns map)
{
super(map);
assert cfm != null;
this.cfm = cfm;
}
public ColumnFamily cloneMeShallow(ISortedColumns.Factory factory, boolean reversedInsertOrder)
{
ColumnFamily cf = ColumnFamily.create(cfm, factory, reversedInsertOrder);
cf.delete(this);
return cf;
}
public ColumnFamily cloneMeShallow()
{
return cloneMeShallow(columns.getFactory(), columns.isInsertReversed());
}
public AbstractType<?> getSubComparator()
{
IColumnSerializer s = getColumnSerializer();
return (s instanceof SuperColumnSerializer) ? ((SuperColumnSerializer) s).getComparator() : null;
}
public ColumnFamilyType getType()
{
return cfm.cfType;
}
public ColumnFamily cloneMe()
{
ColumnFamily cf = new ColumnFamily(cfm, columns.cloneMe());
cf.delete(this);
return cf;
}
public Integer id()
{
return cfm.cfId;
}
/**
* @return The CFMetaData for this row
*/
public CFMetaData metadata()
{
return cfm;
}
public IColumnSerializer getColumnSerializer()
{
return cfm.getColumnSerializer();
}
public boolean isSuper()
{
return getType() == ColumnFamilyType.Super;
}
/**
* Same as addAll() but do a cloneMe of SuperColumn if necessary to
* avoid keeping references to the structure (see #3957).
*/
public void addAllWithSCCopy(ColumnFamily cf, Allocator allocator)
{
if (cf.isSuper())
{
for (IColumn c : cf)
{
columns.addColumn(((SuperColumn)c).cloneMe(), allocator);
}
delete(cf);
}
else
{
addAll(cf, allocator);
}
}
public void addColumn(QueryPath path, ByteBuffer value, long timestamp)
{
addColumn(path, value, timestamp, 0);
}
public void addColumn(QueryPath path, ByteBuffer value, long timestamp, int timeToLive)
{
assert path.columnName != null : path;
assert !metadata().getDefaultValidator().isCommutative();
Column column;
if (timeToLive > 0)
column = new ExpiringColumn(path.columnName, value, timestamp, timeToLive);
else
column = new Column(path.columnName, value, timestamp);
addColumn(path.superColumnName, column);
}
public void addCounter(QueryPath path, long value)
{
assert path.columnName != null : path;
addColumn(path.superColumnName, new CounterUpdateColumn(path.columnName, value, System.currentTimeMillis()));
}
public void addTombstone(QueryPath path, ByteBuffer localDeletionTime, long timestamp)
{
assert path.columnName != null : path;
addColumn(path.superColumnName, new DeletedColumn(path.columnName, localDeletionTime, timestamp));
}
public void addTombstone(QueryPath path, int localDeletionTime, long timestamp)
{
assert path.columnName != null : path;
addColumn(path.superColumnName, new DeletedColumn(path.columnName, localDeletionTime, timestamp));
}
public void addTombstone(ByteBuffer name, int localDeletionTime, long timestamp)
{
addColumn(null, new DeletedColumn(name, localDeletionTime, timestamp));
}
public void addColumn(ByteBuffer superColumnName, Column column)
{
IColumn c;
if (superColumnName == null)
{
c = column;
}
else
{
assert isSuper();
c = new SuperColumn(superColumnName, getSubComparator());
c.addColumn(column); // checks subcolumn name
}
addColumn(c);
}
public void clear()
{
columns.clear();
}
/*
* This function will calculate the difference between 2 column families.
* The external input is assumed to be a superset of internal.
*/
public ColumnFamily diff(ColumnFamily cfComposite)
{
assert cfComposite.id().equals(id());
ColumnFamily cfDiff = ColumnFamily.create(cfm);
if (cfComposite.getMarkedForDeleteAt() > getMarkedForDeleteAt())
{
cfDiff.delete(cfComposite.getLocalDeletionTime(), cfComposite.getMarkedForDeleteAt());
}
// (don't need to worry about cfNew containing IColumns that are shadowed by
// the delete tombstone, since cfNew was generated by CF.resolve, which
// takes care of those for us.)
for (IColumn columnExternal : cfComposite)
{
ByteBuffer cName = columnExternal.name();
IColumn columnInternal = this.columns.getColumn(cName);
if (columnInternal == null)
{
cfDiff.addColumn(columnExternal);
}
else
{
IColumn columnDiff = columnInternal.diff(columnExternal);
if (columnDiff != null)
{
cfDiff.addColumn(columnDiff);
}
}
}
if (!cfDiff.isEmpty() || cfDiff.isMarkedForDelete())
return cfDiff;
return null;
}
int size()
{
int size = DBConstants.longSize + DBConstants.intSize; // tombstone tracking
for (IColumn column : columns)
{
size += column.size();
}
return size;
}
public long maxTimestamp()
{
long maxTimestamp = getMarkedForDeleteAt();
for (IColumn column : columns)
maxTimestamp = Math.max(maxTimestamp, column.maxTimestamp());
return maxTimestamp;
}
@Override
public int hashCode()
{
return new HashCodeBuilder(373, 75437)
.append(cfm)
.append(getMarkedForDeleteAt())
.append(columns).toHashCode();
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || this.getClass() != o.getClass())
return false;
ColumnFamily comparison = (ColumnFamily) o;
return cfm.equals(comparison.cfm)
&& getMarkedForDeleteAt() == comparison.getMarkedForDeleteAt()
&& ByteBufferUtil.compareUnsigned(digest(this), digest(comparison)) == 0;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder("ColumnFamily(");
CFMetaData cfm = metadata();
sb.append(cfm == null ? "<anonymous>" : cfm.cfName);
if (isMarkedForDelete())
sb.append(" -deleted at ").append(getMarkedForDeleteAt()).append("-");
sb.append(" [").append(getComparator().getColumnsString(getSortedColumns())).append("])");
return sb.toString();
}
public static ByteBuffer digest(ColumnFamily cf)
{
MessageDigest digest = FBUtilities.threadLocalMD5Digest();
if (cf != null)
cf.updateDigest(digest);
return ByteBuffer.wrap(digest.digest());
}
public void updateDigest(MessageDigest digest)
{
for (IColumn column : columns)
column.updateDigest(digest);
}
public static AbstractType<?> getComparatorFor(String table, String columnFamilyName, ByteBuffer superColumnName)
{
return superColumnName == null
? Schema.instance.getComparator(table, columnFamilyName)
: Schema.instance.getSubComparator(table, columnFamilyName);
}
public static ColumnFamily diff(ColumnFamily cf1, ColumnFamily cf2)
{
if (cf1 == null)
return cf2;
return cf1.diff(cf2);
}
public void resolve(ColumnFamily cf)
{
resolve(cf, HeapAllocator.instance);
}
public void resolve(ColumnFamily cf, Allocator allocator)
{
// Row _does_ allow null CF objects :( seems a necessary evil for efficiency
if (cf == null)
return;
addAll(cf, allocator);
}
public long serializedSize()
{
return boolSize // nullness bool
+ intSize // id
+ serializedSizeForSSTable();
}
public long serializedSizeForSSTable()
{
int size = intSize // local deletion time
+ longSize // client deletion time
+ intSize; // column count
for (IColumn column : columns)
size += column.serializedSize();
return size;
}
/**
* Goes over all columns and check the fields are valid (as far as we can
* tell).
* This is used to detect corruption after deserialization.
*/
public void validateColumnFields() throws MarshalException
{
CFMetaData metadata = metadata();
for (IColumn column : this)
{
column.validateFields(metadata);
}
}
}