/*
* Copyright (C) 2012-2015 DataStax Inc.
*
* Licensed 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 com.datastax.driver.core;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.TypeToken;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static com.datastax.driver.core.TestUtils.getValue;
import static com.datastax.driver.core.TestUtils.setValue;
import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
public class GettableDataIntegrationTest extends CCMTestsSupport {
boolean is21;
CodecRegistry registry = new CodecRegistry();
// Used for generating unique keys.
AtomicInteger keyCounter = new AtomicInteger(0);
@Override
public void onTestContextInitialized() {
is21 = ccm().getCassandraVersion().compareTo(VersionNumber.parse("2.1.3")) > 0;
// only add tuples / nested collections at > 2.1.3.
execute("CREATE TABLE codec_mapping (k int PRIMARY KEY, "
+ "v int, l list<int>, m map<int,int>" +
(is21 ? ", t tuple<int,int>, s set<frozen<list<int>>>)" : ")"));
}
@Override
public Cluster.Builder createClusterBuilder() {
return Cluster.builder().withCodecRegistry(registry);
}
@BeforeClass(groups = "short")
public void setUpRegistry() {
for (TypeMapping<?> mapping : mappings) {
registry.register(mapping.codec);
}
}
static final ByteBuffer intBuf = ByteBuffer.allocate(4);
static {
intBuf.putInt(1);
intBuf.flip();
}
static InetAddress localhost;
static {
try {
localhost = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
localhost = null;
}
}
// Mappings of Codecs, Data Type to access as (used for determining what get|set method to call), value to set.
private TypeMapping<?>[] mappings = {
// set|getString
new TypeMapping<String>(new IntToStringCodec(), DataType.varchar(), "1"),
// set|getLong
new TypeMapping<Long>(new IntToLongCodec(), DataType.bigint(), 1L),
// set|getBytes
new TypeMapping<ByteBuffer>(new IntToByteBufferCodec(), DataType.blob(), intBuf),
// set|getBool
new TypeMapping<Boolean>(new IntToBooleanCodec(), DataType.cboolean(), true),
// set|getDecimal
new TypeMapping<BigDecimal>(new IntToBigDecimalCodec(), DataType.decimal(), new BigDecimal(1)),
// set|getDouble
new TypeMapping<Double>(new IntToDoubleCodec(), DataType.cdouble(), 1.0d),
// set|getFloat
new TypeMapping<Float>(new IntToFloatCodec(), DataType.cfloat(), 1.0f),
// set|getInet
new TypeMapping<InetAddress>(new IntToInetAddressCodec(), DataType.inet(), localhost),
// set|getTime
new TypeMapping<Long>(new IntToLongCodec(), DataType.time(), 8675309L),
// set|getByte
new TypeMapping<Byte>(new IntToByteCodec(), DataType.tinyint(), (byte) 0xCF),
// set|getShort
new TypeMapping<Short>(new IntToShortCodec(), DataType.smallint(), (short) 1003),
// set|getTimestamp
new TypeMapping<Date>(new IntToDateCodec(), DataType.timestamp(), new Date(124677)),
// set|getDate
new TypeMapping<LocalDate>(new IntToLocalDateCodec(), DataType.date(), LocalDate.fromDaysSinceEpoch(1523)),
// set|getUUID
new TypeMapping<UUID>(new IntToUUIDCodec(), DataType.uuid(), new UUID(244242, 0)),
// set|getVarint
new TypeMapping<BigInteger>(new IntToBigIntegerCodec(), DataType.varint(), BigInteger.valueOf(4566432L))
};
/**
* Validates that all {@link GettableData} types will allow their get methods to be invoked on a column that does
* not match data's cql type if a codec is registered that maps the java type of the getter method to the cql type
* of the column.
* <p/>
* Also validates that all {@link SettableData} types will allow their set methods to be invoked on a column that
* does not match data's cql type if a codec is registered that maps the java type of the setter method to the cql
* type of the column.
* <p/>
* Executes the following for each set|get set:
* <p/>
* <ol>
* <li>Insert row using a prepared statement and binding by name.</li>
* <li>Insert row using a prepared statement and binding by index.</li>
* <li>Insert row using a prepared statement and binding everything at once.</li>
* <li>Retrieve inserted rows and get values by name.</li>
* <li>Retrieve inserted rows and get values by index.</li>
* </ol>
*
* @jira_ticket JAVA-940
* @test_category queries
*/
@Test(groups = "short")
public void should_allow_getting_and_setting_by_type_if_codec_registered() {
String insertStmt = "INSERT INTO codec_mapping (k,v,l,m" + (is21 ? ",t,s" : "") + ") values (?,?,?,?" + (is21 ? ",?,?)" : ")");
PreparedStatement insert = session().prepare(insertStmt);
PreparedStatement select = session().prepare("SELECT v,l,m" + (is21 ? ",t,s" : "") + " from codec_mapping where k=?");
TupleType tupleType = new TupleType(newArrayList(DataType.cint(), DataType.cint()),
cluster().getConfiguration().getProtocolOptions().getProtocolVersion(), registry);
for (TypeMapping<?> mapping : mappings) {
// Keys used to insert data in this iteration.
List<Integer> keys = newArrayList();
// Values to store.
Map<Object, Object> map = ImmutableMap.of(mapping.value, mapping.value);
List<Object> list = newArrayList(mapping.value);
Set<List<Object>> set = ImmutableSet.of(list);
TupleValue tupleValue = new TupleValue(tupleType);
setValue(tupleValue, 0, mapping.outerType, mapping.value);
setValue(tupleValue, 1, mapping.outerType, mapping.value);
// Insert by name.
BoundStatement byName = insert.bind();
int byNameKey = keyCounter.incrementAndGet();
keys.add(byNameKey);
byName.setInt("k", byNameKey);
setValue(byName, "v", mapping.outerType, mapping.value);
byName.setList("l", list, mapping.javaType);
byName.setMap("m", map, mapping.javaType, mapping.javaType);
if (is21) {
byName.setTupleValue("t", tupleValue);
byName.setSet("s", set, TypeTokens.listOf(mapping.javaType));
}
session().execute(byName);
// Insert by index.
BoundStatement byIndex = insert.bind();
int byIndexKey = keyCounter.incrementAndGet();
keys.add(byIndexKey);
byIndex.setInt(0, byIndexKey);
setValue(byIndex, 1, mapping.outerType, mapping.value);
byIndex.setList(2, list, mapping.javaType);
byIndex.setMap(3, map, mapping.javaType, mapping.javaType);
if (is21) {
byIndex.setTupleValue(4, tupleValue);
byIndex.setSet(5, set, TypeTokens.listOf(mapping.javaType));
}
session().execute(byIndex);
// Insert by binding all at once.
BoundStatement fullBind;
int fullBindKey = keyCounter.incrementAndGet();
keys.add(fullBindKey);
if (is21) {
fullBind = insert.bind(fullBindKey, mapping.value, list, map, tupleValue, set);
} else {
fullBind = insert.bind(fullBindKey, mapping.value, list, map);
}
session().execute(fullBind);
for (int key : keys) {
// Retrieve by name.
Row row = session().execute(select.bind(key)).one();
assertThat(getValue(row, "v", mapping.outerType, registry)).isEqualTo(mapping.value);
assertThat(row.getList("l", mapping.codec.getJavaType())).isEqualTo(list);
assertThat(row.getMap("m", mapping.codec.getJavaType(), mapping.codec.getJavaType())).isEqualTo(map);
if (is21) {
TupleValue returnedTuple = row.getTupleValue("t");
assertThat(getValue(returnedTuple, 0, mapping.outerType, registry)).isEqualTo(mapping.value);
assertThat(getValue(returnedTuple, 1, mapping.outerType, registry)).isEqualTo(mapping.value);
assertThat(row.getSet("s", TypeTokens.listOf(mapping.javaType))).isEqualTo(set);
}
// Retrieve by index.
assertThat(getValue(row, 0, mapping.outerType, registry)).isEqualTo(mapping.value);
assertThat(row.getList(1, mapping.codec.getJavaType())).isEqualTo(list);
assertThat(row.getMap(2, mapping.codec.getJavaType(), mapping.codec.getJavaType())).isEqualTo(map);
if (is21) {
TupleValue returnedTuple = row.getTupleValue(3);
assertThat(getValue(returnedTuple, 0, mapping.outerType, registry)).isEqualTo(mapping.value);
assertThat(getValue(returnedTuple, 1, mapping.outerType, registry)).isEqualTo(mapping.value);
assertThat(row.getSet(4, TypeTokens.listOf(mapping.javaType))).isEqualTo(set);
}
}
}
}
private static class TypeMapping<T> {
final TypeCodec<T> codec;
final TypeToken<Object> javaType;
final DataType outerType;
final T value;
@SuppressWarnings("unchecked")
TypeMapping(TypeCodec<T> codec, DataType outerType, T value) {
this.codec = codec;
this.javaType = (TypeToken<Object>) codec.getJavaType();
this.outerType = outerType;
this.value = value;
}
}
// Int <-> Type mappings.
private static class IntToLongCodec extends MappingCodec<Long, Integer> {
IntToLongCodec() {
super(TypeCodec.cint(), Long.class);
}
@Override
protected Long deserialize(Integer value) {
return value.longValue();
}
@Override
protected Integer serialize(Long value) {
return value.intValue();
}
}
private static class IntToStringCodec extends MappingCodec<String, Integer> {
IntToStringCodec() {
super(TypeCodec.cint(), String.class);
}
@Override
protected String deserialize(Integer value) {
return value.toString();
}
@Override
protected Integer serialize(String value) {
return Integer.parseInt(value);
}
}
private static class IntToByteBufferCodec extends MappingCodec<ByteBuffer, Integer> {
IntToByteBufferCodec() {
super(TypeCodec.cint(), ByteBuffer.class);
}
@Override
protected ByteBuffer deserialize(Integer value) {
ByteBuffer buf = ByteBuffer.allocate(4);
buf.putInt(value);
buf.flip();
return buf;
}
@Override
protected Integer serialize(ByteBuffer value) {
return value.duplicate().getInt();
}
}
private static class IntToBooleanCodec extends MappingCodec<Boolean, Integer> {
IntToBooleanCodec() {
super(TypeCodec.cint(), Boolean.class);
}
@Override
protected Boolean deserialize(Integer value) {
return value != 0;
}
@Override
protected Integer serialize(Boolean value) {
return value ? 1 : 0;
}
}
private static class IntToBigDecimalCodec extends MappingCodec<BigDecimal, Integer> {
IntToBigDecimalCodec() {
super(TypeCodec.cint(), BigDecimal.class);
}
@Override
protected BigDecimal deserialize(Integer value) {
return new BigDecimal(value);
}
@Override
protected Integer serialize(BigDecimal value) {
return value.intValue();
}
}
private static class IntToDoubleCodec extends MappingCodec<Double, Integer> {
IntToDoubleCodec() {
super(TypeCodec.cint(), Double.class);
}
@Override
protected Double deserialize(Integer value) {
return value.doubleValue();
}
@Override
protected Integer serialize(Double value) {
return value.intValue();
}
}
private static class IntToFloatCodec extends MappingCodec<Float, Integer> {
IntToFloatCodec() {
super(TypeCodec.cint(), Float.class);
}
@Override
protected Float deserialize(Integer value) {
return value.floatValue();
}
@Override
protected Integer serialize(Float value) {
return value.intValue();
}
}
private static class IntToInetAddressCodec extends MappingCodec<InetAddress, Integer> {
IntToInetAddressCodec() {
super(TypeCodec.cint(), InetAddress.class);
}
@Override
protected InetAddress deserialize(Integer value) {
byte[] address = ByteBuffer.allocate(4).putInt(value).array();
try {
return InetAddress.getByAddress(address);
} catch (UnknownHostException e) {
return null;
}
}
@Override
protected Integer serialize(InetAddress value) {
return ByteBuffer.wrap(value.getAddress()).getInt();
}
}
private static class IntToByteCodec extends MappingCodec<Byte, Integer> {
IntToByteCodec() {
super(TypeCodec.cint(), Byte.class);
}
@Override
protected Byte deserialize(Integer value) {
return value.byteValue();
}
@Override
protected Integer serialize(Byte value) {
return value.intValue();
}
}
private static class IntToShortCodec extends MappingCodec<Short, Integer> {
IntToShortCodec() {
super(TypeCodec.cint(), Short.class);
}
@Override
protected Short deserialize(Integer value) {
return value.shortValue();
}
@Override
protected Integer serialize(Short value) {
return value.intValue();
}
}
private static class IntToDateCodec extends MappingCodec<Date, Integer> {
IntToDateCodec() {
super(TypeCodec.cint(), Date.class);
}
@Override
protected Date deserialize(Integer value) {
return new Date(value);
}
@Override
protected Integer serialize(Date value) {
return new Long(value.getTime()).intValue();
}
}
private static class IntToLocalDateCodec extends MappingCodec<LocalDate, Integer> {
IntToLocalDateCodec() {
super(TypeCodec.cint(), LocalDate.class);
}
@Override
protected LocalDate deserialize(Integer value) {
return LocalDate.fromDaysSinceEpoch(value);
}
@Override
protected Integer serialize(LocalDate value) {
return value.getDaysSinceEpoch();
}
}
private static class IntToUUIDCodec extends MappingCodec<UUID, Integer> {
IntToUUIDCodec() {
super(TypeCodec.cint(), UUID.class);
}
@Override
protected UUID deserialize(Integer value) {
return new UUID(value, 0);
}
@Override
protected Integer serialize(UUID value) {
return new Long(value.getMostSignificantBits()).intValue();
}
}
private static class IntToBigIntegerCodec extends MappingCodec<BigInteger, Integer> {
IntToBigIntegerCodec() {
super(TypeCodec.cint(), BigInteger.class);
}
@Override
protected BigInteger deserialize(Integer value) {
return BigInteger.valueOf((long) value);
}
@Override
protected Integer serialize(BigInteger value) {
return value.intValue();
}
}
}