Skip to content

Commit 74b77c8

Browse files
committed
JAVA-1415: Correctly report if a UDT column is frozen
1 parent d8c646a commit 74b77c8

10 files changed

Lines changed: 109 additions & 28 deletions

File tree

changelog/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- [improvement] JAVA-1328: Provide compatibility with Guava 20.
1616
- [improvement] JAVA-1247: Disable idempotence warnings.
1717
- [improvement] JAVA-1286: Support setting and retrieving udt fields in QueryBuilder.
18+
- [bug] JAVA-1415: Correctly report if a UDT column is frozen.
1819

1920
Merged from 3.1.x branch:
2021

driver-core/src/main/java/com/datastax/driver/core/ColumnDefinitions.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ public String getName(int i) {
145145

146146
/**
147147
* Returns the type of the {@code i}th column in this metadata.
148+
* <p>
149+
* Note that this method does not set the {@link DataType#isFrozen()} flag on the returned
150+
* object, it will always default to {@code false}. Use {@link Cluster#getMetadata()} to
151+
* determine if a column is frozen.
148152
*
149153
* @param i the index in this metadata.
150154
* @return the type of the {@code i}th column in this metadata.
@@ -156,6 +160,10 @@ public DataType getType(int i) {
156160

157161
/**
158162
* Returns the type of the first occurrence of {@code name} in this metadata.
163+
* <p>
164+
* Note that this method does not set the {@link DataType#isFrozen()} flag on the returned
165+
* object, it will always default to {@code false}. Use {@link Cluster#getMetadata()} to
166+
* determine if a column is frozen.
159167
*
160168
* @param name the name of the column.
161169
* @return the type of (the first occurrence of) {@code name} in this metadata.

driver-core/src/main/java/com/datastax/driver/core/DataType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ static DataType decode(ByteBuf buffer, ProtocolVersion protocolVersion, CodecReg
188188
DataType fieldType = decode(buffer, protocolVersion, codecRegistry);
189189
fields.add(new UserType.Field(fieldName, fieldType));
190190
}
191-
return new UserType(keyspace, type, fields, protocolVersion, codecRegistry);
191+
return new UserType(keyspace, type, false, fields, protocolVersion, codecRegistry);
192192
case TUPLE:
193193
nFields = buffer.readShort() & 0xffff;
194194
List<DataType> types = new ArrayList<DataType>(nFields);

driver-core/src/main/java/com/datastax/driver/core/DataTypeClassNameParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ static DataType parseOne(String className, ProtocolVersion protocolVersion, Code
113113
List<UserType.Field> fields = new ArrayList<UserType.Field>(rawFields.size());
114114
for (Map.Entry<String, String> entry : rawFields.entrySet())
115115
fields.add(new UserType.Field(entry.getKey(), parseOne(entry.getValue(), protocolVersion, codecRegistry)));
116-
return new UserType(keyspace, typeName, fields, protocolVersion, codecRegistry);
116+
// create a frozen UserType since C* 2.x UDTs are always frozen.
117+
return new UserType(keyspace, typeName, true, fields, protocolVersion, codecRegistry);
117118
}
118119

119120
if (isTupleType(next)) {

driver-core/src/main/java/com/datastax/driver/core/DataTypeCqlNameParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ static DataType parse(String toParse, Cluster cluster, String currentKeyspaceNam
137137
// We need to remove escaped double quotes within the type name as it is stored unescaped.
138138
// Otherwise it's a UDT. If we only want a shallow definition build it, otherwise search known definitions.
139139
if (shallowUserTypes)
140-
return new UserType.Shallow(currentKeyspaceName, Metadata.handleId(type));
140+
return new UserType.Shallow(currentKeyspaceName, Metadata.handleId(type), frozen);
141141

142142
UserType userType = null;
143143
if (currentUserTypes != null)
@@ -148,7 +148,7 @@ static DataType parse(String toParse, Cluster cluster, String currentKeyspaceNam
148148
if (userType == null)
149149
throw new UnresolvedUserTypeException(currentKeyspaceName, type);
150150
else
151-
return userType;
151+
return userType.copy(frozen);
152152
}
153153

154154
private static class Parser {

driver-core/src/main/java/com/datastax/driver/core/UserType.java

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ public class UserType extends DataType implements Iterable<UserType.Field> {
3333

3434
private final String keyspace;
3535
private final String typeName;
36+
private final boolean frozen;
3637
private final ProtocolVersion protocolVersion;
3738

39+
// can be null, if this object is being constructed from a response message
40+
// see Responses.Result.Rows.Metadata.decode()
3841
private volatile CodecRegistry codecRegistry;
3942

4043
// Note that we don't expose the order of fields, from an API perspective this is a map
@@ -46,20 +49,31 @@ public class UserType extends DataType implements Iterable<UserType.Field> {
4649
// implementation.
4750
final Map<String, int[]> byName;
4851

49-
UserType(String keyspace, String typeName, Collection<Field> fields, ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
50-
super(DataType.Name.UDT);
52+
private UserType(Name name, String keyspace, String typeName, boolean frozen, ProtocolVersion protocolVersion, CodecRegistry codecRegistry, Field[] byIdx, Map<String, int[]> byName) {
53+
super(name);
5154
this.keyspace = keyspace;
5255
this.typeName = typeName;
56+
this.frozen = frozen;
5357
this.protocolVersion = protocolVersion;
54-
// codecRegistry can be null, if this object is being constructed from a response message
55-
// see Responses.Result.Rows.Metadata.decode()
5658
this.codecRegistry = codecRegistry;
57-
this.byIdx = fields.toArray(new Field[fields.size()]);
59+
this.byIdx = byIdx;
60+
this.byName = byName;
61+
}
62+
63+
UserType(String keyspace, String typeName, boolean frozen, Collection<Field> fields, ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
64+
this(DataType.Name.UDT, keyspace, typeName, frozen, protocolVersion, codecRegistry,
65+
fields.toArray(new Field[fields.size()]),
66+
mapByName(fields));
67+
}
5868

69+
private static ImmutableMap<String, int[]> mapByName(Collection<Field> fields) {
5970
ImmutableMap.Builder<String, int[]> builder = new ImmutableMap.Builder<String, int[]>();
60-
for (int i = 0; i < byIdx.length; i++)
61-
builder.put(byIdx[i].getName(), new int[]{i});
62-
this.byName = builder.build();
71+
int i = 0;
72+
for (Field field : fields) {
73+
builder.put(field.getName(), new int[]{i});
74+
i += 1;
75+
}
76+
return builder.build();
6377
}
6478

6579
static UserType build(KeyspaceMetadata ksm, Row row, VersionNumber version, Cluster cluster, Map<String, UserType> userTypes) {
@@ -82,7 +96,7 @@ static UserType build(KeyspaceMetadata ksm, Row row, VersionNumber version, Clus
8296
}
8397
fields.add(new Field(fieldNames.get(i), fieldType));
8498
}
85-
return new UserType(keyspace, name, fields, protocolVersion, codecRegistry);
99+
return new UserType(keyspace, name, false, fields, protocolVersion, codecRegistry);
86100
}
87101

88102
/**
@@ -174,7 +188,15 @@ public DataType getFieldType(String name) {
174188

175189
@Override
176190
public boolean isFrozen() {
177-
return true;
191+
return frozen;
192+
}
193+
194+
public UserType copy(boolean newFrozen) {
195+
if (newFrozen == frozen) {
196+
return this;
197+
} else {
198+
return new UserType(name, keyspace, typeName, newFrozen, protocolVersion, codecRegistry, byIdx, byName);
199+
}
178200
}
179201

180202
@Override
@@ -340,16 +362,18 @@ static class Shallow extends DataType {
340362

341363
final String keyspaceName;
342364
final String typeName;
365+
final boolean frozen;
343366

344-
Shallow(String keyspaceName, String typeName) {
367+
Shallow(String keyspaceName, String typeName, boolean frozen) {
345368
super(Name.UDT);
346369
this.keyspaceName = keyspaceName;
347370
this.typeName = typeName;
371+
this.frozen = frozen;
348372
}
349373

350374
@Override
351375
public boolean isFrozen() {
352-
return false;
376+
return frozen;
353377
}
354378
}
355379
}

driver-core/src/test/java/com/datastax/driver/core/CodecRegistryTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,25 +362,25 @@ public void should_create_tuple_codec() {
362362
@Test(groups = "unit")
363363
public void should_create_udt_codec() {
364364
CodecRegistry registry = new CodecRegistry();
365-
UserType udt = new UserType("ks", "test", Collections.<UserType.Field>emptyList(), V4, registry);
365+
UserType udt = new UserType("ks", "test", false, Collections.<UserType.Field>emptyList(), V4, registry);
366366
assertThat(registry.codecFor(udt))
367367
.isNotNull()
368368
.accepts(udt)
369369
.accepts(UDTValue.class);
370370
registry = new CodecRegistry();
371-
udt = new UserType("ks", "test", Collections.<UserType.Field>emptyList(), V4, registry);
371+
udt = new UserType("ks", "test", false, Collections.<UserType.Field>emptyList(), V4, registry);
372372
assertThat(registry.codecFor(udt, UDTValue.class))
373373
.isNotNull()
374374
.accepts(udt)
375375
.accepts(UDTValue.class);
376376
registry = new CodecRegistry();
377-
udt = new UserType("ks", "test", Collections.<UserType.Field>emptyList(), V4, registry);
377+
udt = new UserType("ks", "test", false, Collections.<UserType.Field>emptyList(), V4, registry);
378378
assertThat(registry.codecFor(new UDTValue(udt)))
379379
.isNotNull()
380380
.accepts(udt)
381381
.accepts(UDTValue.class);
382382
registry = new CodecRegistry();
383-
udt = new UserType("ks", "test", Collections.<UserType.Field>emptyList(), V4, registry);
383+
udt = new UserType("ks", "test", false, Collections.<UserType.Field>emptyList(), V4, registry);
384384
assertThat(registry.codecFor(udt, new UDTValue(udt)))
385385
.isNotNull()
386386
.accepts(udt)

driver-core/src/test/java/com/datastax/driver/core/DataTypeTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ public void parseFormatMapTest() {
238238
public void parseFormatUDTTest() {
239239
String toParse = "{t:'fo''o',i:3,\"L\":['a','b'],s:{3:{a:0x01}}}";
240240

241-
final UserType udt1 = new UserType("ks", "t", Arrays.asList(new UserType.Field("a", DataType.blob())), protocolVersion, codecRegistry);
242-
UserType udt2 = new UserType("ks", "t", Arrays.asList(
241+
final UserType udt1 = new UserType("ks", "t", false, Arrays.asList(new UserType.Field("a", DataType.blob())), protocolVersion, codecRegistry);
242+
UserType udt2 = new UserType("ks", "t", false, Arrays.asList(
243243
new UserType.Field("t", DataType.text()),
244244
new UserType.Field("i", DataType.cint()),
245245
new UserType.Field("L", DataType.list(DataType.text())),

driver-core/src/test/java/com/datastax/driver/core/TypeCodecTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,10 @@ public void test_varchar_vs_text() {
148148
.accepts(t2)
149149
.accepts(t3)
150150
.accepts(t4);
151-
UserType u1 = new UserType("ks", "table", newArrayList(new Field("f1", varchar()), new Field("f2", varchar())), V3, new CodecRegistry());
152-
UserType u2 = new UserType("ks", "table", newArrayList(new Field("f1", text()), new Field("f2", varchar())), V3, new CodecRegistry());
153-
UserType u3 = new UserType("ks", "table", newArrayList(new Field("f1", varchar()), new Field("f2", text())), V3, new CodecRegistry());
154-
UserType u4 = new UserType("ks", "table", newArrayList(new Field("f1", text()), new Field("f2", text())), V3, new CodecRegistry());
151+
UserType u1 = new UserType("ks", "table", false, newArrayList(new Field("f1", varchar()), new Field("f2", varchar())), V3, new CodecRegistry());
152+
UserType u2 = new UserType("ks", "table", false, newArrayList(new Field("f1", text()), new Field("f2", varchar())), V3, new CodecRegistry());
153+
UserType u3 = new UserType("ks", "table", false, newArrayList(new Field("f1", varchar()), new Field("f2", text())), V3, new CodecRegistry());
154+
UserType u4 = new UserType("ks", "table", false, newArrayList(new Field("f1", text()), new Field("f2", text())), V3, new CodecRegistry());
155155
assertThat(TypeCodec.userType(u1))
156156
.accepts(u2)
157157
.accepts(u3)
@@ -219,7 +219,7 @@ public void should_deserialize_empty_buffer_as_tuple_with_null_values() {
219219
@Test(groups = "unit")
220220
public void should_deserialize_empty_buffer_as_udt_with_null_values() {
221221
CodecRegistry codecRegistry = new CodecRegistry();
222-
UserType udt = new UserType("ks", "t", Arrays.asList(
222+
UserType udt = new UserType("ks", "t", false, Arrays.asList(
223223
new UserType.Field("t", DataType.text()),
224224
new UserType.Field("i", DataType.cint()),
225225
new UserType.Field("l", DataType.list(DataType.text()))

driver-core/src/test/java/com/datastax/driver/core/UserTypesTest.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ public void onTestContextInitialized() {
5959
samples = PrimitiveTypeSamples.samples(protocolVersion);
6060
String type1 = "CREATE TYPE phone (alias text, number text)";
6161
String type2 = "CREATE TYPE \"\"\"User Address\"\"\" (street text, \"ZIP\"\"\" int, phones set<frozen<phone>>)";
62+
String type3 = "CREATE TYPE type_for_frozen_test(i int)";
6263
String table = "CREATE TABLE user (id int PRIMARY KEY, addr frozen<\"\"\"User Address\"\"\">)";
63-
execute(type1, type2, table);
64+
execute(type1, type2, type3, table);
6465
// Ci tests fail with "unconfigured columnfamily user"
6566
check().that(userTableExists).before(5, MINUTES).becomesTrue();
6667
}
@@ -468,4 +469,50 @@ public void should_save_and_retrieve_UDTs_with_null_collections() throws Excepti
468469
userType = userTypeDef.newValue().setList(0, new ArrayList<Object>());
469470
assertThat(row.getUDTValue("b")).isEqualTo(userType);
470471
}
472+
473+
@Test(groups = "short")
474+
public void should_indicate_user_type_is_frozen() {
475+
session().execute("CREATE TABLE frozen_table(k int primary key, v frozen<type_for_frozen_test>)");
476+
477+
KeyspaceMetadata keyspaceMetadata = cluster().getMetadata().getKeyspace(this.keyspace);
478+
479+
assertThat(keyspaceMetadata.getUserType("type_for_frozen_test"))
480+
.isNotFrozen();
481+
482+
assertThat(keyspaceMetadata.getTable("frozen_table").getColumn("v").getType())
483+
.isFrozen();
484+
485+
// The frozen flag is not set for result set definitions (the protocol does not provide
486+
// that information and it's not really useful in that situation). We always return false.
487+
ResultSet rs = session().execute("SELECT v FROM frozen_table WHERE k = 1");
488+
assertThat(rs.getColumnDefinitions().getType(0))
489+
.isNotFrozen();
490+
491+
// Same thing for prepared statements
492+
PreparedStatement pst = session().prepare("SELECT v FROM frozen_table WHERE k = ?");
493+
assertThat(pst.getVariables().getType(0))
494+
.isNotFrozen();
495+
}
496+
497+
@Test(groups = "short")
498+
@CassandraVersion(value = "3.6", description = "Non-frozen UDTs were introduced in C* 3.6")
499+
public void should_indicate_user_type_is_not_frozen() {
500+
session().execute("CREATE TABLE not_frozen_table(k int primary key, v type_for_frozen_test)");
501+
502+
KeyspaceMetadata keyspaceMetadata = cluster().getMetadata().getKeyspace(this.keyspace);
503+
504+
assertThat(keyspaceMetadata.getUserType("type_for_frozen_test"))
505+
.isNotFrozen();
506+
507+
assertThat(keyspaceMetadata.getTable("not_frozen_table").getColumn("v").getType())
508+
.isNotFrozen();
509+
510+
ResultSet rs = session().execute("SELECT v FROM not_frozen_table WHERE k = 1");
511+
assertThat(rs.getColumnDefinitions().getType(0))
512+
.isNotFrozen();
513+
514+
PreparedStatement pst = session().prepare("SELECT v FROM not_frozen_table WHERE k = ?");
515+
assertThat(pst.getVariables().getType(0))
516+
.isNotFrozen();
517+
}
471518
}

0 commit comments

Comments
 (0)