Make sub-range selection for non-frozen collections return null instead of empty

patch by Benjamin Lerer; reviewed by Andrés de la Peña for CASSANDRA-14182
This commit is contained in:
Benjamin Lerer 2018-01-22 13:31:02 +01:00
parent 9635c6041a
commit 4de7a65ed9
7 changed files with 107 additions and 10 deletions

View File

@ -1,4 +1,5 @@
4.0
* Make sub-range selection for non-frozen collections return null instead of empty (CASSANDRA-14182)
* BloomFilter serialization format should not change byte ordering (CASSANDRA-9067)
* Remove unused on-heap BloomFilter implementation (CASSANDRA-14152)
* Delete temp test files on exit (CASSANDRA-14153)

View File

@ -304,7 +304,7 @@ abstract class ElementsSelector extends Selector
protected ByteBuffer extractSelection(ByteBuffer collection)
{
return type.getSerializer().getSliceFromSerialized(collection, from, to, type.nameComparator());
return type.getSerializer().getSliceFromSerialized(collection, from, to, type.nameComparator(), type.isFrozenCollection());
}
public AbstractType<?> getType()

View File

@ -134,6 +134,8 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
/**
* Returns the slice of a collection directly from its serialized value.
* <p>If the slice contains no elements an empty collection will be returned for frozen collections, and a
* {@code null} one for non-frozen collections.</p>
*
* @param collection the serialized collection. This cannot be {@code null}.
* @param from the left bound of the slice to extract. This cannot be {@code null} but if this is
@ -141,10 +143,14 @@ public abstract class CollectionSerializer<T> implements TypeSerializer<T>
* of {@code collection}.
* @param comparator the type to use to compare the {@code from} and {@code to} values to those
* in the collection.
* @return a valid serialized collection (possibly empty) corresponding to slice {@code [from, to]}
* of {@code collection}.
* @param frozen {@code true} if the collection is a frozen one, {@code false} otherwise
* @return a serialized collection corresponding to slice {@code [from, to]} of {@code collection}.
*/
public abstract ByteBuffer getSliceFromSerialized(ByteBuffer collection, ByteBuffer from, ByteBuffer to, AbstractType<?> comparator);
public abstract ByteBuffer getSliceFromSerialized(ByteBuffer collection,
ByteBuffer from,
ByteBuffer to,
AbstractType<?> comparator,
boolean frozen);
/**
* Creates a new serialized map composed from the data from {@code input} between {@code startPos}

View File

@ -169,13 +169,19 @@ public class ListSerializer<T> extends CollectionSerializer<List<T>>
return (Class) List.class;
}
@Override
public ByteBuffer getSerializedValue(ByteBuffer collection, ByteBuffer key, AbstractType<?> comparator)
{
// We don't allow selecting an element of a list so we don't need this.
throw new UnsupportedOperationException();
}
public ByteBuffer getSliceFromSerialized(ByteBuffer collection, ByteBuffer from, ByteBuffer to, AbstractType<?> comparator)
@Override
public ByteBuffer getSliceFromSerialized(ByteBuffer collection,
ByteBuffer from,
ByteBuffer to,
AbstractType<?> comparator,
boolean frozen)
{
// We don't allow slicing of list so we don't need this.
throw new UnsupportedOperationException();

View File

@ -129,6 +129,7 @@ public class MapSerializer<K, V> extends CollectionSerializer<Map<K, V>>
}
}
@Override
public ByteBuffer getSerializedValue(ByteBuffer collection, ByteBuffer key, AbstractType<?> comparator)
{
try
@ -155,7 +156,12 @@ public class MapSerializer<K, V> extends CollectionSerializer<Map<K, V>>
}
}
public ByteBuffer getSliceFromSerialized(ByteBuffer collection, ByteBuffer from, ByteBuffer to, AbstractType<?> comparator)
@Override
public ByteBuffer getSliceFromSerialized(ByteBuffer collection,
ByteBuffer from,
ByteBuffer to,
AbstractType<?> comparator,
boolean frozen)
{
if (from == ByteBufferUtil.UNSET_BYTE_BUFFER && to == ByteBufferUtil.UNSET_BYTE_BUFFER)
return collection;
@ -208,6 +214,10 @@ public class MapSerializer<K, V> extends CollectionSerializer<Map<K, V>>
if (comparison == 0)
break;
}
if (count == 0 && !frozen)
return null;
return copyAsNewCollection(collection, count, startPos, input.position(), ProtocolVersion.V3);
}
catch (BufferUnderflowException e)

View File

@ -140,6 +140,7 @@ public class SetSerializer<T> extends CollectionSerializer<Set<T>>
return (Class) Set.class;
}
@Override
public ByteBuffer getSerializedValue(ByteBuffer collection, ByteBuffer key, AbstractType<?> comparator)
{
try
@ -165,7 +166,12 @@ public class SetSerializer<T> extends CollectionSerializer<Set<T>>
}
}
public ByteBuffer getSliceFromSerialized(ByteBuffer collection, ByteBuffer from, ByteBuffer to, AbstractType<?> comparator)
@Override
public ByteBuffer getSliceFromSerialized(ByteBuffer collection,
ByteBuffer from,
ByteBuffer to,
AbstractType<?> comparator,
boolean frozen)
{
if (from == ByteBufferUtil.UNSET_BYTE_BUFFER && to == ByteBufferUtil.UNSET_BYTE_BUFFER)
return collection;
@ -216,6 +222,10 @@ public class SetSerializer<T> extends CollectionSerializer<Set<T>>
if (comparison == 0)
break;
}
if (count == 0 && !frozen)
return null;
return copyAsNewCollection(collection, count, startPos, input.position(), ProtocolVersion.V3);
}
catch (BufferUnderflowException e)

View File

@ -1198,7 +1198,7 @@ public class CollectionsTest extends CQLTester
assertRows(execute("SELECT k, l, m['2'..'3'], o FROM %s WHERE k = 0"),
row(0, "foobar", map("22", "value22"), 42),
row(0, "foobar", map(), 42)
row(0, "foobar", null, 42)
);
assertRows(execute("SELECT k, l, m['22'..], o FROM %s WHERE k = 0"),
@ -1711,7 +1711,7 @@ public class CollectionsTest extends CQLTester
assertRows(result,
row(0,
map("1", "one", "2", "two"), "two", map("2", "two"), map("1", "one", "2", "two"), map(),
map("1", "one", "2", "two"), "two", map("2", "two"), map("1", "one", "2", "two"), null,
map("1", "one", "2", "two"), "two", map("2", "two"), map("1", "one", "2", "two"), map(),
set("1", "2", "3"), "2", set("2", "3"), set("1", "2"), set("3"),
set("1", "2", "3"), "2", set("2", "3"), set("1", "2"), set("3")));
@ -1815,7 +1815,7 @@ public class CollectionsTest extends CQLTester
flush();
assertRows(execute("SELECT m[7..8] FROM %s WHERE k=?", 0),
row(map()));
row((Map<Integer, Integer>) null));
assertRows(execute("SELECT m[0..3] FROM %s WHERE k=?", 0),
row(map(0, 0, 1, 1, 2, 2, 3, 3)));
@ -1915,4 +1915,68 @@ public class CollectionsTest extends CQLTester
assertInvalidMessage("Invalid map literal for m: value (1, '1', 1.0, 1) is not of type frozen<tuple<int, text, double>>",
"INSERT INTO %s (k, m) VALUES (0, {1 : (1, '1', 1.0, 1)})");
}
@Test
public void testSelectionOfEmptyCollections() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, m frozen<map<text, int>>, s frozen<set<int>>)");
execute("INSERT INTO %s(k) VALUES (0)");
execute("INSERT INTO %s(k, m, s) VALUES (1, {}, {})");
execute("INSERT INTO %s(k, m, s) VALUES (2, ?, ?)", map(), set());
execute("INSERT INTO %s(k, m, s) VALUES (3, {'2':2}, {2})");
beforeAndAfterFlush(() ->
{
assertRows(execute("SELECT m, s FROM %s WHERE k = 0"), row(null, null));
assertRows(execute("SELECT m['0'], s[0] FROM %s WHERE k = 0"), row(null, null));
assertRows(execute("SELECT m['0'..'1'], s[0..1] FROM %s WHERE k = 0"), row(null, null));
assertRows(execute("SELECT m['0'..'1']['3'..'5'], s[0..1][3..5] FROM %s WHERE k = 0"), row(null, null));
assertRows(execute("SELECT m, s FROM %s WHERE k = 1"), row(map(), set()));
assertRows(execute("SELECT m['0'], s[0] FROM %s WHERE k = 1"), row(null, null));
assertRows(execute("SELECT m['0'..'1'], s[0..1] FROM %s WHERE k = 1"), row(map(), set()));
assertRows(execute("SELECT m['0'..'1']['3'..'5'], s[0..1][3..5] FROM %s WHERE k = 1"), row(map(), set()));
assertRows(execute("SELECT m, s FROM %s WHERE k = 2"), row(map(), set()));
assertRows(execute("SELECT m['0'], s[0] FROM %s WHERE k = 2"), row(null, null));
assertRows(execute("SELECT m['0'..'1'], s[0..1] FROM %s WHERE k = 2"), row(map(), set()));
assertRows(execute("SELECT m['0'..'1']['3'..'5'], s[0..1][3..5] FROM %s WHERE k = 2"), row(map(), set()));
assertRows(execute("SELECT m, s FROM %s WHERE k = 3"), row(map("2", 2), set(2)));
assertRows(execute("SELECT m['0'], s[0] FROM %s WHERE k = 3"), row(null, null));
assertRows(execute("SELECT m['0'..'1'], s[0..1] FROM %s WHERE k = 3"), row(map(), set()));
assertRows(execute("SELECT m['0'..'1']['3'..'5'], s[0..1][3..5] FROM %s WHERE k = 3"), row(map(), set()));
});
createTable("CREATE TABLE %s (k int PRIMARY KEY, m map<text, int>, s set<int>)");
execute("INSERT INTO %s(k) VALUES (0)");
execute("INSERT INTO %s(k, m, s) VALUES (1, {}, {})");
execute("INSERT INTO %s(k, m, s) VALUES (2, ?, ?)", map(), set());
execute("INSERT INTO %s(k, m, s) VALUES (3, {'2':2}, {2})");
beforeAndAfterFlush(() ->
{
assertRows(execute("SELECT m, s FROM %s WHERE k = 0"), row(null, null));
assertRows(execute("SELECT m['0'], s[0] FROM %s WHERE k = 0"), row(null, null));
assertRows(execute("SELECT m['0'..'1'], s[0..1] FROM %s WHERE k = 0"), row(null, null));
assertRows(execute("SELECT m['0'..'1']['3'..'5'], s[0..1][3..5] FROM %s WHERE k = 0"), row(null, null));
assertRows(execute("SELECT m, s FROM %s WHERE k = 1"), row(null, null));
assertRows(execute("SELECT m['0'], s[0] FROM %s WHERE k = 1"), row(null, null));
assertRows(execute("SELECT m['0'..'1'], s[0..1] FROM %s WHERE k = 1"), row(null, null));
assertRows(execute("SELECT m['0'..'1']['3'..'5'], s[0..1][3..5] FROM %s WHERE k = 1"), row(null, null));
assertRows(execute("SELECT m, s FROM %s WHERE k = 2"), row(null, null));
assertRows(execute("SELECT m['0'], s[0] FROM %s WHERE k = 2"), row(null, null));
assertRows(execute("SELECT m['0'..'1'], s[0..1] FROM %s WHERE k = 2"), row(null, null));
assertRows(execute("SELECT m['0'..'1']['3'..'5'], s[0..1][3..5] FROM %s WHERE k = 2"), row(null, null));
assertRows(execute("SELECT m, s FROM %s WHERE k = 3"), row(map("2", 2), set(2)));
assertRows(execute("SELECT m['0'], s[0] FROM %s WHERE k = 3"), row(null, null));
assertRows(execute("SELECT m['0'..'1'], s[0..1] FROM %s WHERE k = 3"), row(null, null));
assertRows(execute("SELECT m['0'..'1']['3'..'5'], s[0..1][3..5] FROM %s WHERE k = 3"), row(null, null));
});
}
}