/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.db.rows;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.LivenessInfo;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.db.partitions.PartitionStatisticsCollector;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
public class RowsTest
{
private static final String KEYSPACE = "rows_test";
private static final String KCVM_TABLE = "kcvm";
private static final TableMetadata kcvm;
private static final ColumnMetadata v;
private static final ColumnMetadata m;
private static final Clustering c1;
static
{
DatabaseDescriptor.daemonInitialization();
kcvm =
TableMetadata.builder(KEYSPACE, KCVM_TABLE)
.addPartitionKeyColumn("k", IntegerType.instance)
.addClusteringColumn("c", IntegerType.instance)
.addRegularColumn("v", IntegerType.instance)
.addRegularColumn("m", MapType.getInstance(IntegerType.instance, IntegerType.instance, true))
.build();
v = kcvm.getColumn(new ColumnIdentifier("v", false));
m = kcvm.getColumn(new ColumnIdentifier("m", false));
c1 = kcvm.comparator.make(BigInteger.valueOf(1));
}
private static final ByteBuffer BB1 = ByteBufferUtil.bytes(1);
private static final ByteBuffer BB2 = ByteBufferUtil.bytes(2);
private static final ByteBuffer BB3 = ByteBufferUtil.bytes(3);
private static final ByteBuffer BB4 = ByteBufferUtil.bytes(4);
private static class MergedPair<T>
{
public final int idx;
public final T merged;
public final T original;
private MergedPair(int idx, T merged, T original)
{
this.idx = idx;
this.merged = merged;
this.original = original;
}
static <T> MergedPair<T> create(int i, T m, T o)
{
return new MergedPair<>(i, m, o);
}
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MergedPair<?> that = (MergedPair<?>) o;
if (idx != that.idx) return false;
if (merged != null ? !merged.equals(that.merged) : that.merged != null) return false;
return !(original != null ? !original.equals(that.original) : that.original != null);
}
public int hashCode()
{
int result = idx;
result = 31 * result + (merged != null ? merged.hashCode() : 0);
result = 31 * result + (original != null ? original.hashCode() : 0);
return result;
}
public String toString()
{
return "MergedPair{" +
"idx=" + idx +
", merged=" + merged +
", original=" + original +
'}';
}
}
private static class DiffListener implements RowDiffListener
{
int updates = 0;
Clustering clustering = null;
private void updateClustering(Clustering c)
{
assert clustering == null || clustering == c;
clustering = c;
}
List<MergedPair<Cell>> cells = new LinkedList<>();
public void onCell(int i, Clustering clustering, Cell merged, Cell original)
{
updateClustering(clustering);
cells.add(MergedPair.create(i, merged, original));
updates++;
}
List<MergedPair<LivenessInfo>> liveness = new LinkedList<>();
public void onPrimaryKeyLivenessInfo(int i, Clustering clustering, LivenessInfo merged, LivenessInfo original)
{
updateClustering(clustering);
liveness.add(MergedPair.create(i, merged, original));
updates++;
}
List<MergedPair<Row.Deletion>> deletions = new LinkedList<>();
public void onDeletion(int i, Clustering clustering, Row.Deletion merged, Row.Deletion original)
{
updateClustering(clustering);
deletions.add(MergedPair.create(i, merged, original));
updates++;
}
Map<ColumnMetadata, List<MergedPair<DeletionTime>>> complexDeletions = new HashMap<>();
public void onComplexDeletion(int i, Clustering clustering, ColumnMetadata column, DeletionTime merged, DeletionTime original)
{
updateClustering(clustering);
if (!complexDeletions.containsKey(column)) complexDeletions.put(column, new LinkedList<>());
complexDeletions.get(column).add(MergedPair.create(i, merged, original));
updates++;
}
}
public static class StatsCollector implements PartitionStatisticsCollector
{
List<Cell> cells = new LinkedList<>();
public void update(Cell cell)
{
cells.add(cell);
}
List<LivenessInfo> liveness = new LinkedList<>();
public void update(LivenessInfo info)
{
liveness.add(info);
}
List<DeletionTime> deletions = new LinkedList<>();
public void update(DeletionTime deletion)
{
deletions.add(deletion);
}
long columnCount = -1;
public void updateColumnSetPerRow(long columnSetInRow)
{
assert columnCount < 0;
this.columnCount = columnSetInRow;
}
boolean hasLegacyCounterShards = false;
public void updateHasLegacyCounterShards(boolean hasLegacyCounterShards)
{
this.hasLegacyCounterShards |= hasLegacyCounterShards;
}
}
private static long secondToTs(int now)
{
return now * 1000000;
}
private static Row.Builder createBuilder(Clustering c, int now, ByteBuffer vVal, ByteBuffer mKey, ByteBuffer mVal)
{
long ts = secondToTs(now);
Row.Builder builder = BTreeRow.unsortedBuilder(now);
builder.newRow(c);
builder.addPrimaryKeyLivenessInfo(LivenessInfo.create(ts, now));
if (vVal != null)
{
builder.addCell(BufferCell.live(v, ts, vVal));
}
if (mKey != null && mVal != null)
{
builder.addComplexDeletion(m, new DeletionTime(ts - 1, now));
builder.addCell(BufferCell.live(m, ts, mVal, CellPath.create(mKey)));
}
return builder;
}
@Test
public void copy()
{
int now = FBUtilities.nowInSeconds();
long ts = secondToTs(now);
Row.Builder originalBuilder = BTreeRow.unsortedBuilder(now);
originalBuilder.newRow(c1);
LivenessInfo liveness = LivenessInfo.create(ts, now);
originalBuilder.addPrimaryKeyLivenessInfo(liveness);
DeletionTime complexDeletion = new DeletionTime(ts-1, now);
originalBuilder.addComplexDeletion(m, complexDeletion);
List<Cell> expectedCells = Lists.newArrayList(BufferCell.live(v, secondToTs(now), BB1),
BufferCell.live(m, secondToTs(now), BB1, CellPath.create(BB1)),
BufferCell.live(m, secondToTs(now), BB2, CellPath.create(BB2)));
expectedCells.forEach(originalBuilder::addCell);
// We need to use ts-1 so the deletion doesn't shadow what we've created
Row.Deletion rowDeletion = new Row.Deletion(new DeletionTime(ts-1, now), false);
originalBuilder.addRowDeletion(rowDeletion);
RowBuilder builder = new RowBuilder();
Rows.copy(originalBuilder.build(), builder);
Assert.assertEquals(c1, builder.clustering);
Assert.assertEquals(liveness, builder.livenessInfo);
Assert.assertEquals(rowDeletion, builder.deletionTime);
Assert.assertEquals(Lists.newArrayList(Pair.create(m, complexDeletion)), builder.complexDeletions);
Assert.assertEquals(Sets.newHashSet(expectedCells), Sets.newHashSet(builder.cells));
}
@Test
public void collectStats()
{
int now = FBUtilities.nowInSeconds();
long ts = secondToTs(now);
Row.Builder builder = BTreeRow.unsortedBuilder(now);
builder.newRow(c1);
LivenessInfo liveness = LivenessInfo.create(ts, now);
builder.addPrimaryKeyLivenessInfo(liveness);
DeletionTime complexDeletion = new DeletionTime(ts-1, now);
builder.addComplexDeletion(m, complexDeletion);
List<Cell> expectedCells = Lists.newArrayList(BufferCell.live(v, ts, BB1),
BufferCell.live(m, ts, BB1, CellPath.create(BB1)),
BufferCell.live(m, ts, BB2, CellPath.create(BB2)));
expectedCells.forEach(builder::addCell);
// We need to use ts-1 so the deletion doesn't shadow what we've created
Row.Deletion rowDeletion = new Row.Deletion(new DeletionTime(ts-1, now), false);
builder.addRowDeletion(rowDeletion);
StatsCollector collector = new StatsCollector();
Rows.collectStats(builder.build(), collector);
Assert.assertEquals(Lists.newArrayList(liveness), collector.liveness);
Assert.assertEquals(Sets.newHashSet(rowDeletion.time(), complexDeletion), Sets.newHashSet(collector.deletions));
Assert.assertEquals(Sets.newHashSet(expectedCells), Sets.newHashSet(collector.cells));
Assert.assertEquals(2, collector.columnCount);
Assert.assertFalse(collector.hasLegacyCounterShards);
}
public static void addExpectedCells(Set<MergedPair<Cell>> dst, Cell merged, Cell... inputs)
{
for (int i=0; i<inputs.length; i++)
{
dst.add(MergedPair.create(i, merged, inputs[i]));
}
}
@Test
public void diff()
{
int now1 = FBUtilities.nowInSeconds();
long ts1 = secondToTs(now1);
Row.Builder r1Builder = BTreeRow.unsortedBuilder(now1);
r1Builder.newRow(c1);
LivenessInfo r1Liveness = LivenessInfo.create(ts1, now1);
r1Builder.addPrimaryKeyLivenessInfo(r1Liveness);
DeletionTime r1ComplexDeletion = new DeletionTime(ts1-1, now1);
r1Builder.addComplexDeletion(m, r1ComplexDeletion);
Cell r1v = BufferCell.live(v, ts1, BB1);
Cell r1m1 = BufferCell.live(m, ts1, BB1, CellPath.create(BB1));
Cell r1m2 = BufferCell.live(m, ts1, BB2, CellPath.create(BB2));
List<Cell> r1ExpectedCells = Lists.newArrayList(r1v, r1m1, r1m2);
r1ExpectedCells.forEach(r1Builder::addCell);
int now2 = now1 + 1;
long ts2 = secondToTs(now2);
Row.Builder r2Builder = BTreeRow.unsortedBuilder(now2);
r2Builder.newRow(c1);
LivenessInfo r2Liveness = LivenessInfo.create(ts2, now2);
r2Builder.addPrimaryKeyLivenessInfo(r2Liveness);
Cell r2v = BufferCell.live(v, ts2, BB2);
Cell r2m2 = BufferCell.live(m, ts2, BB1, CellPath.create(BB2));
Cell r2m3 = BufferCell.live(m, ts2, BB2, CellPath.create(BB3));
Cell r2m4 = BufferCell.live(m, ts2, BB3, CellPath.create(BB4));
List<Cell> r2ExpectedCells = Lists.newArrayList(r2v, r2m2, r2m3, r2m4);
r2ExpectedCells.forEach(r2Builder::addCell);
Row.Deletion r2RowDeletion = new Row.Deletion(new DeletionTime(ts1 - 2, now2), false);
r2Builder.addRowDeletion(r2RowDeletion);
Row r1 = r1Builder.build();
Row r2 = r2Builder.build();
Row merged = Rows.merge(r1, r2, now2 + 1);
Assert.assertEquals(r1ComplexDeletion, merged.getComplexColumnData(m).complexDeletion());
DiffListener listener = new DiffListener();
Rows.diff(listener, merged, r1, r2);
Assert.assertEquals(c1, listener.clustering);
// check cells
Set<MergedPair<Cell>> expectedCells = Sets.newHashSet();
addExpectedCells(expectedCells, r2v, r1v, r2v); // v
addExpectedCells(expectedCells, r1m1, r1m1, null); // m[1]
addExpectedCells(expectedCells, r2m2, r1m2, r2m2); // m[2]
addExpectedCells(expectedCells, r2m3, null, r2m3); // m[3]
addExpectedCells(expectedCells, r2m4, null, r2m4); // m[4]
Assert.assertEquals(expectedCells.size(), listener.cells.size());
Assert.assertEquals(expectedCells, Sets.newHashSet(listener.cells));
// liveness
List<MergedPair<LivenessInfo>> expectedLiveness = Lists.newArrayList(MergedPair.create(0, r2Liveness, r1Liveness),
MergedPair.create(1, r2Liveness, r2Liveness));
Assert.assertEquals(expectedLiveness, listener.liveness);
// deletions
List<MergedPair<Row.Deletion>> expectedDeletions = Lists.newArrayList(MergedPair.create(0, r2RowDeletion, null),
MergedPair.create(1, r2RowDeletion, r2RowDeletion));
Assert.assertEquals(expectedDeletions, listener.deletions);
// complex deletions
List<MergedPair<DeletionTime>> expectedCmplxDeletions = Lists.newArrayList(MergedPair.create(0, r1ComplexDeletion, r1ComplexDeletion),
MergedPair.create(1, r1ComplexDeletion, DeletionTime.LIVE));
Assert.assertEquals(ImmutableMap.builder().put(m, expectedCmplxDeletions).build(), listener.complexDeletions);
}
/**
* merged row has no column data
*/
@Test
public void diffEmptyMerged()
{
int now1 = FBUtilities.nowInSeconds();
long ts1 = secondToTs(now1);
Row.Builder r1Builder = BTreeRow.unsortedBuilder(now1);
r1Builder.newRow(c1);
LivenessInfo r1Liveness = LivenessInfo.create(ts1, now1);
r1Builder.addPrimaryKeyLivenessInfo(r1Liveness);
// mergedData == null
int now2 = now1 + 1;
long ts2 = secondToTs(now2);
Row.Builder r2Builder = BTreeRow.unsortedBuilder(now2);
r2Builder.newRow(c1);
LivenessInfo r2Liveness = LivenessInfo.create(ts2, now2);
r2Builder.addPrimaryKeyLivenessInfo(r2Liveness);
DeletionTime r2ComplexDeletion = new DeletionTime(ts2-1, now2);
r2Builder.addComplexDeletion(m, r2ComplexDeletion);
Cell r2v = BufferCell.live(v, ts2, BB2);
Cell r2m2 = BufferCell.live(m, ts2, BB1, CellPath.create(BB2));
Cell r2m3 = BufferCell.live(m, ts2, BB2, CellPath.create(BB3));
Cell r2m4 = BufferCell.live(m, ts2, BB3, CellPath.create(BB4));
List<Cell> r2ExpectedCells = Lists.newArrayList(r2v, r2m2, r2m3, r2m4);
r2ExpectedCells.forEach(r2Builder::addCell);
Row.Deletion r2RowDeletion = new Row.Deletion(new DeletionTime(ts1 - 1, now2), false);
r2Builder.addRowDeletion(r2RowDeletion);
Row r1 = r1Builder.build();
Row r2 = r2Builder.build();
DiffListener listener = new DiffListener();
Rows.diff(listener, r1, r2);
Assert.assertEquals(c1, listener.clustering);
// check cells
Set<MergedPair<Cell>> expectedCells = Sets.newHashSet(MergedPair.create(0, null, r2v), // v
MergedPair.create(0, null, r2m2), // m[2]
MergedPair.create(0, null, r2m3), // m[3]
MergedPair.create(0, null, r2m4)); // m[4]
Assert.assertEquals(expectedCells.size(), listener.cells.size());
Assert.assertEquals(expectedCells, Sets.newHashSet(listener.cells));
// complex deletions
List<MergedPair<DeletionTime>> expectedCmplxDeletions = Lists.newArrayList(MergedPair.create(0, null, r2ComplexDeletion));
Assert.assertEquals(ImmutableMap.builder().put(m, expectedCmplxDeletions).build(), listener.complexDeletions);
}
/**
* input row has no column data
*/
@Test
public void diffEmptyInput()
{
int now1 = FBUtilities.nowInSeconds();
long ts1 = secondToTs(now1);
Row.Builder r1Builder = BTreeRow.unsortedBuilder(now1);
r1Builder.newRow(c1);
LivenessInfo r1Liveness = LivenessInfo.create(ts1, now1);
r1Builder.addPrimaryKeyLivenessInfo(r1Liveness);
// mergedData == null
int now2 = now1 + 1;
long ts2 = secondToTs(now2);
Row.Builder r2Builder = BTreeRow.unsortedBuilder(now2);
r2Builder.newRow(c1);
LivenessInfo r2Liveness = LivenessInfo.create(ts2, now2);
r2Builder.addPrimaryKeyLivenessInfo(r2Liveness);
DeletionTime r2ComplexDeletion = new DeletionTime(ts2-1, now2);
r2Builder.addComplexDeletion(m, r2ComplexDeletion);
Cell r2v = BufferCell.live(v, ts2, BB2);
Cell r2m2 = BufferCell.live(m, ts2, BB1, CellPath.create(BB2));
Cell r2m3 = BufferCell.live(m, ts2, BB2, CellPath.create(BB3));
Cell r2m4 = BufferCell.live(m, ts2, BB3, CellPath.create(BB4));
List<Cell> r2ExpectedCells = Lists.newArrayList(r2v, r2m2, r2m3, r2m4);
r2ExpectedCells.forEach(r2Builder::addCell);
Row.Deletion r2RowDeletion = new Row.Deletion(new DeletionTime(ts1 - 1, now2), false);
r2Builder.addRowDeletion(r2RowDeletion);
Row r1 = r1Builder.build();
Row r2 = r2Builder.build();
DiffListener listener = new DiffListener();
Rows.diff(listener, r2, r1);
Assert.assertEquals(c1, listener.clustering);
// check cells
Set<MergedPair<Cell>> expectedCells = Sets.newHashSet(MergedPair.create(0, r2v, null), // v
MergedPair.create(0, r2m2, null), // m[2]
MergedPair.create(0, r2m3, null), // m[3]
MergedPair.create(0, r2m4, null)); // m[4]
Assert.assertEquals(expectedCells.size(), listener.cells.size());
Assert.assertEquals(expectedCells, Sets.newHashSet(listener.cells));
// complex deletions
List<MergedPair<DeletionTime>> expectedCmplxDeletions = Lists.newArrayList(MergedPair.create(0, r2ComplexDeletion, null));
Assert.assertEquals(ImmutableMap.builder().put(m, expectedCmplxDeletions).build(), listener.complexDeletions);
}
@Test
public void merge()
{
int now1 = FBUtilities.nowInSeconds();
Row.Builder existingBuilder = createBuilder(c1, now1, BB1, BB1, BB1);
int now2 = now1 + 1;
long ts2 = secondToTs(now2);
Cell expectedVCell = BufferCell.live(v, ts2, BB2);
Cell expectedMCell = BufferCell.live(m, ts2, BB2, CellPath.create(BB1));
DeletionTime expectedComplexDeletionTime = new DeletionTime(ts2 - 1, now2);
Row.Builder updateBuilder = createBuilder(c1, now2, null, null, null);
updateBuilder.addCell(expectedVCell);
updateBuilder.addComplexDeletion(m, expectedComplexDeletionTime);
updateBuilder.addCell(expectedMCell);
RowBuilder builder = new RowBuilder();
long td = Rows.merge(existingBuilder.build(), updateBuilder.build(), builder, now2 + 1);
Assert.assertEquals(c1, builder.clustering);
Assert.assertEquals(LivenessInfo.create(ts2, now2), builder.livenessInfo);
Assert.assertEquals(Lists.newArrayList(Pair.create(m, new DeletionTime(ts2-1, now2))), builder.complexDeletions);
Assert.assertEquals(2, builder.cells.size());
Assert.assertEquals(Lists.newArrayList(expectedVCell, expectedMCell), Lists.newArrayList(builder.cells));
Assert.assertEquals(ts2 - secondToTs(now1), td);
}
@Test
public void mergeComplexDeletionSupersededByRowDeletion()
{
int now1 = FBUtilities.nowInSeconds();
Row.Builder existingBuilder = createBuilder(c1, now1, null, null, null);
int now2 = now1 + 1;
Row.Builder updateBuilder = createBuilder(c1, now2, null, BB1, BB1);
int now3 = now2 + 1;
Row.Deletion expectedDeletion = new Row.Deletion(new DeletionTime(secondToTs(now3), now3), false);
updateBuilder.addRowDeletion(expectedDeletion);
RowBuilder builder = new RowBuilder();
Rows.merge(existingBuilder.build(), updateBuilder.build(), builder, now3 + 1);
Assert.assertEquals(expectedDeletion, builder.deletionTime);
Assert.assertEquals(Collections.emptyList(), builder.complexDeletions);
Assert.assertEquals(Collections.emptyList(), builder.cells);
}
/**
* If a row's deletion time deletes a row's liveness info, the new row should have it's
* liveness info set to empty
*/
@Test
public void mergeRowDeletionSupercedesLiveness()
{
int now1 = FBUtilities.nowInSeconds();
Row.Builder existingBuilder = createBuilder(c1, now1, null, null, null);
int now2 = now1 + 1;
Row.Builder updateBuilder = createBuilder(c1, now2, BB1, BB1, BB1);
int now3 = now2 + 1;
Row.Deletion expectedDeletion = new Row.Deletion(new DeletionTime(secondToTs(now3), now3), false);
updateBuilder.addRowDeletion(expectedDeletion);
RowBuilder builder = new RowBuilder();
Rows.merge(existingBuilder.build(), updateBuilder.build(), builder, now3 + 1);
Assert.assertEquals(expectedDeletion, builder.deletionTime);
Assert.assertEquals(LivenessInfo.EMPTY, builder.livenessInfo);
Assert.assertEquals(Collections.emptyList(), builder.complexDeletions);
Assert.assertEquals(Collections.emptyList(), builder.cells);
}
// Creates a dummy cell for a (regular) column for the provided name and without a cellPath.
private static Cell liveCell(ColumnMetadata name)
{
return liveCell(name, -1);
}
// Creates a dummy cell for a (regular) column for the provided name.
// If path >= 0, the cell will have a CellPath containing path as an Int32Type.
private static Cell liveCell(ColumnMetadata name, int path)
{
CellPath cp = path < 0 ? null : CellPath.create(ByteBufferUtil.bytes(path));
return new BufferCell(name, 0L, Cell.NO_TTL, Cell.NO_DELETION_TIME, ByteBuffer.allocate(1), cp);
}
// Assert that the cells generated by iterating iterable are the cell of cells (in the same order
// and with neither more nor less cells).
private static void assertCellOrder(Iterable<Cell> iterable, Cell... cells)
{
int i = 0;
for (Cell actual : iterable)
{
Assert.assertFalse(String.format("Got more rows than expected (expecting %d). First unexpected cell is %s", cells.length, actual), i >= cells.length);
Assert.assertEquals(cells[i++], actual);
}
Assert.assertFalse(String.format("Got less rows than expected (got %d while expecting %d).", i, cells.length), i < cells.length);
}
// Make a dummy row (empty clustering) with the provided cells, that are assumed to be in order
private static Row makeDummyRow(Cell ... cells)
{
Row.Builder builder = BTreeRow.sortedBuilder();
builder.newRow(Clustering.EMPTY);
for (Cell cell : cells)
builder.addCell(cell);
return builder.build();
}
@Test
public void testLegacyCellIterator()
{
// Creates a table with
// - 3 Simple columns: a, c and e
// - 2 Complex columns: b and d
TableMetadata metadata =
TableMetadata.builder("dummy_ks", "dummy_tbl")
.addPartitionKeyColumn("k", BytesType.instance)
.addRegularColumn("a", BytesType.instance)
.addRegularColumn("b", MapType.getInstance(Int32Type.instance, BytesType.instance, true))
.addRegularColumn("c", BytesType.instance)
.addRegularColumn("d", MapType.getInstance(Int32Type.instance, BytesType.instance, true))
.addRegularColumn("e", BytesType.instance)
.build();
ColumnMetadata a = metadata.getColumn(new ColumnIdentifier("a", false));
ColumnMetadata b = metadata.getColumn(new ColumnIdentifier("b", false));
ColumnMetadata c = metadata.getColumn(new ColumnIdentifier("c", false));
ColumnMetadata d = metadata.getColumn(new ColumnIdentifier("d", false));
ColumnMetadata e = metadata.getColumn(new ColumnIdentifier("e", false));
Row row;
// Row with only simple columns
row = makeDummyRow(liveCell(a),
liveCell(c),
liveCell(e));
assertCellOrder(row.cellsInLegacyOrder(metadata, false),
liveCell(a),
liveCell(c),
liveCell(e));
assertCellOrder(row.cellsInLegacyOrder(metadata, true),
liveCell(e),
liveCell(c),
liveCell(a));
// Row with only complex columns
row = makeDummyRow(liveCell(b, 1),
liveCell(b, 2),
liveCell(d, 3),
liveCell(d, 4));
assertCellOrder(row.cellsInLegacyOrder(metadata, false),
liveCell(b, 1),
liveCell(b, 2),
liveCell(d, 3),
liveCell(d, 4));
assertCellOrder(row.cellsInLegacyOrder(metadata, true),
liveCell(d, 4),
liveCell(d, 3),
liveCell(b, 2),
liveCell(b, 1));
// Row with mixed simple and complex columns
row = makeDummyRow(liveCell(a),
liveCell(c),
liveCell(e),
liveCell(b, 1),
liveCell(b, 2),
liveCell(d, 3),
liveCell(d, 4));
assertCellOrder(row.cellsInLegacyOrder(metadata, false),
liveCell(a),
liveCell(b, 1),
liveCell(b, 2),
liveCell(c),
liveCell(d, 3),
liveCell(d, 4),
liveCell(e));
assertCellOrder(row.cellsInLegacyOrder(metadata, true),
liveCell(e),
liveCell(d, 4),
liveCell(d, 3),
liveCell(c),
liveCell(b, 2),
liveCell(b, 1),
liveCell(a));
}
}