blob: 62af0f194c85f53ab7f5cf572e107a89a91a9b7a [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 java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.util.*;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.memory.AbstractAllocator;
/**
* The clustering column values for a row.
* <p>
* A {@code Clustering} is a {@code ClusteringPrefix} that must always be "complete", i.e. have
* as many values as there is clustering columns in the table it is part of. It is the clustering
* prefix used by rows.
* <p>
* Note however that while it's size must be equal to the table clustering size, a clustering can have
* {@code null} values, and this mostly for thrift backward compatibility (in practice, if a value is null,
* all of the following ones will be too because that's what thrift allows, but it's never assumed by the
* code so we could start generally allowing nulls for clustering columns if we wanted to).
*/
public class Clustering extends AbstractClusteringPrefix
{
public static final Serializer serializer = new Serializer();
/**
* The special cased clustering used by all static rows. It is a special case in the
* sense that it's always empty, no matter how many clustering columns the table has.
*/
public static final Clustering STATIC_CLUSTERING = new Clustering(EMPTY_VALUES_ARRAY)
{
@Override
public Kind kind()
{
return Kind.STATIC_CLUSTERING;
}
@Override
public String toString()
{
return "STATIC";
}
@Override
public String toString(CFMetaData metadata)
{
return toString();
}
};
/** Empty clustering for tables having no clustering columns. */
public static final Clustering EMPTY = new Clustering(EMPTY_VALUES_ARRAY)
{
@Override
public String toString(CFMetaData metadata)
{
return "EMPTY";
}
};
public Clustering(ByteBuffer... values)
{
super(Kind.CLUSTERING, values);
}
public Kind kind()
{
return Kind.CLUSTERING;
}
public ClusteringPrefix minimize()
{
if (!ByteBufferUtil.canMinimize(values))
return this;
return new Clustering(ByteBufferUtil.minimizeBuffers(values));
}
public Clustering copy(AbstractAllocator allocator)
{
// Important for STATIC_CLUSTERING (but no point in being wasteful in general).
if (size() == 0)
return this;
ByteBuffer[] newValues = new ByteBuffer[size()];
for (int i = 0; i < size(); i++)
newValues[i] = values[i] == null ? null : allocator.clone(values[i]);
return new Clustering(newValues);
}
public String toString(CFMetaData metadata)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size(); i++)
{
ColumnDefinition c = metadata.clusteringColumns().get(i);
sb.append(i == 0 ? "" : ", ").append(c.name).append('=').append(get(i) == null ? "null" : c.type.getString(get(i)));
}
return sb.toString();
}
public String toCQLString(CFMetaData metadata)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size(); i++)
{
ColumnDefinition c = metadata.clusteringColumns().get(i);
sb.append(i == 0 ? "" : ", ").append(c.type.getString(get(i)));
}
return sb.toString();
}
/**
* Serializer for Clustering object.
* <p>
* Because every clustering in a given table must have the same size (ant that size cannot actually change once the table
* has been defined), we don't record that size.
*/
public static class Serializer
{
public void serialize(Clustering clustering, DataOutputPlus out, int version, List<AbstractType<?>> types) throws IOException
{
assert clustering != STATIC_CLUSTERING : "We should never serialize a static clustering";
assert clustering.size() == types.size() : "Invalid clustering for the table: " + clustering;
ClusteringPrefix.serializer.serializeValuesWithoutSize(clustering, out, version, types);
}
public ByteBuffer serialize(Clustering clustering, int version, List<AbstractType<?>> types)
{
try (DataOutputBuffer buffer = new DataOutputBuffer((int)serializedSize(clustering, version, types)))
{
serialize(clustering, buffer, version, types);
return buffer.buffer();
}
catch (IOException e)
{
throw new RuntimeException("Writting to an in-memory buffer shouldn't trigger an IOException", e);
}
}
public long serializedSize(Clustering clustering, int version, List<AbstractType<?>> types)
{
return ClusteringPrefix.serializer.valuesWithoutSizeSerializedSize(clustering, version, types);
}
public Clustering deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
{
if (types.isEmpty())
return EMPTY;
ByteBuffer[] values = ClusteringPrefix.serializer.deserializeValuesWithoutSize(in, types.size(), version, types);
return new Clustering(values);
}
public Clustering deserialize(ByteBuffer in, int version, List<AbstractType<?>> types)
{
try (DataInputBuffer buffer = new DataInputBuffer(in, true))
{
return deserialize(buffer, version, types);
}
catch (IOException e)
{
throw new RuntimeException("Reading from an in-memory buffer shouldn't trigger an IOException", e);
}
}
}
}