Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import com.google.common.annotations.VisibleForTesting;

import org.apache.cassandra.spark.data.CqlField;
import org.apache.cassandra.spark.utils.ByteBufferUtils;

/**
Expand All @@ -35,6 +36,7 @@ public class RowData
private ByteBuffer columnName;
private ByteBuffer value;
private long timestamp;
private int ttl;
private BigInteger token;
@VisibleForTesting
boolean isNewPartition = false;
Expand All @@ -49,6 +51,7 @@ public void setPartitionKeyCopy(ByteBuffer partitionKeyBytes, BigInteger token)
this.value = null;
this.isNewPartition = true;
this.timestamp = 0L;
this.ttl = CqlField.NO_TTL;
}

public boolean isNewPartition()
Expand Down Expand Up @@ -107,6 +110,18 @@ public long getTimestamp()
return timestamp;
}

// TTL

public int getTtl()
{
return ttl;
}

public void setTtl(int ttl)
{
this.ttl = ttl;
}

@Override
public String toString()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@ public class Cell
{
public final Object[] values;
public final int position;
public final boolean isPkCkOnly;
public final boolean isNewRow;
public final long timestamp;
public final int ttl;

Cell(Object[] values, int position, boolean isNewRow, long timestamp)
Cell(Object[] values, int position, boolean isPkCkOnly, boolean isNewRow, long timestamp, int ttl)
{
this.values = values;
this.position = position;
this.isPkCkOnly = isPkCkOnly;
this.isNewRow = isNewRow;
this.timestamp = timestamp;
this.ttl = ttl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ private boolean getNext() throws IOException
// columns are projected. The column we find is irrelevant because if we fall under this
// condition it means that we are in a situation where the row has only PK + CK, but no
// regular columns.
next = new Cell(values, firstProjectedValueColumnPositionOrZero, newRow, rowData.getTimestamp());
next = new Cell(values, firstProjectedValueColumnPositionOrZero, true, newRow, rowData.getTimestamp(), rowData.getTtl());
return true;
}

Expand All @@ -235,7 +235,7 @@ private boolean getNext() throws IOException
}

// Update next Cell
next = new Cell(values, field.position(), newRow, rowData.getTimestamp());
next = new Cell(values, field.position(), false, newRow, rowData.getTimestamp(), rowData.getTtl());

return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -249,6 +250,27 @@ public static String getOrDefault(Map<String, String> options, String key, Strin
return options.getOrDefault(lowerCaseKey(key), defaultValue);
}

/**
* Returns sub-map with keys that match given prefix from original map. Prefix match is case-insensitive.
*
* @param options source map
* @param keyPrefix prefix of keys to be returned
* @param truncatePrefix whether to truncate prefix from output's map keys
* @param defaultValue default value returned when source map does not contain any key with given prefix
* @return sub-map with keys that match given prefix
*/
public static Map<String, String> getKeysWithPrefix(Map<String, String> options, String keyPrefix,
boolean truncatePrefix, Map<String, String> defaultValue)
{
int prefixLength = keyPrefix.length();
String lowerCasePrefix = lowerCaseKey(keyPrefix);
Map<String, String> subMap = options.entrySet().stream()
.filter(entry -> lowerCaseKey(entry.getKey()).startsWith(lowerCasePrefix))
.collect(Collectors.toMap(k -> truncatePrefix ? k.getKey().substring(prefixLength) : k.getKey(),
Map.Entry::getValue));
return subMap.isEmpty() ? defaultValue : subMap;
}

