Merge branch 'cassandra-2.0' into cassandra-2.1 Conflicts: src/java/org/apache/cassandra/cql3/statements/SelectStatement.java src/java/org/apache/cassandra/db/marshal/CompositeType.java test/unit/org/apache/cassandra/db/marshal/CompositeTypeTest.java
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/ab1a02cf Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/ab1a02cf Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/ab1a02cf Branch: refs/heads/trunk Commit: ab1a02cfa69e0c57579b312202b477e43bb90b45 Parents: 486cc4c b218536 Author: Tyler Hobbs <ty...@datastax.com> Authored: Wed Apr 2 13:17:18 2014 -0500 Committer: Tyler Hobbs <ty...@datastax.com> Committed: Wed Apr 2 13:17:18 2014 -0500 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cql3/statements/SelectStatement.java | 7 +- .../apache/cassandra/db/filter/ColumnSlice.java | 32 +- .../service/pager/AbstractQueryPager.java | 11 + .../service/pager/SliceQueryPager.java | 5 + .../cassandra/db/filter/ColumnSliceTest.java | 290 +++++++++++++++++++ 6 files changed, 342 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/ab1a02cf/CHANGES.txt ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/ab1a02cf/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index edc7f5c,56e87e8..7ca7d93 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@@ -22,9 -22,8 +22,10 @@@ import java.util.* import com.google.common.base.Objects; import com.google.common.base.Predicate; +import com.google.common.collect.AbstractIterator; import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; + import org.github.jamm.MemoryMeter; import org.apache.cassandra.auth.Permission; @@@ -38,7 -37,7 +39,6 @@@ import org.apache.cassandra.db.filter.* import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.dht.*; import org.apache.cassandra.exceptions.*; --import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.StorageProxy; @@@ -49,6 -50,9 +49,8 @@@ import org.apache.cassandra.thrift.Thri import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; -import org.apache.cassandra.utils.Pair; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; /** * Encapsulates a completely parsed SELECT query, including the target http://git-wip-us.apache.org/repos/asf/cassandra/blob/ab1a02cf/src/java/org/apache/cassandra/db/filter/ColumnSlice.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/db/filter/ColumnSlice.java index 262ebae,9eff12a..3838ee5 --- a/src/java/org/apache/cassandra/db/filter/ColumnSlice.java +++ b/src/java/org/apache/cassandra/db/filter/ColumnSlice.java @@@ -52,39 -47,22 +52,65 @@@ public class ColumnSlic this.finish = finish; } - public boolean isAlwaysEmpty(AbstractType<?> comparator, boolean reversed) + public boolean isAlwaysEmpty(CellNameType comparator, boolean reversed) { - Comparator<ByteBuffer> orderedComparator = reversed ? comparator.reverseComparator : comparator; - return (start.remaining() > 0 && finish.remaining() > 0 && orderedComparator.compare(start, finish) > 0); + Comparator<Composite> orderedComparator = reversed ? comparator.reverseComparator() : comparator; + return !start.isEmpty() && !finish.isEmpty() && orderedComparator.compare(start, finish) > 0; } - public boolean includes(Comparator<ByteBuffer> cmp, ByteBuffer name) + public boolean includes(Comparator<Composite> cmp, Composite name) { - return cmp.compare(start, name) <= 0 && (finish.equals(ByteBufferUtil.EMPTY_BYTE_BUFFER) || cmp.compare(finish, name) >= 0); + return cmp.compare(start, name) <= 0 && (finish.isEmpty() || cmp.compare(finish, name) >= 0); + } + + public boolean isBefore(Comparator<Composite> cmp, Composite name) + { + return !finish.isEmpty() && cmp.compare(finish, name) < 0; + } + + public boolean intersects(List<ByteBuffer> minCellNames, List<ByteBuffer> maxCellNames, CellNameType comparator, boolean reversed) + { + assert minCellNames.size() == maxCellNames.size(); + + Composite sStart = reversed ? finish : start; + Composite sEnd = reversed ? start : finish; + - for (int i = 0; i < minCellNames.size(); i++) ++ if (compare(sStart, maxCellNames, comparator, true) > 0 || compare(sEnd, minCellNames, comparator, false) < 0) ++ return false; ++ ++ // We could safely return true here, but there's a minor optimization: if the first component is restricted ++ // to a single value, we can check that the second component falls within the min/max for that component ++ // (and repeat for all components). ++ for (int i = 0; i < Math.min(Math.min(sStart.size(), sEnd.size()), minCellNames.size()); i++) + { + AbstractType<?> t = comparator.subtype(i); - if ( (i < sEnd.size() && t.compare(sEnd.get(i), minCellNames.get(i)) < 0) - || (i < sStart.size() && t.compare(sStart.get(i), maxCellNames.get(i)) > 0)) ++ // we already know the first component falls within its min/max range (otherwise we wouldn't get here) ++ if (i > 0 && (t.compare(sEnd.get(i), minCellNames.get(i)) < 0 || t.compare(sStart.get(i), maxCellNames.get(i)) > 0)) + return false; ++ ++ // if this component isn't equal in the start and finish, we don't need to check any more ++ if (t.compare(sStart.get(i), sEnd.get(i)) != 0) ++ break; + } ++ + return true; } - public boolean isBefore(Comparator<ByteBuffer> cmp, ByteBuffer name) ++ /** Helper method for intersects() */ ++ private int compare(Composite sliceBounds, List<ByteBuffer> sstableBounds, CellNameType comparator, boolean isSliceStart) + { - return !finish.equals(ByteBufferUtil.EMPTY_BYTE_BUFFER) && cmp.compare(finish, name) < 0; ++ for (int i = 0; i < sstableBounds.size(); i++) ++ { ++ if (i >= sliceBounds.size()) ++ return isSliceStart ? -1 : 1; ++ ++ int comparison = comparator.subtype(i).compare(sliceBounds.get(i), sstableBounds.get(i)); ++ if (comparison != 0) ++ return comparison; ++ } ++ return 0; + } + @Override public final int hashCode() { http://git-wip-us.apache.org/repos/asf/cassandra/blob/ab1a02cf/src/java/org/apache/cassandra/service/pager/AbstractQueryPager.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/ab1a02cf/src/java/org/apache/cassandra/service/pager/SliceQueryPager.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/service/pager/SliceQueryPager.java index 3a8c81f,ec229cb..8108098 --- a/src/java/org/apache/cassandra/service/pager/SliceQueryPager.java +++ b/src/java/org/apache/cassandra/service/pager/SliceQueryPager.java @@@ -33,9 -34,11 +35,11 @@@ import org.slf4j.LoggerFactory */ public class SliceQueryPager extends AbstractQueryPager implements SinglePartitionPager { + private static final Logger logger = LoggerFactory.getLogger(SliceQueryPager.class); + private final SliceFromReadCommand command; - private volatile ByteBuffer lastReturned; + private volatile CellName lastReturned; // Don't use directly, use QueryPagers method instead SliceQueryPager(SliceFromReadCommand command, ConsistencyLevel consistencyLevel, boolean localQuery) http://git-wip-us.apache.org/repos/asf/cassandra/blob/ab1a02cf/test/unit/org/apache/cassandra/db/filter/ColumnSliceTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/db/filter/ColumnSliceTest.java index 0000000,0000000..4718795 new file mode 100644 --- /dev/null +++ b/test/unit/org/apache/cassandra/db/filter/ColumnSliceTest.java @@@ -1,0 -1,0 +1,290 @@@ ++/* ++ * * 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.filter; ++ ++import org.apache.cassandra.db.composites.Composite; ++import org.apache.cassandra.db.composites.CompoundDenseCellNameType; ++import org.apache.cassandra.db.marshal.AbstractType; ++import org.apache.cassandra.db.marshal.Int32Type; ++import org.apache.cassandra.utils.ByteBufferUtil; ++import org.junit.Test; ++ ++import java.nio.ByteBuffer; ++import java.util.ArrayList; ++import java.util.List; ++ ++import static org.junit.Assert.assertFalse; ++import static org.junit.Assert.assertTrue; ++ ++public class ColumnSliceTest ++{ ++ @Test ++ public void testIntersectsSingleSlice() ++ { ++ List<AbstractType<?>> types = new ArrayList<>(); ++ types.add(Int32Type.instance); ++ types.add(Int32Type.instance); ++ types.add(Int32Type.instance); ++ CompoundDenseCellNameType nameType = new CompoundDenseCellNameType(types); ++ ++ // filter falls entirely before sstable ++ ColumnSlice slice = new ColumnSlice(composite(0, 0, 0), composite(1, 0, 0)); ++ assertFalse(slice.intersects(columnNames(2, 0, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ // same case, but with empty start ++ slice = new ColumnSlice(composite(), composite(1, 0, 0)); ++ assertFalse(slice.intersects(columnNames(2, 0, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ // same case, but with missing components for start ++ slice = new ColumnSlice(composite(0), composite(1, 0, 0)); ++ assertFalse(slice.intersects(columnNames(2, 0, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ // same case, but with missing components for start and end ++ slice = new ColumnSlice(composite(0), composite(1, 0)); ++ assertFalse(slice.intersects(columnNames(2, 0, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ ++ // end of slice matches start of sstable for the first component, but not the second component ++ slice = new ColumnSlice(composite(0, 0, 0), composite(1, 0, 0)); ++ assertFalse(slice.intersects(columnNames(1, 1, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ // same case, but with missing components for start ++ slice = new ColumnSlice(composite(0), composite(1, 0, 0)); ++ assertFalse(slice.intersects(columnNames(1, 1, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ // same case, but with missing components for start and end ++ slice = new ColumnSlice(composite(0), composite(1, 0)); ++ assertFalse(slice.intersects(columnNames(1, 1, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ // first two components match, but not the last ++ slice = new ColumnSlice(composite(0, 0, 0), composite(1, 1, 0)); ++ assertFalse(slice.intersects(columnNames(1, 1, 1), columnNames(3, 1, 1), nameType, false)); ++ ++ // all three components in slice end match the start of the sstable ++ slice = new ColumnSlice(composite(0, 0, 0), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 1, 1), columnNames(3, 1, 1), nameType, false)); ++ ++ ++ // filter falls entirely after sstable ++ slice = new ColumnSlice(composite(4, 0, 0), composite(4, 0, 0)); ++ assertFalse(slice.intersects(columnNames(2, 0, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ // same case, but with empty end ++ slice = new ColumnSlice(composite(4, 0, 0), composite()); ++ assertFalse(slice.intersects(columnNames(2, 0, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ // same case, but with missing components for end ++ slice = new ColumnSlice(composite(4, 0, 0), composite(1)); ++ assertFalse(slice.intersects(columnNames(2, 0, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ // same case, but with missing components for start and end ++ slice = new ColumnSlice(composite(4, 0), composite(1)); ++ assertFalse(slice.intersects(columnNames(2, 0, 0), columnNames(3, 0, 0), nameType, false)); ++ ++ ++ // start of slice matches end of sstable for the first component, but not the second component ++ slice = new ColumnSlice(composite(1, 1, 1), composite(2, 0, 0)); ++ assertFalse(slice.intersects(columnNames(0, 0, 0), columnNames(1, 0, 0), nameType, false)); ++ ++ // start of slice matches end of sstable for the first two components, but not the last component ++ slice = new ColumnSlice(composite(1, 1, 1), composite(2, 0, 0)); ++ assertFalse(slice.intersects(columnNames(0, 0, 0), columnNames(1, 1, 0), nameType, false)); ++ ++ // all three components in the slice start match the end of the sstable ++ slice = new ColumnSlice(composite(1, 1, 1), composite(2, 0, 0)); ++ assertTrue(slice.intersects(columnNames(0, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ ++ // slice covers entire sstable (with no matching edges) ++ slice = new ColumnSlice(composite(0, 0, 0), composite(2, 0, 0)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ // same case, but with empty ends ++ slice = new ColumnSlice(composite(), composite()); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ // same case, but with missing components ++ slice = new ColumnSlice(composite(0), composite(2, 0)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ // slice covers entire sstable (with matching start) ++ slice = new ColumnSlice(composite(1, 0, 0), composite(2, 0, 0)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ // slice covers entire sstable (with matching end) ++ slice = new ColumnSlice(composite(0, 0, 0), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ // slice covers entire sstable (with matching start and end) ++ slice = new ColumnSlice(composite(1, 0, 0), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ ++ // slice falls entirely within sstable (with matching start) ++ slice = new ColumnSlice(composite(1, 0, 0), composite(1, 1, 0)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ // same case, but with a missing end component ++ slice = new ColumnSlice(composite(1, 0, 0), composite(1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ // slice falls entirely within sstable (with matching end) ++ slice = new ColumnSlice(composite(1, 1, 0), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ // same case, but with a missing start component ++ slice = new ColumnSlice(composite(1, 1), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(1, 1, 1), nameType, false)); ++ ++ ++ // slice falls entirely within sstable ++ slice = new ColumnSlice(composite(1, 1, 0), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 2, 2), nameType, false)); ++ ++ // same case, but with a missing start component ++ slice = new ColumnSlice(composite(1, 1), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 2, 2), nameType, false)); ++ ++ // same case, but with a missing start and end components ++ slice = new ColumnSlice(composite(1), composite(1, 2)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 2, 2), nameType, false)); ++ ++ // slice falls entirely within sstable (slice start and end are the same) ++ slice = new ColumnSlice(composite(1, 1, 1), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 2, 2), nameType, false)); ++ ++ ++ // slice starts within sstable, empty end ++ slice = new ColumnSlice(composite(1, 1, 1), composite()); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // same case, but with missing end components ++ slice = new ColumnSlice(composite(1, 1, 1), composite(3)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // slice starts within sstable (matching sstable start), empty end ++ slice = new ColumnSlice(composite(1, 0, 0), composite()); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // same case, but with missing end components ++ slice = new ColumnSlice(composite(1, 0, 0), composite(3)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // slice starts within sstable (matching sstable end), empty end ++ slice = new ColumnSlice(composite(2, 0, 0), composite()); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // same case, but with missing end components ++ slice = new ColumnSlice(composite(2, 0, 0), composite(3)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ ++ // slice ends within sstable, empty end ++ slice = new ColumnSlice(composite(), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // same case, but with missing start components ++ slice = new ColumnSlice(composite(0), composite(1, 1, 1)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // slice ends within sstable (matching sstable start), empty start ++ slice = new ColumnSlice(composite(), composite(1, 0, 0)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // same case, but with missing start components ++ slice = new ColumnSlice(composite(0), composite(1, 0, 0)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // slice ends within sstable (matching sstable end), empty start ++ slice = new ColumnSlice(composite(), composite(2, 0, 0)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ // same case, but with missing start components ++ slice = new ColumnSlice(composite(0), composite(2, 0, 0)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 0, 0), nameType, false)); ++ ++ ++ // the slice technically falls within the sstable range, but since the first component is restricted to ++ // a single value, we can check that the second component does not fall within its min/max ++ slice = new ColumnSlice(composite(1, 2, 0), composite(1, 3, 0)); ++ assertFalse(slice.intersects(columnNames(1, 0, 0), columnNames(2, 1, 0), nameType, false)); ++ ++ // same case, but with a missing start component ++ slice = new ColumnSlice(composite(1, 2), composite(1, 3, 0)); ++ assertFalse(slice.intersects(columnNames(1, 0, 0), columnNames(2, 1, 0), nameType, false)); ++ ++ // same case, but with a missing end component ++ slice = new ColumnSlice(composite(1, 2, 0), composite(1, 3)); ++ assertFalse(slice.intersects(columnNames(1, 0, 0), columnNames(2, 1, 0), nameType, false)); ++ ++ // same case, but with a missing start and end components ++ slice = new ColumnSlice(composite(1, 2), composite(1, 3)); ++ assertFalse(slice.intersects(columnNames(1, 0, 0), columnNames(2, 1, 0), nameType, false)); ++ ++ ++ // same as the previous set of tests, but the second component is equal in the slice start and end ++ slice = new ColumnSlice(composite(1, 2, 0), composite(1, 2, 0)); ++ assertFalse(slice.intersects(columnNames(1, 0, 0), columnNames(2, 1, 0), nameType, false)); ++ ++ // same case, but with a missing start component ++ slice = new ColumnSlice(composite(1, 2), composite(1, 2, 0)); ++ assertFalse(slice.intersects(columnNames(1, 0, 0), columnNames(2, 1, 0), nameType, false)); ++ ++ // same case, but with a missing end component ++ slice = new ColumnSlice(composite(1, 2, 0), composite(1, 2)); ++ assertFalse(slice.intersects(columnNames(1, 0, 0), columnNames(2, 1, 0), nameType, false)); ++ ++ // same case, but with a missing start and end components ++ slice = new ColumnSlice(composite(1, 2), composite(1, 2)); ++ assertFalse(slice.intersects(columnNames(1, 0, 0), columnNames(2, 1, 0), nameType, false)); ++ ++ // same as the previous tests, but it's the third component that doesn't fit in its range this time ++ slice = new ColumnSlice(composite(1, 1, 2), composite(1, 1, 3)); ++ assertFalse(slice.intersects(columnNames(1, 1, 0), columnNames(2, 2, 1), nameType, false)); ++ ++ ++ // basic check on reversed slices ++ slice = new ColumnSlice(composite(1, 0, 0), composite(0, 0, 0)); ++ assertFalse(slice.intersects(columnNames(2, 0, 0), columnNames(3, 0, 0), nameType, true)); ++ ++ slice = new ColumnSlice(composite(1, 0, 0), composite(0, 0, 0)); ++ assertFalse(slice.intersects(columnNames(1, 1, 0), columnNames(3, 0, 0), nameType, true)); ++ ++ slice = new ColumnSlice(composite(1, 1, 1), composite(1, 1, 0)); ++ assertTrue(slice.intersects(columnNames(1, 0, 0), columnNames(2, 2, 2), nameType, true)); ++ } ++ ++ private static Composite composite(Integer ... components) ++ { ++ List<AbstractType<?>> types = new ArrayList<>(); ++ types.add(Int32Type.instance); ++ types.add(Int32Type.instance); ++ types.add(Int32Type.instance); ++ CompoundDenseCellNameType nameType = new CompoundDenseCellNameType(types); ++ return nameType.make(components); ++ } ++ ++ private static List<ByteBuffer> columnNames(Integer ... components) ++ { ++ List<ByteBuffer> names = new ArrayList<>(components.length); ++ for (int component : components) ++ names.add(ByteBufferUtil.bytes(component)); ++ return names; ++ } ++}