Skip to content

Commit 4c33f1f

Browse files
Dekratesmiklosovic
authored andcommitted
Restrict BytesType compatibility to scalar types only
Converting collections or UDTs to raw bytes is nonsensical - it allows reading raw serialized bytes which have no meaningful interpretation. patch by Mikołaj Diakowski; reviewed by Stefan Miklosovic, Brandon Williams for CASSANDRA-20982
1 parent 34c3736 commit 4c33f1f

File tree

5 files changed

+89
-7
lines changed

5 files changed

+89
-7
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
4.0.20
2+
* Restrict BytesType compatibility to scalar types only (CASSANDRA-20982)
23
* Backport fix to nodetool gcstats output for direct memory (CASSANDRA-21037)
34
* ArrayIndexOutOfBoundsException with repaired data tracking and counters (CASSANDRA-20871)
45
* Fix cleanup of old incremental repair sessions in case of owned token range changes or a table deleting (CASSANDRA-20877)

src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,10 @@ private void addColumn(KeyspaceMetadata keyspace,
219219
// columns is pushed deeper down the line. The latter would still be problematic in cases of schema races.
220220
if (!type.isSerializationCompatibleWith(droppedColumn.type))
221221
{
222-
throw ire("Cannot re-add previously dropped column '%s' of type %s, incompatible with previous type %s",
222+
throw ire("Cannot add a column '%s' of type %s, incompatible with previously dropped column '%s' of type %s",
223223
name,
224224
type.asCQL3Type(),
225+
name,
225226
droppedColumn.type.asCQL3Type());
226227
}
227228

src/java/org/apache/cassandra/db/marshal/BytesType.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ public boolean isValueCompatibleWithInternal(AbstractType<?> otherType)
8585
return true;
8686
}
8787

88+
@Override
89+
public boolean isSerializationCompatibleWith(AbstractType<?> previous)
90+
{
91+
// BytesType should only be compatible with simple scalar types, not with collections or UDTs
92+
// because converting a collection or UDT to raw bytes is nonsensical
93+
return !previous.isCollection() && !previous.isUDT() && super.isSerializationCompatibleWith(previous);
94+
}
95+
8896
public CQL3Type asCQL3Type()
8997
{
9098
return CQL3Type.Native.BLOB;

test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,29 +52,30 @@ public void testNonFrozenCollectionsAreIncompatibleWithBlob() throws Throwable
5252
{
5353
createTable("CREATE TABLE %s (a int, b " + type + ", PRIMARY KEY (a));");
5454
alterTable("ALTER TABLE %s DROP b;");
55-
assertInvalidMessage("Cannot re-add previously dropped column 'b' of type blob, incompatible with previous type " + type,
55+
assertInvalidMessage("Cannot add a column 'b' of type blob, incompatible with previously dropped column 'b' of type " + type,
5656
"ALTER TABLE %s ADD b blob;");
5757
}
5858

5959
for (String type : collectionTypes)
6060
{
6161
createTable("CREATE TABLE %s (a int, b blob, PRIMARY KEY (a));");
6262
alterTable("ALTER TABLE %s DROP b;");
63-
assertInvalidMessage("Cannot re-add previously dropped column 'b' of type " + type + ", incompatible with previous type blob",
63+
assertInvalidMessage("Cannot add a column 'b' of type " + type + ", incompatible with previously dropped column 'b' of type blob",
6464
"ALTER TABLE %s ADD b " + type + ';');
6565
}
6666
}
6767

6868
@Test
69-
public void testFrozenCollectionsAreCompatibleWithBlob()
69+
public void testFrozenCollectionsAreNotCompatibleWithBlob() throws Throwable
7070
{
7171
String[] collectionTypes = new String[] {"frozen<map<int, int>>", "frozen<set<int>>", "frozen<list<int>>"};
7272

7373
for (String type : collectionTypes)
7474
{
7575
createTable("CREATE TABLE %s (a int, b " + type + ", PRIMARY KEY (a));");
7676
alterTable("ALTER TABLE %s DROP b;");
77-
alterTable("ALTER TABLE %s ADD b blob;");
77+
assertInvalidMessage("Cannot add a column 'b' of type blob, incompatible with previously dropped column 'b' of type " + type,
78+
"ALTER TABLE %s ADD b blob;");
7879
}
7980
}
8081

@@ -482,8 +483,8 @@ public void testInvalidDroppingAndAddingOfCollections() throws Throwable
482483
{
483484
createTable("create table %s (k int, c int, v " + typePair[0] + ", PRIMARY KEY (k, c))");
484485
execute("alter table %s drop v");
485-
assertInvalidMessage("Cannot re-add previously dropped column 'v' of type "
486-
+ typePair[1] + ", incompatible with previous type " + typePair[0],
486+
assertInvalidMessage("Cannot add a column 'v' of type "
487+
+ typePair[1] + ", incompatible with previously dropped column 'v' of type " + typePair[0],
487488
"alter table %s add v " + typePair[1]);
488489
}
489490
}

test/unit/org/apache/cassandra/db/marshal/BytesTypeTest.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@
1919
*/
2020
package org.apache.cassandra.db.marshal;
2121

22+
import java.util.Arrays;
23+
24+
import org.apache.cassandra.cql3.FieldIdentifier;
2225
import org.apache.cassandra.serializers.MarshalException;
26+
import org.apache.cassandra.utils.ByteBufferUtil;
2327
import org.junit.Test;
2428

29+
import static org.junit.Assert.assertFalse;
30+
import static org.junit.Assert.assertTrue;
31+
2532
public class BytesTypeTest
2633
{
2734
private static final String INVALID_HEX = "33AG45F"; // Invalid (has a G)
@@ -38,4 +45,68 @@ public void testFromStringWithValidString()
3845
{
3946
BytesType.instance.fromString(VALID_HEX);
4047
}
48+
49+
@Test
50+
public void testValueCompatibilityWithScalarTypes()
51+
{
52+
// BytesType should be value-compatible with simple scalar types
53+
assertTrue("BytesType should be value-compatible with AsciiType",
54+
BytesType.instance.isValueCompatibleWith(AsciiType.instance));
55+
assertTrue("BytesType should be value-compatible with UTF8Type",
56+
BytesType.instance.isValueCompatibleWith(UTF8Type.instance));
57+
assertTrue("BytesType should be value-compatible with Int32Type",
58+
BytesType.instance.isValueCompatibleWith(Int32Type.instance));
59+
assertTrue("BytesType should be value-compatible with LongType",
60+
BytesType.instance.isValueCompatibleWith(LongType.instance));
61+
assertTrue("BytesType should be value-compatible with UUIDType",
62+
BytesType.instance.isValueCompatibleWith(UUIDType.instance));
63+
assertTrue("BytesType should be value-compatible with BooleanType",
64+
BytesType.instance.isValueCompatibleWith(BooleanType.instance));
65+
assertTrue("BytesType should be value-compatible with itself",
66+
BytesType.instance.isValueCompatibleWith(BytesType.instance));
67+
}
68+
69+
@Test
70+
public void testSerializationIncompatibilityWithCollections()
71+
{
72+
// BytesType should NOT be serialization-compatible with collections (even frozen ones)
73+
// because converting a collection to raw bytes for schema changes is nonsensical
74+
ListType<?> frozenList = ListType.getInstance(Int32Type.instance, false);
75+
SetType<?> frozenSet = SetType.getInstance(Int32Type.instance, false);
76+
MapType<?, ?> frozenMap = MapType.getInstance(Int32Type.instance, UTF8Type.instance, false);
77+
78+
assertFalse("BytesType should NOT be serialization-compatible with frozen<list>",
79+
BytesType.instance.isSerializationCompatibleWith(frozenList));
80+
assertFalse("BytesType should NOT be serialization-compatible with frozen<set>",
81+
BytesType.instance.isSerializationCompatibleWith(frozenSet));
82+
assertFalse("BytesType should NOT be serialization-compatible with frozen<map>",
83+
BytesType.instance.isSerializationCompatibleWith(frozenMap));
84+
85+
// Also test with multi-cell collections
86+
ListType<?> multiCellList = ListType.getInstance(Int32Type.instance, true);
87+
SetType<?> multiCellSet = SetType.getInstance(Int32Type.instance, true);
88+
MapType<?, ?> multiCellMap = MapType.getInstance(Int32Type.instance, UTF8Type.instance, true);
89+
90+
assertFalse("BytesType should NOT be serialization-compatible with list",
91+
BytesType.instance.isSerializationCompatibleWith(multiCellList));
92+
assertFalse("BytesType should NOT be serialization-compatible with set",
93+
BytesType.instance.isSerializationCompatibleWith(multiCellSet));
94+
assertFalse("BytesType should NOT be serialization-compatible with map",
95+
BytesType.instance.isSerializationCompatibleWith(multiCellMap));
96+
}
97+
98+
@Test
99+
public void testSerializationIncompatibilityWithUDT()
100+
{
101+
// BytesType should NOT be serialization-compatible with User Defined Types
102+
// because converting a UDT to raw bytes for schema changes is nonsensical
103+
UserType udt = new UserType("ks",
104+
ByteBufferUtil.bytes("myType"),
105+
Arrays.asList(FieldIdentifier.forQuoted("field1"), FieldIdentifier.forQuoted("field2")),
106+
Arrays.asList(Int32Type.instance, UTF8Type.instance),
107+
true);
108+
109+
assertFalse("BytesType should NOT be serialization-compatible with UDT",
110+
BytesType.instance.isSerializationCompatibleWith(udt));
111+
}
41112
}

0 commit comments

Comments
 (0)