blob: 2cac9251b8d7e5dc40db85c3bb8d234fb8a44b87 [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.cql3.selection;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.serializers.CollectionSerializer;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* Represents a list of timestamps associated to a CQL column. Those timestamps can either be writetimes or TTLs,
* according to {@link TimestampsType}.
*/
abstract class ColumnTimestamps
{
/**
* The timestamps type.
*/
protected final TimestampsType type;
protected ColumnTimestamps(TimestampsType type)
{
this.type = type;
}
/**
* @return the timestamps type
*/
public TimestampsType type()
{
return type;
}
/**
* Retrieves the timestamps at the specified position.
*
* @param index the timestamps position
* @return the timestamps at the specified position or a {@link #NO_TIMESTAMP}
*/
public abstract ColumnTimestamps get(int index);
public abstract ColumnTimestamps max();
/**
* Returns a view of the portion of the timestamps within the specified range.
*
* @param range the indexes range
* @return a view of the specified range within this {@link ColumnTimestamps}
*/
public abstract ColumnTimestamps slice(Range<Integer> range);
/**
* Converts the timestamps into their serialized form.
*
* @param protocolVersion the protocol version to use for the serialization
* @return the serialized timestamps
*/
public abstract ByteBuffer toByteBuffer(ProtocolVersion protocolVersion);
/**
* Appends an empty timestamp at the end of this list.
*/
public abstract void addNoTimestamp();
/**
* Appends the timestamp of the specified cell at the end of this list.
*/
public abstract void addTimestampFrom(Cell<?> cell, int nowInSecond);
/**
* Creates a new {@link ColumnTimestamps} instance for the specified column type.
*
* @param timestampType the timestamps type
* @param columnType the column type
* @return a {@link ColumnTimestamps} instance for the specified column type
*/
static ColumnTimestamps newTimestamps(TimestampsType timestampType, AbstractType<?> columnType)
{
if (!columnType.isMultiCell())
return new SingleTimestamps(timestampType);
// For UserType we know that the size will not change, so we can initialize the array with the proper capacity.
if (columnType instanceof UserType)
return new MultipleTimestamps(timestampType, ((UserType) columnType).size());
return new MultipleTimestamps(timestampType, 0);
}
/**
* The type of represented timestamps.
*/
public enum TimestampsType
{
WRITETIMES
{
@Override
long getTimestamp(Cell<?> cell, int nowInSecond)
{
return cell.timestamp();
}
@Override
long defaultValue()
{
return Long.MIN_VALUE;
}
@Override
ByteBuffer toByteBuffer(long timestamp)
{
return timestamp == defaultValue() ? null : ByteBufferUtil.bytes(timestamp);
}
},
TTLS
{
@Override
long getTimestamp(Cell<?> cell, int nowInSecond)
{
if (!cell.isExpiring())
return defaultValue();
int remaining = cell.localDeletionTime() - nowInSecond;
return remaining >= 0 ? remaining : defaultValue();
}
@Override
long defaultValue()
{
return -1;
}
@Override
ByteBuffer toByteBuffer(long timestamp)
{
return timestamp == defaultValue() ? null : ByteBufferUtil.bytes((int) timestamp);
}
};
/**
* Extracts the timestamp from the specified cell.
*
* @param cell the cell
* @param nowInSecond the query timestamp insecond
* @return the timestamp corresponding to this type
*/
abstract long getTimestamp(Cell<?> cell, int nowInSecond);
/**
* Returns the value to use when there is no timestamp.
*
* @return the value to use when there is no timestamp
*/
abstract long defaultValue();
/**
* Serializes the specified timestamp.
*
* @param timestamp the timestamp to serialize
* @return the bytes corresponding to the specified timestamp
*/
abstract ByteBuffer toByteBuffer(long timestamp);
}
/**
* A {@link ColumnTimestamps} that doesn't contain any timestamps.
*/
static final ColumnTimestamps NO_TIMESTAMP = new ColumnTimestamps(null)
{
@Override
public ColumnTimestamps get(int index)
{
return this;
}
@Override
public ColumnTimestamps max()
{
return this;
}
@Override
public ColumnTimestamps slice(Range<Integer> range)
{
return this;
}
@Override
public ByteBuffer toByteBuffer(ProtocolVersion protocolVersion)
{
return null;
}
@Override
public void addNoTimestamp()
{
throw new UnsupportedOperationException();
}
@Override
public void addTimestampFrom(Cell<?> cell, int nowInSecond)
{
throw new UnsupportedOperationException();
}
@Override
public String toString()
{
return "no timestamp";
}
};
/**
* A {@link ColumnTimestamps} that can contains a single timestamp (for columns that aren't multicell).
*/
private static class SingleTimestamps extends ColumnTimestamps
{
protected long timestamp;
public SingleTimestamps(TimestampsType type)
{
this(type, type.defaultValue());
}
public SingleTimestamps(TimestampsType type, long timestamp)
{
super(type);
this.timestamp = timestamp;
}
@Override
public void addNoTimestamp()
{
timestamp = type.defaultValue();
}
@Override
public void addTimestampFrom(Cell<?> cell, int nowInSecond)
{
timestamp = type.getTimestamp(cell, nowInSecond);
}
@Override
public ColumnTimestamps get(int index)
{
// If this method is called it means that it is an element selection on a frozen collection/UDT,
// so we can safely return this Timestamps as all the elements also share that timestamp
return this;
}
@Override
public ColumnTimestamps max()
{
return this;
}
@Override
public ColumnTimestamps slice(Range<Integer> range)
{
return range.isEmpty() ? NO_TIMESTAMP : this;
}
@Override
public ByteBuffer toByteBuffer(ProtocolVersion protocolVersion)
{
return timestamp == type.defaultValue() ? null : type.toByteBuffer(timestamp);
}
@Override
public String toString()
{
return type + ": " + timestamp;
}
}
/**
* A {@link ColumnTimestamps} that can contain multiple timestamps (for unfrozen collections or UDTs).
*/
private static final class MultipleTimestamps extends ColumnTimestamps
{
private final List<Long> timestamps;
public MultipleTimestamps(TimestampsType type, int initialCapacity)
{
this(type, new ArrayList<>(initialCapacity));
}
public MultipleTimestamps(TimestampsType type, List<Long> timestamps)
{
super(type);
this.timestamps = timestamps;
}
@Override
public void addNoTimestamp()
{
timestamps.add(type.defaultValue());
}
@Override
public void addTimestampFrom(Cell<?> cell, int nowInSecond)
{
timestamps.add(type.getTimestamp(cell, nowInSecond));
}
@Override
public ColumnTimestamps get(int index)
{
return timestamps.isEmpty()
? NO_TIMESTAMP
: new SingleTimestamps(type, timestamps.get(index));
}
@Override
public ColumnTimestamps max()
{
return timestamps.isEmpty()
? NO_TIMESTAMP
: new SingleTimestamps(type, Collections.max(timestamps));
}
@Override
public ColumnTimestamps slice(Range<Integer> range)
{
if (range.isEmpty())
return NO_TIMESTAMP;
// Prepare the "from" argument for the call to List#sublist below. That argument is always specified and
// inclusive, whereas the range lower bound can be open, closed or not specified.
int from = 0;
if (range.hasLowerBound())
{
from = range.lowerBoundType() == BoundType.CLOSED
? range.lowerEndpoint() // inclusive range lower bound, inclusive "from" is the same list position
: range.lowerEndpoint() + 1; // exclusive range lower bound, inclusive "from" is the next list position
}
// Prepare the "to" argument for the call to List#sublist below. That argument is always specified and
// exclusive, whereas the range upper bound can be open, closed or not specified.
int to = timestamps.size();
if (range.hasUpperBound())
{
to = range.upperBoundType() == BoundType.CLOSED
? range.upperEndpoint() + 1 // inclusive range upper bound, exclusive "to" is the next list position
: range.upperEndpoint(); // exclusive range upper bound, exclusive "to" is the same list position
}
return new MultipleTimestamps(type, timestamps.subList(from, to));
}
@Override
public ByteBuffer toByteBuffer(ProtocolVersion protocolVersion)
{
if (timestamps.isEmpty())
return null;
List<ByteBuffer> buffers = new ArrayList<>(timestamps.size());
timestamps.forEach(timestamp -> buffers.add(type.toByteBuffer(timestamp)));
return CollectionSerializer.pack(buffers, timestamps.size());
}
@Override
public String toString()
{
return type + ": " + timestamps;
}
}
}