diff --git a/src/java/org/apache/cassandra/db/AbstractReadQuery.java b/src/java/org/apache/cassandra/db/AbstractReadQuery.java
index 7eec41148f08..d1df5a0f253f 100644
--- a/src/java/org/apache/cassandra/db/AbstractReadQuery.java
+++ b/src/java/org/apache/cassandra/db/AbstractReadQuery.java
@@ -19,6 +19,8 @@
import java.util.Set;
+import com.google.common.annotations.VisibleForTesting;
+
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.cql3.statements.SelectOptions;
@@ -62,7 +64,7 @@ public TableMetadata metadata()
// Monitorable interface
public String name()
{
- return toCQLString();
+ return toRedactedCQLString();
}
@Override
@@ -96,23 +98,53 @@ public ColumnFilter columnFilter()
}
/**
- * Recreate the CQL string corresponding to this query.
+ * Recreates the CQL string corresponding to this query, representing any specific values with '?',
+ * to prevent leaking sensitive data.
+ * @see #toCQLString(boolean)
+ */
+ public String toRedactedCQLString()
+ {
+ return toCQLString(true);
+ }
+
+ /**
+ * Recreates the CQL string corresponding to this query, printing specific values without any redaction.
+ * This might leak sensitive data if the query string ends up in logs or any other unprotected place, so this only
+ * should be used for debugging purposes or to present the query string to the same end user that created the query.
+ * @see #toCQLString(boolean)
+ */
+ public String toUnredactedCQLString()
+ {
+ return toCQLString(false);
+ }
+
+ /**
+ * Recreates the CQL string corresponding to this query.
+ *
+ * If the {@code redact} parameter is set to {@code true}, the query string will be redacted, replacing any specific
+ * column values with '?'. If set to {@code false}, the query string will not be redacted, and it might expose the
+ * queried column values which might contain sensitive data. The latter will be problematic if the query string ends
+ * up in logs or any other unprotected place. Therefore, non-redaction should only be used for debugging purposes or
+ * to present the query string to the same end user that created the query.
*
* Note that in general the returned string will not be exactly the original user string, first
- * because there isn't always a single syntax for a given query, but also because we don't have
+ * because there isn't always a single syntax for a given query, but also because we don't have
* all the information needed (we know the non-PK columns queried but not the PK ones as internally
- * we query them all). So this shouldn't be relied too strongly, but this should be good enough for
- * debugging purpose which is what this is for.
+ * we query them all). So this shouldn't be relied upon too strongly, but this should be good enough for
+ * debugging purposes which is what this is for.
+ *
+ * @param redact whether to redact the queried column values.
*/
- public String toCQLString()
+ @VisibleForTesting
+ protected String toCQLString(boolean redact)
{
CqlBuilder builder = new CqlBuilder();
- builder.append("SELECT ").append(columnFilter().toCQLString());
+ builder.append("SELECT ").append(columnFilter().toCQLString(redact));
builder.append(" FROM ").append(ColumnIdentifier.maybeQuote(metadata().keyspace))
.append('.')
.append(ColumnIdentifier.maybeQuote(metadata().name));
- appendCQLWhereClause(builder);
+ appendCQLWhereClause(builder, redact);
if (limits() != DataLimits.NONE)
builder.append(' ').append(limits());
@@ -132,5 +164,5 @@ public String toCQLString()
return builder.toString();
}
- protected abstract void appendCQLWhereClause(CqlBuilder builder);
+ protected abstract void appendCQLWhereClause(CqlBuilder builder, boolean redact);
}
diff --git a/src/java/org/apache/cassandra/db/Clustering.java b/src/java/org/apache/cassandra/db/Clustering.java
index d062837a3e27..32efdee715de 100644
--- a/src/java/org/apache/cassandra/db/Clustering.java
+++ b/src/java/org/apache/cassandra/db/Clustering.java
@@ -78,14 +78,14 @@ public default String toString(TableMetadata metadata)
return sb.toString();
}
- public default String toCQLString(TableMetadata metadata)
+ default String toCQLString(TableMetadata metadata, boolean redact)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size(); i++)
{
ColumnMetadata c = metadata.clusteringColumns().get(i);
ByteBuffer value = bufferAt(i);
- sb.append(i == 0 ? "" : ", ").append(c.type.toCQLString(value));
+ sb.append(i == 0 ? "" : ", ").append(c.type.toCQLString(value, redact));
}
return sb.toString();
}
diff --git a/src/java/org/apache/cassandra/db/DataRange.java b/src/java/org/apache/cassandra/db/DataRange.java
index 71c1cef5492d..353344a49b4f 100644
--- a/src/java/org/apache/cassandra/db/DataRange.java
+++ b/src/java/org/apache/cassandra/db/DataRange.java
@@ -271,7 +271,7 @@ public String toString(TableMetadata metadata)
return String.format("range=%s pfilter=%s", keyRange.getString(metadata.partitionKeyType), clusteringIndexFilter.toString(metadata));
}
- public String toCQLString(TableMetadata metadata)
+ public String toCQLString(TableMetadata metadata, boolean redact)
{
if (isUnrestricted())
return "";
@@ -283,20 +283,20 @@ public String toCQLString(TableMetadata metadata)
* key are the same. If that is the case, we want to print the query as an equality on the partition key
* rather than a token range, as if it was a partition query, for better readability.
*/
- return ((DecoratedKey) startKey()).toCQLString(metadata);
+ return ((DecoratedKey) startKey()).toCQLString(metadata, redact);
}
else
{
StringBuilder builder = new StringBuilder();
if (!startKey().isMinimum())
{
- appendCQLClause(startKey(), builder, metadata, true, keyRange.isStartInclusive());
+ appendCQLClause(startKey(), builder, metadata, true, keyRange.isStartInclusive(), redact);
}
if (!stopKey().isMinimum())
{
if (builder.length() > 0)
builder.append(" AND ");
- appendCQLClause(stopKey(), builder, metadata, false, keyRange.isEndInclusive());
+ appendCQLClause(stopKey(), builder, metadata, false, keyRange.isEndInclusive(), redact);
}
return builder.toString();
}
@@ -306,7 +306,8 @@ private void appendCQLClause(PartitionPosition pos,
StringBuilder builder,
TableMetadata metadata,
boolean isStart,
- boolean isInclusive)
+ boolean isInclusive,
+ boolean redact)
{
builder.append("token(");
builder.append(ColumnMetadata.toCQLString(metadata.partitionKeyColumns()));
@@ -315,14 +316,14 @@ private void appendCQLClause(PartitionPosition pos,
{
builder.append(getOperator(isStart, isInclusive)).append(' ');
builder.append("token(");
- appendKeyString(builder, metadata.partitionKeyType, ((DecoratedKey)pos).getKey());
+ appendKeyString(builder, metadata.partitionKeyType, ((DecoratedKey)pos).getKey(), redact);
builder.append(')');
}
else
{
Token.KeyBound keyBound = (Token.KeyBound) pos;
- builder.append(getOperator(isStart, isStart == keyBound.isMinimumBound)).append(" ");
- builder.append(keyBound.getToken());
+ builder.append(getOperator(isStart, isStart == keyBound.isMinimumBound)).append(' ');
+ builder.append(redact ? "?" : keyBound.getToken());
}
}
@@ -335,18 +336,18 @@ private static String getOperator(boolean isStart, boolean isInclusive)
// TODO: this is reused in SinglePartitionReadCommand but this should not really be here. Ideally
// we need a more "native" handling of composite partition keys.
- public static void appendKeyString(StringBuilder builder, AbstractType> type, ByteBuffer key)
+ public static void appendKeyString(StringBuilder builder, AbstractType> type, ByteBuffer key, boolean redact)
{
if (type instanceof CompositeType)
{
CompositeType ct = (CompositeType)type;
ByteBuffer[] values = ct.split(key);
for (int i = 0; i < ct.subTypes().size(); i++)
- builder.append(i == 0 ? "" : ", ").append(ct.subTypes().get(i).toCQLString(values[i]));
+ builder.append(i == 0 ? "" : ", ").append(ct.subTypes().get(i).toCQLString(values[i], redact));
}
else
{
- builder.append(type.toCQLString(key));
+ builder.append(type.toCQLString(key, redact));
}
}
diff --git a/src/java/org/apache/cassandra/db/DecoratedKey.java b/src/java/org/apache/cassandra/db/DecoratedKey.java
index a47cf0165c60..1ed11f60765d 100644
--- a/src/java/org/apache/cassandra/db/DecoratedKey.java
+++ b/src/java/org/apache/cassandra/db/DecoratedKey.java
@@ -172,26 +172,27 @@ public String toString()
* For multi-column keys: "k1 = 1 AND k2 = 2"
*
* @param metadata the table metadata
+ * @param redact whether to redact the key value, as in "k1 = ? AND k2 = ?".
*/
- public String toCQLString(TableMetadata metadata)
+ public String toCQLString(TableMetadata metadata, boolean redact)
{
List columns = metadata.partitionKeyColumns();
if (columns.size() == 1)
- return toCQLString(columns.get(0), getKey());
+ return toCQLString(columns.get(0), getKey(), redact);
ByteBuffer[] values = ((CompositeType) metadata.partitionKeyType).split(getKey());
StringJoiner joiner = new StringJoiner(" AND ");
for (int i = 0; i < columns.size(); i++)
- joiner.add(toCQLString(columns.get(i), values[i]));
+ joiner.add(toCQLString(columns.get(i), values[i], redact));
return joiner.toString();
}
- private static String toCQLString(ColumnMetadata metadata, ByteBuffer key)
+ private static String toCQLString(ColumnMetadata metadata, ByteBuffer key, boolean redact)
{
- return String.format("%s = %s", metadata.name.toCQLString(), metadata.type.toCQLString(key));
+ return String.format("%s = %s", metadata.name.toCQLString(), metadata.type.toCQLString(key, redact));
}
public Token getToken()
diff --git a/src/java/org/apache/cassandra/db/MultiPartitionReadQuery.java b/src/java/org/apache/cassandra/db/MultiPartitionReadQuery.java
index ff4d33c03bd6..7f9570b1fe02 100644
--- a/src/java/org/apache/cassandra/db/MultiPartitionReadQuery.java
+++ b/src/java/org/apache/cassandra/db/MultiPartitionReadQuery.java
@@ -28,14 +28,14 @@ public interface MultiPartitionReadQuery extends ReadQuery
{
List ranges();
- default void appendCQLWhereClause(CqlBuilder builder)
+ default void appendCQLWhereClause(CqlBuilder builder, boolean redact)
{
// Append the data ranges.
TableMetadata metadata = metadata();
- boolean hasRanges = appendRanges(builder);
+ boolean hasRanges = appendRanges(builder, redact);
// Append the clustering index filter and the row filter.
- String filter = ranges().get(0).clusteringIndexFilter.toCQLString(metadata, rowFilter());
+ String filter = ranges().get(0).clusteringIndexFilter.toCQLString(metadata, rowFilter() , redact);
if (!filter.isEmpty())
{
if (filter.startsWith("ORDER BY"))
@@ -48,7 +48,7 @@ else if (hasRanges)
}
}
- private boolean appendRanges(CqlBuilder builder)
+ private boolean appendRanges(CqlBuilder builder, boolean redact)
{
List ranges = ranges();
if (ranges.size() == 1)
@@ -57,7 +57,7 @@ private boolean appendRanges(CqlBuilder builder)
if (range.isUnrestricted())
return false;
- String rangeString = range.toCQLString(metadata());
+ String rangeString = range.toCQLString(metadata(), redact);
if (!rangeString.isEmpty())
{
builder.append(" WHERE ").append(rangeString);
@@ -71,7 +71,7 @@ private boolean appendRanges(CqlBuilder builder)
{
if (i > 0)
builder.append(" OR ");
- builder.append(ranges.get(i).toCQLString(metadata()));
+ builder.append(ranges.get(i).toCQLString(metadata(), redact));
}
builder.append(')');
return true;
diff --git a/src/java/org/apache/cassandra/db/MultiRangeReadCommand.java b/src/java/org/apache/cassandra/db/MultiRangeReadCommand.java
index 0ef61e12cb33..4f79f212d378 100644
--- a/src/java/org/apache/cassandra/db/MultiRangeReadCommand.java
+++ b/src/java/org/apache/cassandra/db/MultiRangeReadCommand.java
@@ -321,9 +321,9 @@ public Verb verb()
}
@Override
- public void appendCQLWhereClause(CqlBuilder builder)
+ public void appendCQLWhereClause(CqlBuilder builder, boolean redact)
{
- MultiPartitionReadQuery.super.appendCQLWhereClause(builder);
+ MultiPartitionReadQuery.super.appendCQLWhereClause(builder, redact);
}
@Override
diff --git a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
index 4dbb43cb7856..cb23b91da4d2 100644
--- a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
+++ b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
@@ -394,9 +394,9 @@ public Verb verb()
}
@Override
- public void appendCQLWhereClause(CqlBuilder builder)
+ public void appendCQLWhereClause(CqlBuilder builder, boolean redact)
{
- PartitionRangeReadQuery.super.appendCQLWhereClause(builder);
+ PartitionRangeReadQuery.super.appendCQLWhereClause(builder, redact);
}
@Override
diff --git a/src/java/org/apache/cassandra/db/ReadCommand.java b/src/java/org/apache/cassandra/db/ReadCommand.java
index 1d42f0ae5053..e8a19ae5ae21 100644
--- a/src/java/org/apache/cassandra/db/ReadCommand.java
+++ b/src/java/org/apache/cassandra/db/ReadCommand.java
@@ -607,7 +607,7 @@ private Threshold.GuardedCounter createTombstoneCounter()
Threshold guardrail = shouldRespectTombstoneThresholds()
? Guardrails.scannedTombstones
: DefaultGuardrail.DefaultThreshold.NEVER_TRIGGERED;
- return guardrail.newCounter(ReadCommand.this::toCQLString, true, null);
+ return guardrail.newCounter(ReadCommand.this::toRedactedCQLString, false, null);
}
private MetricRecording()
@@ -671,7 +671,7 @@ private void countTombstone(ClusteringPrefix> clustering)
{
metric.tombstoneFailures.inc();
throw new TombstoneOverwhelmingException(tombstones.get(),
- ReadCommand.this.toCQLString(),
+ ReadCommand.this.toRedactedCQLString(),
ReadCommand.this.metadata(),
currentKey,
clustering);
diff --git a/src/java/org/apache/cassandra/db/ReadExecutionController.java b/src/java/org/apache/cassandra/db/ReadExecutionController.java
index 9968942da038..1ca52dc481b8 100644
--- a/src/java/org/apache/cassandra/db/ReadExecutionController.java
+++ b/src/java/org/apache/cassandra/db/ReadExecutionController.java
@@ -112,7 +112,7 @@ int oldestUnrepairedTombstone()
{
return oldestUnrepairedTombstone;
}
-
+
void updateMinOldestUnrepairedTombstone(int candidate)
{
oldestUnrepairedTombstone = Math.min(oldestUnrepairedTombstone, candidate);
@@ -254,7 +254,7 @@ public boolean isRepairedDataDigestConclusive()
{
return repairedDataInfo.isConclusive();
}
-
+
public RepairedDataInfo getRepairedDataInfo()
{
return repairedDataInfo;
@@ -262,7 +262,7 @@ public RepairedDataInfo getRepairedDataInfo()
private void addSample()
{
- String cql = command.toCQLString();
+ String cql = command.toRedactedCQLString();
int timeMicros = (int) Math.min(TimeUnit.NANOSECONDS.toMicros(clock.now() - createdAtNanos), Integer.MAX_VALUE);
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(baseMetadata.id);
if (cfs != null)
diff --git a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
index ddcb912dc63a..de6700d312ae 100644
--- a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
+++ b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
@@ -1152,9 +1152,9 @@ public Verb verb()
}
@Override
- public void appendCQLWhereClause(CqlBuilder builder)
+ public void appendCQLWhereClause(CqlBuilder builder, boolean redact)
{
- SinglePartitionReadQuery.super.appendCQLWhereClause(builder);
+ SinglePartitionReadQuery.super.appendCQLWhereClause(builder, redact);
}
protected void serializeSelection(DataOutputPlus out, int version) throws IOException
diff --git a/src/java/org/apache/cassandra/db/SinglePartitionReadQuery.java b/src/java/org/apache/cassandra/db/SinglePartitionReadQuery.java
index 1f55368b4db4..e31546954937 100644
--- a/src/java/org/apache/cassandra/db/SinglePartitionReadQuery.java
+++ b/src/java/org/apache/cassandra/db/SinglePartitionReadQuery.java
@@ -159,16 +159,16 @@ default boolean selectsClustering(DecoratedKey key, Clustering> clustering)
return rowFilter().clusteringKeyRestrictionsAreSatisfiedBy(clustering);
}
- default void appendCQLWhereClause(CqlBuilder builder)
+ default void appendCQLWhereClause(CqlBuilder builder, boolean redact)
{
builder.append(" WHERE ");
// Append the partition key restrictions.
TableMetadata metadata = metadata();
- builder.append(partitionKey().toCQLString(metadata));
+ builder.append(partitionKey().toCQLString(metadata, redact));
// Append the clustering index filter and the row filter.
- String filter = clusteringIndexFilter().toCQLString(metadata(), rowFilter());
+ String filter = clusteringIndexFilter().toCQLString(metadata(), rowFilter(), redact);
if (!filter.isEmpty())
{
builder.append(filter.startsWith("ORDER BY") ? " " : " AND ")
diff --git a/src/java/org/apache/cassandra/db/Slices.java b/src/java/org/apache/cassandra/db/Slices.java
index 75ed9b10a374..a9fcd44c06cb 100644
--- a/src/java/org/apache/cassandra/db/Slices.java
+++ b/src/java/org/apache/cassandra/db/Slices.java
@@ -153,9 +153,10 @@ public ClusteringBound> end()
*
* @param metadata the table metadata
* @param rowFilter a row filter
+ * @param redact whether to redact the slice column values
* @return a CQL string representing this slice and the specified {@link RowFilter}
*/
- public abstract String toCQLString(TableMetadata metadata, RowFilter rowFilter);
+ public abstract String toCQLString(TableMetadata metadata, RowFilter rowFilter, boolean redact);
/**
* Checks if this Slices is empty.
@@ -565,7 +566,7 @@ public String toString()
}
@Override
- public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
+ public String toCQLString(TableMetadata metadata, RowFilter rowFilter, boolean redact)
{
StringBuilder sb = new StringBuilder();
@@ -617,7 +618,7 @@ public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
if (values.size() == 1)
{
- sb.append(" = ").append(column.type.toCQLString(first.startValue));
+ sb.append(" = ").append(column.type.toCQLString(first.startValue, redact));
}
else
{
@@ -625,7 +626,7 @@ public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
int j = 0;
for (ByteBuffer value : values)
{
- sb.append(j++ == 0 ? "" : ", ").append(column.type.toCQLString(value));
+ sb.append(j++ == 0 ? "" : ", ").append(column.type.toCQLString(value, redact));
}
sb.append(')');
}
@@ -648,7 +649,7 @@ public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
else
operator = first.startInclusive ? Operator.GTE : Operator.GT;
sb.append(' ').append(operator).append(' ')
- .append(column.type.toCQLString(first.startValue));
+ .append(column.type.toCQLString(first.startValue, redact));
}
if (first.endValue != null)
{
@@ -661,7 +662,7 @@ public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
else
operator = first.endInclusive ? Operator.LTE : Operator.LT;
sb.append(' ').append(operator).append(' ')
- .append(column.type.toCQLString(first.endValue));
+ .append(column.type.toCQLString(first.endValue, redact));
}
}
@@ -676,7 +677,7 @@ public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
// Append the row filter.
if (!rowFilter.isEmpty())
{
- String filter = rowFilter.toCQLString();
+ String filter = rowFilter.toCQLString(redact);
sb.append(filter.startsWith("ORDER BY") ? " " : " AND ");
sb.append(filter);
}
@@ -803,9 +804,9 @@ public String toString()
}
@Override
- public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
+ public String toCQLString(TableMetadata metadata, RowFilter rowFilter, boolean redact)
{
- return rowFilter.toCQLString();
+ return rowFilter.toCQLString(redact);
}
}
@@ -885,7 +886,7 @@ public String toString()
}
@Override
- public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
+ public String toCQLString(TableMetadata metadata, RowFilter rowFilter, boolean redact)
{
return "";
}
diff --git a/src/java/org/apache/cassandra/db/VirtualTablePartitionRangeReadQuery.java b/src/java/org/apache/cassandra/db/VirtualTablePartitionRangeReadQuery.java
index 9e73bfb0907e..1a0ccac58fe1 100644
--- a/src/java/org/apache/cassandra/db/VirtualTablePartitionRangeReadQuery.java
+++ b/src/java/org/apache/cassandra/db/VirtualTablePartitionRangeReadQuery.java
@@ -95,8 +95,8 @@ protected UnfilteredPartitionIterator queryVirtualTable()
}
@Override
- public void appendCQLWhereClause(CqlBuilder builder)
+ public void appendCQLWhereClause(CqlBuilder builder, boolean redact)
{
- PartitionRangeReadQuery.super.appendCQLWhereClause(builder);
+ PartitionRangeReadQuery.super.appendCQLWhereClause(builder, redact);
}
}
diff --git a/src/java/org/apache/cassandra/db/VirtualTableSinglePartitionReadQuery.java b/src/java/org/apache/cassandra/db/VirtualTableSinglePartitionReadQuery.java
index 30b5697accd2..03e4134c3ffd 100644
--- a/src/java/org/apache/cassandra/db/VirtualTableSinglePartitionReadQuery.java
+++ b/src/java/org/apache/cassandra/db/VirtualTableSinglePartitionReadQuery.java
@@ -75,9 +75,9 @@ private VirtualTableSinglePartitionReadQuery(TableMetadata metadata,
}
@Override
- public void appendCQLWhereClause(CqlBuilder builder)
+ public void appendCQLWhereClause(CqlBuilder builder, boolean redact)
{
- SinglePartitionReadQuery.super.appendCQLWhereClause(builder);
+ SinglePartitionReadQuery.super.appendCQLWhereClause(builder, redact);
}
@Override
diff --git a/src/java/org/apache/cassandra/db/filter/ClusteringIndexFilter.java b/src/java/org/apache/cassandra/db/filter/ClusteringIndexFilter.java
index 17f6615c43d4..0c6b2017276c 100644
--- a/src/java/org/apache/cassandra/db/filter/ClusteringIndexFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/ClusteringIndexFilter.java
@@ -159,9 +159,10 @@ static interface InternalDeserializer
*
* @param metadata the table metadata
* @param rowFilter a row filter
+ * @param redact whether to redact the clustering column value
* @return a CQL string representing this clustering index filter and the specified {@link RowFilter}
*/
- String toCQLString(TableMetadata metadata, RowFilter rowFilter);
+ String toCQLString(TableMetadata metadata, RowFilter rowFilter, boolean redact);
public interface Serializer
{
diff --git a/src/java/org/apache/cassandra/db/filter/ClusteringIndexNamesFilter.java b/src/java/org/apache/cassandra/db/filter/ClusteringIndexNamesFilter.java
index 347e88e1c7ff..830094514a54 100644
--- a/src/java/org/apache/cassandra/db/filter/ClusteringIndexNamesFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/ClusteringIndexNamesFilter.java
@@ -163,10 +163,10 @@ public String toString(TableMetadata metadata)
}
@Override
- public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
+ public String toCQLString(TableMetadata metadata, RowFilter rowFilter, boolean redact)
{
if (metadata.clusteringColumns().isEmpty() || clusterings.isEmpty())
- return rowFilter.toCQLString();
+ return rowFilter.toCQLString(redact);
boolean isSingleColumn = metadata.clusteringColumns().size() == 1;
boolean isSingleClustering = clusterings.size() == 1;
@@ -183,7 +183,7 @@ public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
{
sb.append(i++ == 0 ? "" : ", ")
.append(isSingleColumn ? "" : '(')
- .append(clustering.toCQLString(metadata))
+ .append(clustering.toCQLString(metadata, redact))
.append(isSingleColumn ? "" : ')');
maxClusteringSize = Math.max(maxClusteringSize, clustering.size());
@@ -200,7 +200,7 @@ public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
if (!rowFilter.isEmpty())
{
- String filter = rowFilter.toCQLString();
+ String filter = rowFilter.toCQLString(redact);
sb.append(filter.startsWith("ORDER BY") ? " " : " AND ");
sb.append(filter);
}
diff --git a/src/java/org/apache/cassandra/db/filter/ClusteringIndexSliceFilter.java b/src/java/org/apache/cassandra/db/filter/ClusteringIndexSliceFilter.java
index 85820771a6d8..4cc78fc1715b 100644
--- a/src/java/org/apache/cassandra/db/filter/ClusteringIndexSliceFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/ClusteringIndexSliceFilter.java
@@ -134,10 +134,10 @@ public String toString(TableMetadata metadata)
}
@Override
- public String toCQLString(TableMetadata metadata, RowFilter rowFilter)
+ public String toCQLString(TableMetadata metadata, RowFilter rowFilter, boolean redact)
{
StringBuilder sb = new StringBuilder();
- sb.append(slices.toCQLString(metadata, rowFilter));
+ sb.append(slices.toCQLString(metadata, rowFilter, redact));
appendOrderByToCQLString(metadata, sb);
return sb.toString();
}
diff --git a/src/java/org/apache/cassandra/db/filter/ColumnFilter.java b/src/java/org/apache/cassandra/db/filter/ColumnFilter.java
index dbb11ad26250..7fe468593b03 100644
--- a/src/java/org/apache/cassandra/db/filter/ColumnFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/ColumnFilter.java
@@ -385,9 +385,10 @@ public boolean isWildcard()
/**
* Returns the CQL string corresponding to this {@code ColumnFilter}.
*
+ * @param redact whether to redact the queried column names, in case they contain sensitive data.
* @return the CQL string corresponding to this {@code ColumnFilter}.
*/
- public abstract String toCQLString();
+ public abstract String toCQLString(boolean redact);
/**
* Returns the sub-selections or {@code null} if there are none.
@@ -722,7 +723,8 @@ public String toString()
return "*/*";
}
- public String toCQLString()
+ @Override
+ public String toCQLString(boolean redact)
{
return "*";
}
@@ -924,20 +926,22 @@ public String toString()
{
prefix = queried.statics.isEmpty()
? "/"
- : String.format("+%s/", toString(queried.statics.selectOrderIterator(), false));
+ : String.format("+%s/", toString(queried.statics.selectOrderIterator(), false, false));
}
- return prefix + toString(queried.selectOrderIterator(), false);
+ return prefix + toString(queried.selectOrderIterator(), false, false);
}
@Override
- public String toCQLString()
+ public String toCQLString(boolean redact)
{
- return queried.isEmpty() ? "*" : toString(queried.selectOrderIterator(), true);
+ return queried.isEmpty() ? "*" : toString(queried.selectOrderIterator(), true, redact);
}
- private String toString(Iterator columns, boolean cql)
+ private String toString(Iterator columns, boolean cql, boolean redact)
{
+ assert cql || !redact : "Cannot redact non-CQL representation";
+
StringJoiner joiner = cql ? new StringJoiner(", ") : new StringJoiner(", ", "[", "]");
while (columns.hasNext())
@@ -952,7 +956,7 @@ private String toString(Iterator columns, boolean cql)
if (s.isEmpty())
joiner.add(columnName);
else
- s.forEach(subSel -> joiner.add(String.format("%s%s", columnName, subSel.toString(cql))));
+ s.forEach(subSel -> joiner.add(String.format("%s%s", columnName, subSel.toString(cql, redact))));
}
return joiner.toString();
}
diff --git a/src/java/org/apache/cassandra/db/filter/ColumnSubselection.java b/src/java/org/apache/cassandra/db/filter/ColumnSubselection.java
index 1438663a9178..74672deb5476 100644
--- a/src/java/org/apache/cassandra/db/filter/ColumnSubselection.java
+++ b/src/java/org/apache/cassandra/db/filter/ColumnSubselection.java
@@ -91,10 +91,17 @@ public int compareTo(ColumnSubselection other)
@Override
public String toString()
{
- return toString(false);
+ return toString(false, false);
}
- protected abstract String toString(boolean cql);
+ /**
+ * Returns a string representation of this subselection.
+ *
+ * @param cql if true, the string representation will be in CQL format
+ * @param redact if true, the string representation will redact sensitive data
+ * @return a string representation of this subselection
+ */
+ protected abstract String toString(boolean cql, boolean redact);
private static class Slice extends ColumnSubselection
{
@@ -130,13 +137,13 @@ else if (cmp.compare(to, path) < 0)
}
@Override
- protected String toString(boolean cql)
+ protected String toString(boolean cql, boolean redact)
{
// This asserts we're dealing with a collection since that's the only thing it's used for so far.
AbstractType> type = ((CollectionType>)column().type).nameComparator();
return String.format("[%s:%s]",
- from == CellPath.BOTTOM ? "" : (cql ? type.toCQLString(from.get(0)) : type.getString(from.get(0))),
- to == CellPath.TOP ? "" : (cql ? type.toCQLString(to.get(0)) : type.getString(to.get(0))));
+ from == CellPath.BOTTOM ? "" : (cql ? type.toCQLString(from.get(0), redact) : type.getString(from.get(0))),
+ to == CellPath.TOP ? "" : (cql ? type.toCQLString(to.get(0), redact) : type.getString(to.get(0))));
}
}
@@ -166,11 +173,11 @@ public int compareInclusionOf(CellPath path)
}
@Override
- protected String toString(boolean cql)
+ protected String toString(boolean cql, boolean redact)
{
- // This assert we're dealing with a collection since that's the only thing it's used for so far.
+ // This asserts we're dealing with a collection since that's the only thing it's used for so far.
AbstractType> type = ((CollectionType>)column().type).nameComparator();
- return String.format("[%s]", cql ? type.toCQLString(element.get(0)) : type.getString(element.get(0)));
+ return String.format("[%s]", cql ? type.toCQLString(element.get(0), redact) : type.getString(element.get(0)));
}
}
diff --git a/src/java/org/apache/cassandra/db/filter/RowFilter.java b/src/java/org/apache/cassandra/db/filter/RowFilter.java
index f4446901f8fb..b8a7aa65f428 100644
--- a/src/java/org/apache/cassandra/db/filter/RowFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/RowFilter.java
@@ -335,9 +335,9 @@ public String toString()
return root.toString();
}
- public String toCQLString()
+ public String toCQLString(boolean redact)
{
- return root.toCQLString();
+ return root.toCQLString(redact);
}
public static Builder builder()
@@ -739,10 +739,10 @@ private int numFilteredValues()
@Override
public String toString()
{
- return toCQLString();
+ return toCQLString(false);
}
- public String toCQLString()
+ public String toCQLString(boolean redact)
{
StringBuilder sb = new StringBuilder();
for (Expression expression : expressions)
@@ -751,14 +751,14 @@ public String toCQLString()
continue;
if (sb.length() > 0)
sb.append(isDisjunction ? " OR " : " AND ");
- sb.append(expression.toCQLString());
+ sb.append(expression.toCQLString(redact));
}
for (FilterElement child : children)
{
if (sb.length() > 0)
sb.append(isDisjunction ? " OR " : " AND ");
sb.append('(');
- sb.append(child.toCQLString());
+ sb.append(child.toCQLString(redact));
sb.append(')');
}
for (Expression expression : expressions)
@@ -767,7 +767,7 @@ public String toCQLString()
continue;
if (sb.length() > 0)
sb.append(' ');
- sb.append(expression.toCQLString());
+ sb.append(expression.toCQLString(redact));
}
return sb.toString();
}
@@ -1053,10 +1053,10 @@ public int hashCode()
@Override
public String toString()
{
- return toCQLString();
+ return toCQLString(false);
}
- public String toCQLString()
+ public String toCQLString(boolean redact)
{
return "";
}
@@ -1366,7 +1366,7 @@ private boolean containsKey(TableMetadata metadata, DecoratedKey partitionKey, R
}
@Override
- public String toCQLString()
+ public String toCQLString(boolean redact)
{
AbstractType> type = column.type;
switch (operator)
@@ -1391,19 +1391,33 @@ public String toCQLString()
// These don't have a value, so we return here to prevent an error calling type.getString(value)
return String.format("ORDER BY %s %s", column.name.toCQLString(), operator);
case ANN:
- return String.format("ORDER BY %s ANN OF %s", column.name.toCQLString(), valueAsCQLString(type, value));
+ return String.format("ORDER BY %s ANN OF %s", column.name.toCQLString(), truncateValue(type.toCQLString(value, redact)));
+ case LIKE_PREFIX:
+ return likeToCQLString("'%s%%'", type, redact);
+ case LIKE_SUFFIX:
+ return likeToCQLString("'%%%s'", type, redact);
+ case LIKE_CONTAINS:
+ return likeToCQLString("'%%%s%%'", type, redact);
+ case LIKE_MATCHES:
+ return likeToCQLString("'%s'", type, redact);
default:
break;
}
- return String.format("%s %s %s", column.name.toCQLString(), operator, valueAsCQLString(type, value));
+ return String.format("%s %s %s", column.name.toCQLString(), operator, truncateValue(type.toCQLString(value, redact)));
+ }
+
+ private String likeToCQLString(String pattern, AbstractType> type, boolean redact)
+ {
+ if (redact)
+ return String.format("%s LIKE ?", column.name.toCQLString());
+
+ String stringValue = String.format(pattern, type.getString(value));
+ return String.format("%s LIKE %s", column.name.toCQLString(), truncateValue(stringValue));
}
- private static String valueAsCQLString(AbstractType> type, ByteBuffer value)
+ private static String truncateValue(String value)
{
- var valueString = type.toCQLString(value);
- if (valueString.length() > 9)
- valueString = valueString.substring(0, 6) + "...";
- return valueString;
+ return value.length() > 9 ? value.substring(0, 6) + "..." : value;
}
@Override
@@ -1515,14 +1529,14 @@ private boolean isSatisfiedByEq(TableMetadata metadata, DecoratedKey partitionKe
}
@Override
- public String toCQLString()
+ public String toCQLString(boolean redact)
{
MapType, ?> mt = (MapType, ?>) column.type;
return String.format("%s[%s] %s %s",
column.name.toCQLString(),
- mt.nameComparator().toCQLString(key),
+ mt.nameComparator().toCQLString(key, redact),
operator,
- mt.valueComparator().toCQLString(value));
+ mt.valueComparator().toCQLString(value, redact));
}
@Override
@@ -1689,13 +1703,13 @@ public boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey,
}
@Override
- public String toCQLString()
+ public String toCQLString(boolean redact)
{
return String.format("GEO_DISTANCE(%s, %s) %s %s",
column.name.toCQLString(),
- column.type.toCQLString(value),
+ column.type.toCQLString(value, redact),
distanceOperator,
- FloatType.instance.toCQLString(distance));
+ FloatType.instance.toCQLString(distance, redact));
}
@Override
@@ -1769,7 +1783,7 @@ public ByteBuffer getValue()
}
@Override
- public String toCQLString()
+ public String toCQLString(boolean redact)
{
return String.format("expr(%s, %s)",
targetIndex.name,
diff --git a/src/java/org/apache/cassandra/db/marshal/AbstractType.java b/src/java/org/apache/cassandra/db/marshal/AbstractType.java
index db415465c853..d82de63eea66 100644
--- a/src/java/org/apache/cassandra/db/marshal/AbstractType.java
+++ b/src/java/org/apache/cassandra/db/marshal/AbstractType.java
@@ -183,13 +183,20 @@ public ByteBuffer decompose(T value)
}
/**
- * Generates a CQL literal representing the specified binary value.
+ * Returns a CQL literal representing the specified binary value, or "?" if redaction is requested.
*
- * @param bytes the value to convert to a CQL literal.
+ * @param bytes the value to convert to a CQL literal
+ * @param redact whether to mask the value with '?' (for redaction purposes)
*/
- public String toCQLString(ByteBuffer bytes)
+ public String toCQLString(ByteBuffer bytes, boolean redact)
{
- return bytes == null ? "null" : asCQL3Type().toCQLLiteral(bytes, ProtocolVersion.CURRENT);
+ if (redact)
+ return "?";
+
+ if (bytes == null)
+ return "null";
+
+ return asCQL3Type().toCQLLiteral(bytes, ProtocolVersion.CURRENT);
}
/** get a string representation of the bytes used for various identifier (NOT just for log messages) */
diff --git a/src/java/org/apache/cassandra/db/monitoring/MonitoringTask.java b/src/java/org/apache/cassandra/db/monitoring/MonitoringTask.java
index 0f8555f17aa3..44f2c32cfb8c 100644
--- a/src/java/org/apache/cassandra/db/monitoring/MonitoringTask.java
+++ b/src/java/org/apache/cassandra/db/monitoring/MonitoringTask.java
@@ -47,7 +47,7 @@
* We also log timed out operations, see CASSANDRA-7392.
* Since CASSANDRA-12403 we also log queries that were slow.
*/
-class MonitoringTask
+public class MonitoringTask
{
private static final String LINE_SEPARATOR = getProperty("line.separator");
private static final Logger logger = LoggerFactory.getLogger(MonitoringTask.class);
@@ -65,7 +65,7 @@ class MonitoringTask
private static final int MAX_OPERATIONS = Integer.parseInt(System.getProperty(Config.PROPERTY_PREFIX + "monitoring_max_operations", "50"));
@VisibleForTesting
- static MonitoringTask instance = make(REPORT_INTERVAL_MS, MAX_OPERATIONS);
+ public static MonitoringTask instance = make(REPORT_INTERVAL_MS, MAX_OPERATIONS);
private final ScheduledFuture> reportingTask;
private final OperationsQueue failedOperationsQueue;
@@ -133,7 +133,7 @@ private List getLogMessages(AggregatedOperations operations)
}
@VisibleForTesting
- private void logOperations(long approxCurrentTimeNanos)
+ public void logOperations(long approxCurrentTimeNanos)
{
logSlowOperations(approxCurrentTimeNanos);
logFailedOperations(approxCurrentTimeNanos);
diff --git a/src/java/org/apache/cassandra/db/rows/AbstractRow.java b/src/java/org/apache/cassandra/db/rows/AbstractRow.java
index 74f111a4ed23..2990e6449c94 100644
--- a/src/java/org/apache/cassandra/db/rows/AbstractRow.java
+++ b/src/java/org/apache/cassandra/db/rows/AbstractRow.java
@@ -140,10 +140,10 @@ public String toString(TableMetadata metadata, boolean includeClusterKeys, boole
sb.append(" ]");
}
sb.append(": ");
- if(includeClusterKeys)
+ if (includeClusterKeys)
sb.append(clustering().toString(metadata));
else
- sb.append(clustering().toCQLString(metadata));
+ sb.append(clustering().toCQLString(metadata, false));
sb.append(" | ");
boolean isFirst = true;
for (ColumnData cd : this)
diff --git a/src/java/org/apache/cassandra/index/sai/plan/Plan.java b/src/java/org/apache/cassandra/index/sai/plan/Plan.java
index 668d1d4cf22a..9623bfe5efd8 100644
--- a/src/java/org/apache/cassandra/index/sai/plan/Plan.java
+++ b/src/java/org/apache/cassandra/index/sai/plan/Plan.java
@@ -283,21 +283,51 @@ enum ControlFlow { Continue, Break }
*/
protected abstract double estimateSelectivity();
+ /**
+ * Formats the whole plan as a pretty tree, redacting the queried column values.
+ */
+ public final String toRedactedStringRecursive()
+ {
+ return toStringRecursive(true);
+ }
+
+ /**
+ * Formats the whole plan as a pretty tree, not redacting the queried column values.
+ */
+ public final String toUnredactedStringRecursive()
+ {
+ return toStringRecursive(false);
+ }
+
/**
* Formats the whole plan as a pretty tree
+ *
+ * @param redact whether to redact the queried column values.
*/
- public final String toStringRecursive()
+ private final String toStringRecursive(boolean redact)
{
- TreeFormatter formatter = new TreeFormatter<>(Plan::toString, Plan::subplans);
+ TreeFormatter formatter = new TreeFormatter<>(plan -> plan.toString(redact), Plan::subplans);
return formatter.format(this);
}
/**
- * Returns the string representation of this node only
+ * Returns the string representation of this node only, without redacting the queried column values.
+ * @see #toString(boolean)
*/
+ @Override
public final String toString()
{
- String title = title();
+ return toString(false);
+ }
+
+ /**
+ * Returns the string representation of this node only
+ *
+ * @param redact whether to redact the queried column values.
+ */
+ public final String toString(boolean redact)
+ {
+ String title = title(redact);
String description = description();
return (title.isEmpty())
? String.format("%s (%s)\n%s", getClass().getSimpleName(), cost(), description).stripTrailing()
@@ -306,17 +336,17 @@ public final String toString()
/**
* Returns additional information specific to the node displayed in the first line.
- * The information is included in the output of {@link #toString()} and {@link #toStringRecursive()}.
+ * The information is included in the output of {@link #toString()} and {@link #toRedactedStringRecursive()}.
* It is up to subclasses to implement it.
*/
- protected String title()
+ protected String title(boolean redact)
{
return "";
}
/**
* Returns additional information specific to the node, displayed below the title.
- * The information is included in the output of {@link #toString()} and {@link #toStringRecursive()}.
+ * The information is included in the output of {@link #toString()} and {@link #toRedactedStringRecursive()}.
* It is up to subclasses to implement it.
*/
protected String description()
@@ -345,7 +375,7 @@ protected String description()
public final Plan optimize()
{
if (logger.isTraceEnabled())
- logger.trace("Optimizing plan:\n{}", this.toStringRecursive());
+ logger.trace("Optimizing plan:\n{}", toRedactedStringRecursive());
Plan bestPlanSoFar = this;
List leaves = nodesOfType(Leaf.class);
@@ -360,14 +390,14 @@ public final Plan optimize()
Plan candidate = bestPlanSoFar.removeRestriction(leaf.id);
if (logger.isTraceEnabled())
- logger.trace("Candidate query plan:\n{}", candidate.toStringRecursive());
+ logger.trace("Candidate query plan:\n{}", candidate.toRedactedStringRecursive());
if (candidate.fullCost() <= bestPlanSoFar.fullCost())
bestPlanSoFar = candidate;
}
if (logger.isTraceEnabled())
- logger.trace("Optimized plan:\n{}", bestPlanSoFar.toStringRecursive());
+ logger.trace("Optimized plan:\n{}", bestPlanSoFar.toRedactedStringRecursive());
return bestPlanSoFar;
}
@@ -796,7 +826,7 @@ public IndexScan(Factory factory, int id, Expression predicate, long matchingKey
}
@Override
- protected final String title()
+ protected final String title(boolean redact)
{
return String.format("of %s (sel: %.9f, step: %.1f)",
getIndexName(), selectivity(), access.meanDistance());
@@ -1689,9 +1719,9 @@ protected Filter withAccess(Access access)
}
@Override
- protected String title()
+ protected String title(boolean redact)
{
- return String.format("%s (sel: %.9f)", filter, selectivity() / source.get().selectivity());
+ return String.format("%s (sel: %.9f)", filter.toCQLString(redact), selectivity() / source.get().selectivity());
}
}
@@ -1757,7 +1787,7 @@ protected RowsIteration withAccess(Access access)
}
@Override
- protected String title()
+ protected String title(boolean redact)
{
return "" + limit;
}
diff --git a/src/java/org/apache/cassandra/index/sai/plan/QueryController.java b/src/java/org/apache/cassandra/index/sai/plan/QueryController.java
index e8eb24cb1007..df7f8c3dcffd 100644
--- a/src/java/org/apache/cassandra/index/sai/plan/QueryController.java
+++ b/src/java/org/apache/cassandra/index/sai/plan/QueryController.java
@@ -415,11 +415,11 @@ Plan buildPlan()
updateIndexMetricsQueriesCount(plan);
if (logger.isTraceEnabled())
- logger.trace("Query execution plan:\n" + plan.toStringRecursive());
+ logger.trace("Query execution plan:\n" + plan.toRedactedStringRecursive());
if (Tracing.isTracing())
{
- Tracing.trace("Query execution plan:\n" + plan.toStringRecursive());
+ Tracing.trace("Query execution plan:\n" + plan.toUnredactedStringRecursive());
List origIndexScans = keysIterationPlan.nodesOfType(Plan.IndexScan.class);
List selectedIndexScans = plan.nodesOfType(Plan.IndexScan.class);
Tracing.trace("Selecting {} {} of {} out of {} indexes",
diff --git a/src/java/org/apache/cassandra/service/context/DefaultOperationContext.java b/src/java/org/apache/cassandra/service/context/DefaultOperationContext.java
index f22d8e9dd6fc..f5916ff66a8c 100644
--- a/src/java/org/apache/cassandra/service/context/DefaultOperationContext.java
+++ b/src/java/org/apache/cassandra/service/context/DefaultOperationContext.java
@@ -58,7 +58,7 @@ static class Factory implements OperationContext.Factory
@Override
public OperationContext forRead(ReadCommand command, ColumnFamilyStore cfs)
{
- return new DefaultOperationContext(command::toCQLString);
+ return new DefaultOperationContext(command::toUnredactedCQLString);
}
}
}
diff --git a/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java b/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java
index 56c53d7b7a85..32ccccc119f7 100644
--- a/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java
+++ b/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java
@@ -196,17 +196,25 @@ public void close()
// of cached rows we have had during the query.
if (!hitFailureThreshold && maxRowsCached > cachedRowsWarnThreshold)
{
- String message = String.format("Replica filtering protection has cached up to %d rows during query %s, " +
- "which is over the warning threshold of %d rows defined by " +
- "'cached_replica_rows_warn_threshold' in cassandra.yaml.",
- maxRowsCached, command.toCQLString(), cachedRowsWarnThreshold);
-
- ClientWarn.instance.warn(message);
- oneMinuteLogger.warn(message);
- Tracing.trace(message);
+ String clearMessage = cachedRowsWarnMessage(false);
+ String redactedMessage = cachedRowsWarnMessage(true);
+
+ ClientWarn.instance.warn(clearMessage);
+ oneMinuteLogger.warn(redactedMessage);
+ Tracing.trace(clearMessage);
}
}
+ private String cachedRowsWarnMessage(boolean redact)
+ {
+ return String.format("Replica filtering protection has cached up to %d rows during query %s, " +
+ "which is over the warning threshold of %d rows defined by " +
+ "'cached_replica_rows_warn_threshold' in cassandra.yaml.",
+ maxRowsCached,
+ redact ? command.toRedactedCQLString() : command.toUnredactedCQLString(),
+ cachedRowsWarnThreshold);
+ }
+
@Override
public UnfilteredRowIterators.MergeListener getRowMergeListener(DecoratedKey partitionKey, List versions)
{
@@ -286,17 +294,25 @@ private void incrementCachedRows()
if (currentRowsCached == cachedRowsFailThreshold + 1)
{
hitFailureThreshold = true;
- String message = String.format("Replica filtering protection has cached %d rows during query %s, " +
- "which is over the failure threshold of %d rows defined by " +
- "'cached_replica_rows_fail_threshold' in cassandra.yaml.",
- currentRowsCached, command.toCQLString(), cachedRowsFailThreshold);
-
- logger.error(message);
- Tracing.trace(message);
- throw new OverloadedException(message);
+ String clearMessage = cachedRowsFailMessage(false);
+ String redactedMessage = cachedRowsFailMessage(true);
+
+ logger.error(redactedMessage);
+ Tracing.trace(clearMessage);
+ throw new OverloadedException(redactedMessage);
}
}
+ private String cachedRowsFailMessage(boolean redact)
+ {
+ return String.format("Replica filtering protection has cached %d rows during query %s, " +
+ "which is over the failure threshold of %d rows defined by " +
+ "'cached_replica_rows_fail_threshold' in cassandra.yaml.",
+ currentRowsCached,
+ redact ? command.toRedactedCQLString() : command.toUnredactedCQLString(),
+ cachedRowsFailThreshold);
+ }
+
private void releaseCachedRows(int count)
{
maxRowsCached = Math.max(currentRowsCached, maxRowsCached);
diff --git a/src/java/org/apache/cassandra/service/reads/repair/ReadRepairEvent.java b/src/java/org/apache/cassandra/service/reads/repair/ReadRepairEvent.java
index 5cec8025ea14..b98ee56ebea1 100644
--- a/src/java/org/apache/cassandra/service/reads/repair/ReadRepairEvent.java
+++ b/src/java/org/apache/cassandra/service/reads/repair/ReadRepairEvent.java
@@ -22,7 +22,6 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -66,7 +65,7 @@ enum ReadRepairEventType
{
this.keyspace = readRepair.cfs.keyspace;
this.tableName = readRepair.cfs.getTableName();
- this.cqlCommand = readRepair.command.toCQLString();
+ this.cqlCommand = readRepair.command.toRedactedCQLString();
this.consistency = readRepair.replicaPlan().consistencyLevel();
this.speculativeRetry = readRepair.cfs.metadata().params.speculativeRetry.kind();
this.destinations = destinations;
diff --git a/test/distributed/org/apache/cassandra/distributed/test/SlowQueryLoggerTest.java b/test/distributed/org/apache/cassandra/distributed/test/SlowQueryLoggerTest.java
new file mode 100644
index 000000000000..6dc8889837c0
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/distributed/test/SlowQueryLoggerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright DataStax, Inc.
+ *
+ * Licensed 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.distributed.test;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.function.Consumer;
+
+import org.junit.Test;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
+import org.apache.cassandra.db.ReadCommand;
+import org.apache.cassandra.db.ReadExecutionController;
+import org.apache.cassandra.db.monitoring.MonitoringTask;
+import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.ICoordinator;
+import org.apache.cassandra.distributed.api.IInvokableInstance;
+import org.apache.cassandra.utils.Throwables;
+import org.assertj.core.api.AbstractIterableAssert;
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.ListAssert;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL;
+import static org.apache.cassandra.utils.MonotonicClock.approxTime;
+
+public class SlowQueryLoggerTest extends TestBaseImpl
+{
+ private static final String TABLE = "t";
+ private static final int SLOW_QUERY_LOG_TIMEOUT_MS = 100;
+
+ /**
+ * Test that the slow query logger does not log sensitive data.
+ */
+ @Test
+ public void testDoesNotLogSensitiveData() throws Throwable
+ {
+ // effectively disable the scheduled monitoring task so we control it manually for better test stability
+ System.setProperty("cassandra.slow_query_log_interval_in_ms", "3600000");
+
+ try (Cluster cluster = init(Cluster.build(2)
+ .withInstanceInitializer(SlowQueryLoggerTest.BBHelper::install)
+ .withConfig(config -> config.set("slow_query_log_timeout_in_ms", SLOW_QUERY_LOG_TIMEOUT_MS))
+ .start()))
+ {
+ ICoordinator coordinator = cluster.coordinator(1);
+ IInvokableInstance node = cluster.get(2);
+
+ cluster.schemaChange(format("CREATE TABLE %s.%s (k text, c text, v text, PRIMARY KEY (k, c))"));
+ coordinator.execute(format("INSERT INTO %s.%s (k, c, v) VALUES ('secret_k', 'secret_c', 'secret_v')"), ALL);
+
+ long mark = node.logs().mark();
+ coordinator.execute(format("SELECT * FROM %s.%s WHERE k = 'secret_k' AND c = 'secret_c' AND v = 'secret_v' ALLOW FILTERING"), ALL);
+ node.runOnInstance(() -> MonitoringTask.instance.logOperations(approxTime.now()));
+
+ assertLogsContain(mark, node, "Some operations were slow", format("