Skip to content

Commit 7ca013f

Browse files
akhakuabsurdfarce
authored andcommitted
JAVA-3057 Allow decoding a UDT that has more fields than expected
patch by Ammar Khaku; reviewed by Andy Tolbert and Bret McGuire reference: #1635
1 parent 7bc085b commit 7ca013f

File tree

3 files changed

+95
-16
lines changed
  • core/src
  • integration-tests/src/test/java/com/datastax/oss/driver/internal/core/type/codec

3 files changed

+95
-16
lines changed

core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,14 @@
3030
import java.nio.BufferUnderflowException;
3131
import java.nio.ByteBuffer;
3232
import net.jcip.annotations.ThreadSafe;
33+
import org.slf4j.Logger;
34+
import org.slf4j.LoggerFactory;
3335

3436
@ThreadSafe
3537
public class UdtCodec implements TypeCodec<UdtValue> {
3638

39+
private static final Logger LOG = LoggerFactory.getLogger(UdtCodec.class);
40+
3741
private final UserDefinedType cqlType;
3842

3943
public UdtCodec(@NonNull UserDefinedType cqlType) {
@@ -107,10 +111,8 @@ public UdtValue decode(@Nullable ByteBuffer bytes, @NonNull ProtocolVersion prot
107111
int i = 0;
108112
while (input.hasRemaining()) {
109113
if (i == cqlType.getFieldTypes().size()) {
110-
throw new IllegalArgumentException(
111-
String.format(
112-
"Too many fields in encoded UDT value, expected %d",
113-
cqlType.getFieldTypes().size()));
114+
LOG.debug("Encountered unexpected fields when parsing codec {}", cqlType);
115+
break;
114116
}
115117
int elementSize = input.getInt();
116118
ByteBuffer element;

core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,18 @@ public void should_decode_udt() {
136136
}
137137

138138
@Test
139-
public void should_fail_to_decode_udt_when_too_many_fields() {
140-
assertThatThrownBy(
141-
() ->
142-
decode(
143-
"0x"
144-
+ ("00000004" + "00000001")
145-
+ "ffffffff"
146-
+ ("00000001" + "61")
147-
// extra contents
148-
+ "ffffffff"))
149-
.isInstanceOf(IllegalArgumentException.class)
150-
.hasMessage("Too many fields in encoded UDT value, expected 3");
139+
public void should_decode_udt_when_too_many_fields() {
140+
UdtValue udt =
141+
decode(
142+
"0x"
143+
+ ("00000004" + "00000001")
144+
+ "ffffffff"
145+
+ ("00000001" + "61")
146+
// extra contents
147+
+ "ffffffff");
148+
assertThat(udt.getInt(0)).isEqualTo(1);
149+
assertThat(udt.isNull(1)).isTrue();
150+
assertThat(udt.getString(2)).isEqualTo("a");
151151
}
152152

153153
/** Test for JAVA-2557. Ensures that the codec can decode null fields with any negative length. */
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package com.datastax.oss.driver.internal.core.type.codec;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
22+
23+
import com.datastax.oss.driver.api.core.CqlSession;
24+
import com.datastax.oss.driver.api.core.cql.Row;
25+
import com.datastax.oss.driver.api.core.data.UdtValue;
26+
import com.datastax.oss.driver.api.core.type.UserDefinedType;
27+
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
28+
import com.datastax.oss.driver.api.testinfra.ccm.CcmRule;
29+
import com.datastax.oss.driver.api.testinfra.session.SessionRule;
30+
import com.datastax.oss.driver.categories.ParallelizableTests;
31+
import java.util.Objects;
32+
import org.junit.Rule;
33+
import org.junit.Test;
34+
import org.junit.experimental.categories.Category;
35+
import org.junit.rules.RuleChain;
36+
import org.junit.rules.TestRule;
37+
38+
@Category(ParallelizableTests.class)
39+
public class UdtCodecIT {
40+
41+
private CcmRule ccmRule = CcmRule.getInstance();
42+
43+
private SessionRule<CqlSession> sessionRule = SessionRule.builder(ccmRule).build();
44+
45+
@Rule public TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule);
46+
47+
@Test
48+
public void should_decoding_udt_be_backward_compatible() {
49+
CqlSession session = sessionRule.session();
50+
session.execute("CREATE TYPE test_type_1 (a text, b int)");
51+
session.execute("CREATE TABLE test_table_1 (e int primary key, f frozen<test_type_1>)");
52+
// insert a row using version 1 of the UDT schema
53+
session.execute("INSERT INTO test_table_1(e, f) VALUES(1, {a: 'a', b: 1})");
54+
UserDefinedType udt =
55+
session
56+
.getMetadata()
57+
.getKeyspace(sessionRule.keyspace())
58+
.flatMap(ks -> ks.getUserDefinedType("test_type_1"))
59+
.orElseThrow(IllegalStateException::new);
60+
TypeCodec<?> oldCodec = session.getContext().getCodecRegistry().codecFor(udt);
61+
// update UDT schema
62+
session.execute("ALTER TYPE test_type_1 add i text");
63+
// insert a row using version 2 of the UDT schema
64+
session.execute("INSERT INTO test_table_1(e, f) VALUES(2, {a: 'b', b: 2, i: 'b'})");
65+
Row row =
66+
Objects.requireNonNull(session.execute("SELECT f FROM test_table_1 WHERE e = ?", 2).one());
67+
// Try to read new row with old codec. Using row.getUdtValue() would not cause any issues,
68+
// because new codec will be automatically registered (using all 3 attributes).
69+
// If application leverages generic row.get(String, Codec) method, data reading with old codec
70+
// should
71+
// be backward-compatible.
72+
UdtValue value = Objects.requireNonNull((UdtValue) row.get("f", oldCodec));
73+
assertThat(value.getString("a")).isEqualTo("b");
74+
assertThat(value.getInt("b")).isEqualTo(2);
75+
assertThatThrownBy(() -> value.getString("i")).hasMessage("i is not a field in this UDT");
76+
}
77+
}

0 commit comments

Comments
 (0)