/**
* Method to check if key is present in {@code options} map.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,6 @@
import org.apache.cassandra.spark.data.partitioner.ConsistencyLevel;
import org.apache.cassandra.spark.data.partitioner.Partitioner;
import org.apache.cassandra.spark.data.partitioner.TokenPartitioner;
import org.apache.cassandra.spark.sparksql.LastModifiedTimestampDecorator;
import org.apache.cassandra.spark.sparksql.RowBuilder;
import org.apache.cassandra.spark.sparksql.filters.SSTableTimeRangeFilter;
import org.apache.cassandra.spark.utils.CqlUtils;
import org.apache.cassandra.spark.utils.ReaderTimeProvider;
Expand All @@ -101,12 +99,13 @@
import org.apache.cassandra.spark.validation.StartupValidatable;
import org.apache.cassandra.spark.validation.StartupValidator;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.catalyst.InternalRow;
import org.apache.spark.sql.types.DataType;
import org.apache.spark.util.ShutdownHookManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static org.apache.cassandra.spark.data.SchemaFeatureCustomizer.addCellLastModifiedTimestamp;
import static org.apache.cassandra.spark.data.SchemaFeatureCustomizer.addCellTtl;
import static org.apache.cassandra.spark.data.SchemaFeatureCustomizer.aliasLastModifiedTimestamp;
import static org.apache.cassandra.spark.utils.CqlUtils.isTimeRangeFilterSupported;
import static org.apache.cassandra.spark.utils.Properties.NODE_STATUS_NOT_CONSIDERED;

Expand Down Expand Up @@ -142,7 +141,11 @@ public class CassandraDataLayer extends PartitionedDataLayer implements StartupV
protected List<SchemaFeature> requestedFeatures;
protected Map<String, ReplicationFactor> rfMap;
@Nullable
protected String lastModifiedTimestampField;
protected String rowLastModifiedTimestampField;
@Nullable
protected Map<String, String> cellLastModifiedTimestampFields;
@Nullable
protected Map<String, String> cellTtlFields;
protected Set<String> sstableVersionsOnCluster;
// volatile in order to publish the reference for visibility
protected volatile CqlTable cqlTable;
Expand Down Expand Up @@ -172,7 +175,9 @@ public CassandraDataLayer(@NotNull ClientConfig options,
this.enableStats = options.enableStats();
this.readIndexOffset = options.readIndexOffset();
this.useIncrementalRepair = options.useIncrementalRepair();
this.lastModifiedTimestampField = options.lastModifiedTimestampField();
this.rowLastModifiedTimestampField = options.rowLastModifiedTimestampField();
this.cellLastModifiedTimestampFields = options.cellLastModifiedTimestampFields();
this.cellTtlFields = options.cellTtlFields();
this.requestedFeatures = options.requestedFeatures();
this.sstableTimeRangeFilter = options.sstableTimeRangeFilter;
}
Expand All @@ -198,7 +203,9 @@ protected CassandraDataLayer(@Nullable String keyspace,
boolean enableStats,
boolean readIndexOffset,
boolean useIncrementalRepair,
@Nullable String lastModifiedTimestampField,
@Nullable String rowLastModifiedTimestampField,
@Nullable Map<String, String> cellLastModifiedTimestampFields,
@Nullable Map<String, String> cellTtlFields,
List<SchemaFeature> requestedFeatures,
@NotNull Map<String, ReplicationFactor> rfMap,
TimeProvider timeProvider,
Expand All @@ -221,11 +228,21 @@ protected CassandraDataLayer(@Nullable String keyspace,
this.enableStats = enableStats;
this.readIndexOffset = readIndexOffset;
this.useIncrementalRepair = useIncrementalRepair;
this.lastModifiedTimestampField = lastModifiedTimestampField;
this.requestedFeatures = requestedFeatures;
if (lastModifiedTimestampField != null)
this.rowLastModifiedTimestampField = rowLastModifiedTimestampField;
if (rowLastModifiedTimestampField != null)
{
aliasLastModifiedTimestamp(this.requestedFeatures, this.rowLastModifiedTimestampField);
}
this.cellLastModifiedTimestampFields = cellLastModifiedTimestampFields;
if (cellLastModifiedTimestampFields != null)
{
addCellLastModifiedTimestamp(this.requestedFeatures, this.cellLastModifiedTimestampFields);
}
this.cellTtlFields = cellTtlFields;
if (cellTtlFields != null)
{
aliasLastModifiedTimestamp(this.requestedFeatures, this.lastModifiedTimestampField);
addCellTtl(this.requestedFeatures, this.cellTtlFields);
}
this.rfMap = rfMap;
this.timeProvider = timeProvider;
Expand Down Expand Up @@ -847,7 +864,9 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE
this.enableStats = in.readBoolean();
this.readIndexOffset = in.readBoolean();
this.useIncrementalRepair = in.readBoolean();
this.lastModifiedTimestampField = readNullable(in);
this.rowLastModifiedTimestampField = readNullable(in);
this.cellLastModifiedTimestampFields = readNullableObject(in);
this.cellTtlFields = readNullableObject(in);
int features = in.readShort();
List<SchemaFeature> requestedFeatures = new ArrayList<>(features);
for (int feature = 0; feature < features; feature++)
Expand All @@ -856,11 +875,13 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE
requestedFeatures.add(SchemaFeatureSet.valueOf(featureName.toUpperCase()));
}
this.requestedFeatures = requestedFeatures;
// Has alias for last modified timestamp
if (this.lastModifiedTimestampField != null)
// initialize features
if (this.rowLastModifiedTimestampField != null)
{
aliasLastModifiedTimestamp(this.requestedFeatures, this.lastModifiedTimestampField);
aliasLastModifiedTimestamp(this.requestedFeatures, this.rowLastModifiedTimestampField);
}
addCellLastModifiedTimestamp(this.requestedFeatures, this.cellLastModifiedTimestampFields);
addCellTtl(this.requestedFeatures, this.cellTtlFields);
this.rfMap = (Map<String, ReplicationFactor>) in.readObject();
this.timeProvider = new ReaderTimeProvider(in.readLong());
this.sstableTimeRangeFilter = (SSTableTimeRangeFilter) in.readObject();
Expand Down Expand Up @@ -901,12 +922,19 @@ private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFou
out.writeBoolean(this.readIndexOffset);
out.writeBoolean(this.useIncrementalRepair);
// If lastModifiedTimestampField exist, it aliases the LMT field
writeNullable(out, this.lastModifiedTimestampField);
writeNullable(out, this.rowLastModifiedTimestampField);
writeNullableObject(out, this.cellLastModifiedTimestampFields);
writeNullableObject(out, this.cellTtlFields);
// Serialize only distinct request features
List<String> featureNames = requestedFeatures.stream()
.map(SchemaFeature::optionName)
.distinct()
.collect(Collectors.toList());
// Write the list of requested features: first write the size, then write the feature names
out.writeShort(this.requestedFeatures.size());
for (SchemaFeature feature : requestedFeatures)
out.writeShort(featureNames.size());
for (String featureName : featureNames)
{
out.writeUTF(feature.optionName());
out.writeUTF(featureName);
}
out.writeObject(this.rfMap);
out.writeLong(timeProvider.referenceEpochInSeconds());
Expand All @@ -927,6 +955,19 @@ private static void writeNullable(ObjectOutputStream out, @Nullable String strin
}
}

private static void writeNullableObject(ObjectOutputStream out, @Nullable Object object) throws IOException
{
if (object == null)
{
out.writeBoolean(false);
}
else
{
out.writeBoolean(true);
out.writeObject(object);
}
}

@Nullable
private static String readNullable(ObjectInputStream in) throws IOException
{
Expand All @@ -937,6 +978,16 @@ private static String readNullable(ObjectInputStream in) throws IOException
return null;
}

@Nullable
private static <T> T readNullableObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
if (in.readBoolean())
{
return (T) in.readObject();
}
return null;
}

/**
* Validates that all SSTables being read have versions that were observed in gossip info.
* This catches cases where SSTables have unexpected versions that weren't seen during driver initialization.
Expand Down Expand Up @@ -1039,11 +1090,23 @@ public void write(Kryo kryo, Output out, CassandraDataLayer dataLayer)
out.writeBoolean(dataLayer.readIndexOffset);
out.writeBoolean(dataLayer.useIncrementalRepair);
// If lastModifiedTimestampField exist, it aliases the LMT field
out.writeString(dataLayer.lastModifiedTimestampField);
out.writeString(dataLayer.rowLastModifiedTimestampField);
out.writeBoolean(dataLayer.cellLastModifiedTimestampFields != null);
if (dataLayer.cellLastModifiedTimestampFields != null)
{
kryo.writeObject(out, dataLayer.cellLastModifiedTimestampFields);
}
out.writeBoolean(dataLayer.cellTtlFields != null);
if (dataLayer.cellTtlFields != null)
{
kryo.writeObject(out, dataLayer.cellTtlFields);
}
// Write the list of requested features: first write the size, then write the feature names
SchemaFeaturesListWrapper listWrapper = new SchemaFeaturesListWrapper();
// Serialize only distinct request features
listWrapper.requestedFeatureNames = dataLayer.requestedFeatures.stream()
.map(SchemaFeature::optionName)
.distinct()
.collect(Collectors.toList());
kryo.writeObject(out, listWrapper);
kryo.writeObject(out, dataLayer.rfMap);
Expand Down Expand Up @@ -1089,6 +1152,8 @@ public CassandraDataLayer read(Kryo kryo, Input in, Class<CassandraDataLayer> ty
in.readBoolean(),
in.readBoolean(),
in.readString(),
in.readBoolean() ? kryo.readObject(in, HashMap.class) : null,
in.readBoolean() ? kryo.readObject(in, HashMap.class) : null,
kryo.readObject(in, SchemaFeaturesListWrapper.class).toList(),
kryo.readObject(in, HashMap.class),
new ReaderTimeProvider(in.readLong()),
Expand Down Expand Up @@ -1204,45 +1269,4 @@ protected void await(CountDownLatch latch)
throw new RuntimeException(exception);
}
}

static void aliasLastModifiedTimestamp(List<SchemaFeature> requestedFeatures, String alias)
{
SchemaFeature featureAlias = new SchemaFeature()
{
@Override
public String optionName()
{
return SchemaFeatureSet.LAST_MODIFIED_TIMESTAMP.optionName();
}

@Override
public String fieldName()
{
return alias;
}

@Override
public DataType fieldDataType()
{
return SchemaFeatureSet.LAST_MODIFIED_TIMESTAMP.fieldDataType();
}

@Override
public <T extends InternalRow> RowBuilder<T> decorate(RowBuilder<T> builder)
{
return new LastModifiedTimestampDecorator<>(builder, alias);
}

@Override
public boolean fieldNullable()
{
return SchemaFeatureSet.LAST_MODIFIED_TIMESTAMP.fieldNullable();
}
};
int index = requestedFeatures.indexOf(SchemaFeatureSet.LAST_MODIFIED_TIMESTAMP);
if (index >= 0)
{
requestedFeatures.set(index, featureAlias);
}
}
}
Loading