From 865b67b21d326728936f0b80681129c73a2e374a Mon Sep 17 00:00:00 2001 From: jacek-lewandowski Date: Mon, 1 Mar 2021 12:24:28 +0000 Subject: [PATCH] Fix ColumnFilter behaviour to prevent digest mitmatches during upgrades MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit patch by Jacek Lewandowski; reviewed by Andrés de la Peña and Yifan Cai for CASSANDRA-16415 --- CHANGES.txt | 1 + .../cassandra/db/filter/ColumnFilter.java | 82 +--- .../test/ReadDigestConsistencyTest.java | 113 ++++++ .../cassandra/db/filter/ColumnFilterTest.java | 360 ++++++++++++++++-- 4 files changed, 462 insertions(+), 94 deletions(-) create mode 100644 test/distributed/org/apache/cassandra/distributed/test/ReadDigestConsistencyTest.java diff --git a/CHANGES.txt b/CHANGES.txt index 9f0a5a7ab8..c174cd3ef2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.0.25: + * Fix ColumnFilter behaviour to prevent digest mitmatches during upgrades (CASSANDRA-16415) * Update debian packaging for python3 (CASSANDRA-16396) * Avoid pushing schema mutations when setting up distributed system keyspaces locally (CASSANDRA-16387) Merged from 2.2: diff --git a/src/java/org/apache/cassandra/db/filter/ColumnFilter.java b/src/java/org/apache/cassandra/db/filter/ColumnFilter.java index 858c9441d5..520c43cc1a 100644 --- a/src/java/org/apache/cassandra/db/filter/ColumnFilter.java +++ b/src/java/org/apache/cassandra/db/filter/ColumnFilter.java @@ -26,7 +26,6 @@ import com.google.common.collect.TreeMultimap; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.db.*; -import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.io.util.DataInputPlus; @@ -141,44 +140,6 @@ public class ColumnFilter return false; } - /** - * Whether the provided cell of a complex column is selected by this selection. - */ - public boolean includes(Cell cell) - { - if (isFetchAll || subSelections == null || !cell.column().isComplex()) - return true; - - SortedSet s = subSelections.get(cell.column().name); - if (s.isEmpty()) - return true; - - for (ColumnSubselection subSel : s) - if (subSel.compareInclusionOf(cell.path()) == 0) - return true; - - return false; - } - - /** - * Whether we can skip the value of the cell of a complex column. - */ - public boolean canSkipValue(ColumnDefinition column, CellPath path) - { - if (!isFetchAll || subSelections == null || !column.isComplex()) - return false; - - SortedSet s = subSelections.get(column.name); - if (s.isEmpty()) - return false; - - for (ColumnSubselection subSel : s) - if (subSel.compareInclusionOf(path) == 0) - return false; - - return true; - } - /** * Creates a new {@code Tester} to efficiently test the inclusion of cells of complex column * {@code column}. @@ -340,44 +301,31 @@ public class ColumnFilter Objects.equals(otherCf.subSelections, this.subSelections); } + @Override public String toString() { + String prefix = ""; + if (isFetchAll) - return "*"; + return "*/*"; if (queried.isEmpty()) - return ""; + return prefix + "[]"; - Iterator defs = queried.selectOrderIterator(); - if (!defs.hasNext()) - return ""; - - StringBuilder sb = new StringBuilder(); - appendColumnDef(sb, defs.next()); - while (defs.hasNext()) - appendColumnDef(sb.append(", "), defs.next()); - return sb.toString(); - } - - private void appendColumnDef(StringBuilder sb, ColumnDefinition column) - { - if (subSelections == null) + StringJoiner joiner = new StringJoiner(", ", "[", "]"); + Iterator it = queried.selectOrderIterator(); + while (it.hasNext()) { - sb.append(column.name); - return; - } + ColumnDefinition column = it.next(); + SortedSet s = subSelections != null ? subSelections.get(column.name) : Collections.emptySortedSet(); - SortedSet s = subSelections.get(column.name); - if (s.isEmpty()) - { - sb.append(column.name); - return; + if (s.isEmpty()) + joiner.add(String.valueOf(column.name)); + else + s.forEach(subSel -> joiner.add(String.format("%s%s", column.name, subSel))); } - - int i = 0; - for (ColumnSubselection subSel : s) - sb.append(i++ == 0 ? "" : ", ").append(column.name).append(subSel); + return prefix + joiner.toString(); } public static class Serializer diff --git a/test/distributed/org/apache/cassandra/distributed/test/ReadDigestConsistencyTest.java b/test/distributed/org/apache/cassandra/distributed/test/ReadDigestConsistencyTest.java new file mode 100644 index 0000000000..8071eea5d9 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/ReadDigestConsistencyTest.java @@ -0,0 +1,113 @@ +/* + * 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.distributed.test; + +import java.util.Arrays; +import java.util.UUID; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.api.ConsistencyLevel; +import org.apache.cassandra.distributed.api.ICoordinator; + +public class ReadDigestConsistencyTest extends TestBaseImpl +{ + public static final String TABLE_NAME = "tbl"; + public static final String CREATE_TABLE = String.format("CREATE TABLE %s.%s (" + + " k int, " + + " c int, " + + " s1 int static, " + + " s2 set static, " + + " v1 int, " + + " v2 set, " + + " PRIMARY KEY (k, c))", KEYSPACE, TABLE_NAME); + + public static final String INSERT = String.format("INSERT INTO %s.%s (k, c, s1, s2, v1, v2) VALUES (?, ?, ?, ?, ?, ?)", KEYSPACE, TABLE_NAME); + + + public static final String SELECT_TRACE = "SELECT activity FROM system_traces.events where session_id = ? and source = ? ALLOW FILTERING;"; + + @Test + public void testDigestConsistency() throws Exception + { + try (Cluster cluster = init(builder().withNodes(2).start())) + { + cluster.schemaChange(CREATE_TABLE); + insertData(cluster.coordinator(1)); + testDigestConsistency(cluster.coordinator(1)); + testDigestConsistency(cluster.coordinator(2)); + } + } + + public static void checkTraceForDigestMismatch(ICoordinator coordinator, String query, Object... boundValues) + { + UUID sessionId = UUID.randomUUID(); + coordinator.executeWithTracing(sessionId, query, ConsistencyLevel.ALL, boundValues); + Object[][] results = coordinator.execute(SELECT_TRACE, + ConsistencyLevel.ALL, + sessionId, + coordinator.instance().broadcastAddress().getAddress()); + for (Object[] result : results) + { + String activity = (String) result[0]; + Assert.assertFalse(String.format("Found Digest Mismatch while executing query: %s with bound values %s on %s/%s", + query, + Arrays.toString(boundValues), + coordinator.instance().broadcastAddress(), + coordinator.instance().getReleaseVersionString()), + activity.toLowerCase().contains("mismatch for key")); + } + } + + public static void insertData(ICoordinator coordinator) + { + coordinator.execute(String.format("INSERT INTO %s.%s (k, c, s1, s2, v1, v2) VALUES (1, 1, 2, {1, 2, 3, 4, 5}, 3, {6, 7, 8, 9, 10})", KEYSPACE, TABLE_NAME), ConsistencyLevel.ALL); + coordinator.execute(String.format("INSERT INTO %s.%s (k, c, s1, s2, v1, v2) VALUES (1, 2, 3, {2, 3, 4, 5, 6}, 4, {7, 8, 9, 10, 11})", KEYSPACE, TABLE_NAME), ConsistencyLevel.ALL); + } + + public static void testDigestConsistency(ICoordinator coordinator) + { + String queryPattern = "SELECT %s FROM %s.%s WHERE %s"; + String[] columnss1 = { + "k, c", + "*", + "v1", + "v2", + "v1, s1", + "v1, s2" + }; + + String[] columnss2 = { + "s1", + "s2" + }; + + for (String columns : columnss1) + { + checkTraceForDigestMismatch(coordinator, String.format(queryPattern, columns, KEYSPACE, TABLE_NAME, "k = 1")); + checkTraceForDigestMismatch(coordinator, String.format(queryPattern, columns, KEYSPACE, TABLE_NAME, "k = 1 AND c = 2")); + } + for (String columns : columnss2) + { + checkTraceForDigestMismatch(coordinator, String.format(queryPattern, columns, KEYSPACE, TABLE_NAME, "k = 1")); + } + } +} diff --git a/test/unit/org/apache/cassandra/db/filter/ColumnFilterTest.java b/test/unit/org/apache/cassandra/db/filter/ColumnFilterTest.java index aa560915c1..80c1e42f23 100644 --- a/test/unit/org/apache/cassandra/db/filter/ColumnFilterTest.java +++ b/test/unit/org/apache/cassandra/db/filter/ColumnFilterTest.java @@ -18,12 +18,19 @@ package org.apache.cassandra.db.filter; +import java.io.IOException; +import java.util.function.Consumer; + +import com.google.common.base.Throwables; +import org.junit.Assert; import org.junit.Test; -import junit.framework.Assert; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.db.PartitionColumns; import org.apache.cassandra.db.marshal.Int32Type; +import org.apache.cassandra.db.marshal.SetType; +import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.dht.Murmur3Partitioner; import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataInputPlus; @@ -31,40 +38,339 @@ import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.utils.ByteBufferUtil; +import static org.junit.Assert.assertEquals; + public class ColumnFilterTest { - final static ColumnFilter.Serializer serializer = new ColumnFilter.Serializer(); + private static final ColumnFilter.Serializer serializer = new ColumnFilter.Serializer(); + + private final CFMetaData metadata = CFMetaData.Builder.create("ks", "table") + .withPartitioner(Murmur3Partitioner.instance) + .addPartitionKey("pk", Int32Type.instance) + .addClusteringColumn("ck", Int32Type.instance) + .addStaticColumn("s1", Int32Type.instance) + .addStaticColumn("s2", SetType.getInstance(Int32Type.instance, true)) + .addRegularColumn("v1", Int32Type.instance) + .addRegularColumn("v2", SetType.getInstance(Int32Type.instance, true)) + .build(); + + private final ColumnDefinition s1 = metadata.getColumnDefinition(ByteBufferUtil.bytes("s1")); + private final ColumnDefinition s2 = metadata.getColumnDefinition(ByteBufferUtil.bytes("s2")); + private final ColumnDefinition v1 = metadata.getColumnDefinition(ByteBufferUtil.bytes("v1")); + private final ColumnDefinition v2 = metadata.getColumnDefinition(ByteBufferUtil.bytes("v2")); + private final CellPath path0 = CellPath.create(ByteBufferUtil.bytes(0)); + private final CellPath path1 = CellPath.create(ByteBufferUtil.bytes(1)); + private final CellPath path2 = CellPath.create(ByteBufferUtil.bytes(2)); + private final CellPath path3 = CellPath.create(ByteBufferUtil.bytes(3)); + private final CellPath path4 = CellPath.create(ByteBufferUtil.bytes(4)); + + // Select all @Test - public void columnFilterSerialisationRoundTrip() throws Exception + public void testSelectAll() { - CFMetaData metadata = CFMetaData.Builder.create("ks", "table") - .withPartitioner(Murmur3Partitioner.instance) - .addPartitionKey("pk", Int32Type.instance) - .addClusteringColumn("ck", Int32Type.instance) - .addRegularColumn("v1", Int32Type.instance) - .addRegularColumn("v2", Int32Type.instance) - .addRegularColumn("v3", Int32Type.instance) - .build(); + Consumer check = filter -> { + testRoundTrips(filter); + assertEquals("*/*", filter.toString()); + assertFetchedQueried(true, true, filter, v1, v2, s1, s2); + assertCellFetchedQueried(true, true, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(true, true, filter, s2, path0, path1, path2, path3, path4); + }; - ColumnDefinition v1 = metadata.getColumnDefinition(ByteBufferUtil.bytes("v1")); - - testRoundTrip(ColumnFilter.all(metadata), metadata, MessagingService.VERSION_30); - testRoundTrip(ColumnFilter.all(metadata), metadata, MessagingService.VERSION_3014); - - testRoundTrip(ColumnFilter.selection(metadata.partitionColumns().without(v1)), metadata, MessagingService.VERSION_30); - testRoundTrip(ColumnFilter.selection(metadata.partitionColumns().without(v1)), metadata, MessagingService.VERSION_3014); - - testRoundTrip(ColumnFilter.selection(metadata, metadata.partitionColumns().without(v1)), metadata, MessagingService.VERSION_30); - testRoundTrip(ColumnFilter.selection(metadata, metadata.partitionColumns().without(v1)), metadata, MessagingService.VERSION_3014); + check.accept(ColumnFilter.all(metadata)); + check.accept(ColumnFilter.allColumnsBuilder(metadata).build()); } - static void testRoundTrip(ColumnFilter columnFilter, CFMetaData metadata, int version) throws Exception + // Selections + + @Test + public void testSelectNothing() { - DataOutputBuffer output = new DataOutputBuffer(); - serializer.serialize(columnFilter, output, version); - Assert.assertEquals(serializer.serializedSize(columnFilter, version), output.position()); - DataInputPlus input = new DataInputBuffer(output.buffer(), false); - Assert.assertEquals(serializer.deserialize(input, version, metadata), columnFilter); + Consumer check = filter -> { + testRoundTrips(filter); + assertEquals("[]", filter.toString()); + assertFetchedQueried(false, false, filter, v1, v2, s1, s2); + assertCellFetchedQueried(false, false, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(false, false, filter, s2, path0, path1, path2, path3, path4); + }; + + check.accept(ColumnFilter.selection(PartitionColumns.NONE)); + check.accept(ColumnFilter.selectionBuilder().build()); + } + + @Test + public void testSelectSimpleColumn() + { + Consumer check = filter -> { + testRoundTrips(filter); + assertEquals("[v1]", filter.toString()); + assertFetchedQueried(true, true, filter, v1); + assertFetchedQueried(false, false, filter, v2, s1, s2); + assertCellFetchedQueried(false, false, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(false, false, filter, s2, path0, path1, path2, path3, path4); + }; + + check.accept(ColumnFilter.selection(PartitionColumns.builder().add(v1).build())); + check.accept(ColumnFilter.selectionBuilder().add(v1).build()); + } + + @Test + public void testSelectComplexColumn() + { + Consumer check = filter -> { + testRoundTrips(filter); + assertEquals("[v2]", filter.toString()); + assertFetchedQueried(true, true, filter, v2); + assertFetchedQueried(false, false, filter, v1, s1, s2); + assertCellFetchedQueried(true, true, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(false, false, filter, s2, path0, path1, path2, path3, path4); + }; + + check.accept(ColumnFilter.selection(PartitionColumns.builder().add(v2).build())); + check.accept(ColumnFilter.selectionBuilder().add(v2).build()); + } + + @Test + public void testSelectStaticColumn() + { + Consumer check = filter -> { + testRoundTrips(filter); + assertEquals("[s1]", filter.toString()); + assertFetchedQueried(true, true, filter, s1); + assertFetchedQueried(false, false, filter, v1, v2, s2); + assertCellFetchedQueried(false, false, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(false, false, filter, s2, path0, path1, path2, path3, path4); + }; + + check.accept(ColumnFilter.selection(PartitionColumns.builder().add(s1).build())); + check.accept(ColumnFilter.selectionBuilder().add(s1).build()); + } + + @Test + public void testSelectStaticComplexColumn() + { + Consumer check = filter -> { + testRoundTrips(filter); + assertEquals("[s2]", filter.toString()); + assertFetchedQueried(true, true, filter, s2); + assertFetchedQueried(false, false, filter, v1, v2, s1); + assertCellFetchedQueried(false, false, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(true, true, filter, s2, path0, path1, path2, path3, path4); + }; + + check.accept(ColumnFilter.selection(PartitionColumns.builder().add(s2).build())); + check.accept(ColumnFilter.selectionBuilder().add(s2).build()); + } + + @Test + public void testSelectColumns() + { + Consumer check = filter -> { + testRoundTrips(filter); + assertEquals("[s1, s2, v1, v2]", filter.toString()); + assertFetchedQueried(true, true, filter, v1, v2, s1, s2); + assertCellFetchedQueried(true, true, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(true, true, filter, s2, path0, path1, path2, path3, path4); + }; + + check.accept(ColumnFilter.selection(PartitionColumns.builder().add(v1).add(v2).add(s1).add(s2).build())); + check.accept(ColumnFilter.selectionBuilder().add(v1).add(v2).add(s1).add(s2).build()); + } + + @Test + public void testSelectIndividualCells() + { + ColumnFilter filter = ColumnFilter.selectionBuilder().select(v2, path1).select(v2, path3).build(); + testRoundTrips(filter); + assertEquals("[v2[1], v2[3]]", filter.toString()); + assertFetchedQueried(true, true, filter, v2); + assertFetchedQueried(false, false, filter, v1, s1, s2); + assertCellFetchedQueried(true, true, filter, v2, path1, path3); + assertCellFetchedQueried(false, false, filter, v2, path0, path2, path4); + assertCellFetchedQueried(false, false, filter, s2, path0, path1, path2, path3, path4); + } + + @Test + public void testSelectIndividualCellsFromStatic() + { + ColumnFilter filter = ColumnFilter.selectionBuilder().select(s2, path1).select(s2, path3).build(); + testRoundTrips(filter); + assertEquals("[s2[1], s2[3]]", filter.toString()); + assertFetchedQueried(true, true, filter, s2); + assertFetchedQueried(false, false, filter, v1, v2, s1); + assertCellFetchedQueried(false, false, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(true, true, filter, s2, path1, path3); + assertCellFetchedQueried(false, false, filter, s2, path0, path2, path4); + } + + @Test + public void testSelectCellSlice() + { + ColumnFilter filter = ColumnFilter.selectionBuilder().slice(v2, path1, path3).build(); + testRoundTrips(filter); + assertEquals("[v2[1:3]]", filter.toString()); + assertFetchedQueried(true, true, filter, v2); + assertFetchedQueried(false, false, filter, v1, s1, s2); + assertCellFetchedQueried(true, true, filter, v2, path1, path2, path3); + assertCellFetchedQueried(false, false, filter, v2, path0, path4); + assertCellFetchedQueried(false, false, filter, s2, path0, path1, path2, path3, path4); + } + + @Test + public void testSelectCellSliceFromStatic() + { + ColumnFilter filter = ColumnFilter.selectionBuilder().slice(s2, path1, path3).build(); + testRoundTrips(filter); + assertEquals("[s2[1:3]]", filter.toString()); + assertFetchedQueried(true, true, filter, s2); + assertFetchedQueried(false, false, filter, v1, v2, s1); + assertCellFetchedQueried(false, false, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(true, true, filter, s2, path1, path2, path3); + assertCellFetchedQueried(false, false, filter, s2, path0, path4); + } + + @Test + public void testSelectColumnsWithCellsAndSlices() + { + ColumnFilter filter = ColumnFilter.selectionBuilder() + .add(v1) + .add(s1) + .slice(v2, path0, path2) + .select(v2, path4) + .select(s2, path0) + .slice(s2, path2, path4) + .build(); + testRoundTrips(filter); + assertEquals("[s1, s2[0], s2[2:4], v1, v2[0:2], v2[4]]", filter.toString()); + assertFetchedQueried(true, true, filter, v1, v2, s1, s2); + assertCellFetchedQueried(true, true, filter, v2, path0, path1, path2, path4); + assertCellFetchedQueried(false, false, filter, v2, path3); + assertCellFetchedQueried(true, true, filter, s2, path0, path2, path3, path4); + assertCellFetchedQueried(false, false, filter, s2, path1); + } + + // select with metadata + + @Test + public void testSelectSimpleColumnWithMetadata() + { + Consumer check = filter -> { + testRoundTrips(filter); + assertFetchedQueried(true, true, filter, v1); + + assertEquals("*/*", filter.toString()); + assertFetchedQueried(true, true, filter, s1, s2, v2); + assertCellFetchedQueried(true, true, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(true, true, filter, s2, path0, path1, path2, path3, path4); + }; + + check.accept(ColumnFilter.selection(metadata, PartitionColumns.builder().add(v1).build())); + check.accept(ColumnFilter.allColumnsBuilder(metadata).add(v1).build()); + } + + @Test + public void testSelectStaticColumnWithMetadata() + { + Consumer check = filter -> { + testRoundTrips(filter); + assertFetchedQueried(true, true, filter, s1); + + assertEquals("*/*", filter.toString()); + assertFetchedQueried(true, true, filter, v1, v2, s2); + assertCellFetchedQueried(true, true, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(true, true, filter, s2, path0, path1, path2, path3, path4); + }; + + check.accept(ColumnFilter.selection(metadata, PartitionColumns.builder().add(s1).build())); + check.accept(ColumnFilter.allColumnsBuilder(metadata).add(s1).build()); + } + + @Test + public void testSelectCellWithMetadata() + { + ColumnFilter filter = ColumnFilter.allColumnsBuilder(metadata).select(v2, path1).build(); + testRoundTrips(filter); + assertFetchedQueried(true, true, filter, v2); + + assertEquals("*/*", filter.toString()); + assertFetchedQueried(true, true, filter, s1, s2, v1); + assertCellFetchedQueried(true, true, filter, v2, path1); + assertCellFetchedQueried(true, false, filter, v2, path0, path2, path3, path4); + assertCellFetchedQueried(true, true, filter, s2, path0, path1, path2, path3, path4); + } + + @Test + public void testSelectStaticColumnCellWithMetadata() + { + ColumnFilter filter = ColumnFilter.allColumnsBuilder(metadata).select(s2, path1).build(); + testRoundTrips(filter); + assertFetchedQueried(true, true, filter, s2); + + assertEquals("*/*", filter.toString()); + assertFetchedQueried(true, true, filter, v1, v2, s1); + assertCellFetchedQueried(true, true, filter, v2, path0, path1, path2, path3, path4); + assertCellFetchedQueried(true, true, filter, s2, path1); + assertCellFetchedQueried(true, false, filter, s2, path0, path2, path3, path4); + } + + private void testRoundTrips(ColumnFilter cf) + { + testRoundTrip(cf, MessagingService.VERSION_30); + testRoundTrip(cf, MessagingService.VERSION_3014); + } + + private void testRoundTrip(ColumnFilter columnFilter, int version) + { + try + { + DataOutputBuffer output = new DataOutputBuffer(); + serializer.serialize(columnFilter, output, version); + Assert.assertEquals(serializer.serializedSize(columnFilter, version), output.position()); + DataInputPlus input = new DataInputBuffer(output.buffer(), false); + ColumnFilter deserialized = serializer.deserialize(input, version, metadata); + Assert.assertEquals(deserialized, columnFilter); + } + catch (IOException e) + { + throw Throwables.propagate(e); + } + } + + private static void assertFetchedQueried(boolean expectedIncluded, + boolean expectedNotSkipped, + ColumnFilter filter, + ColumnDefinition... columns) + { + for (ColumnDefinition column : columns) + { + assertEquals(String.format("Expected includes(%s) to be %s", column.name, expectedIncluded), + expectedIncluded, filter.includes(column)); + if (expectedIncluded) + assertEquals(String.format("Expected canSkipValue(%s) to be %s", column.name, !expectedNotSkipped), + !expectedNotSkipped, filter.canSkipValue(column)); + } + } + + private static void assertCellFetchedQueried(boolean expectedIncluded, + boolean expectedNotSkipped, + ColumnFilter filter, + ColumnDefinition column, + CellPath... paths) + { + ColumnFilter.Tester tester = filter.newTester(column); + + for (CellPath path : paths) + { + int p = ByteBufferUtil.toInt(path.get(0)); + + if (tester != null) + { + assertEquals(String.format("Expected tester.includes(%s:%s) to be %s", column.name, p, expectedIncluded), + expectedIncluded, tester.includes(path)); + if (expectedIncluded) + assertEquals(String.format("Expected tester.canSkipValue(%s:%s) to be %s", column.name, p, !expectedNotSkipped), + !expectedNotSkipped, tester.canSkipValue(path)); + } + } } }