/*
* 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.datastax.driver.core.utils.CassandraVersion;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.testng.annotations.Test;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.Callable;
import static com.datastax.driver.core.Assertions.assertThat;
import static com.datastax.driver.core.ConditionChecker.check;
import static com.datastax.driver.core.Metadata.quote;
import static java.util.concurrent.TimeUnit.MINUTES;
@CassandraVersion("2.1.0")
public class UserTypesTest extends CCMTestsSupport {
private List<DataType> DATA_TYPE_PRIMITIVES;
private Map<DataType, Object> samples;
private final static List<DataType.Name> DATA_TYPE_NON_PRIMITIVE_NAMES =
new ArrayList<DataType.Name>(EnumSet.of(DataType.Name.LIST, DataType.Name.SET, DataType.Name.MAP, DataType.Name.TUPLE));
private final Callable<Boolean> userTableExists = new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return cluster().getMetadata().getKeyspace(keyspace).getTable("user") != null;
}
};
@Override
public void onTestContextInitialized() {
ProtocolVersion protocolVersion = ccm().getProtocolVersion();
DATA_TYPE_PRIMITIVES = new ArrayList<DataType>(TestUtils.allPrimitiveTypes(protocolVersion));
DATA_TYPE_PRIMITIVES.remove(DataType.counter());
samples = PrimitiveTypeSamples.samples(protocolVersion);
String type1 = "CREATE TYPE phone (alias text, number text)";
String type2 = "CREATE TYPE \"\"\"User Address\"\"\" (street text, \"ZIP\"\"\" int, phones set<frozen<phone>>)";
String type3 = "CREATE TYPE type_for_frozen_test(i int)";
String table = "CREATE TABLE user (id int PRIMARY KEY, addr frozen<\"\"\"User Address\"\"\">)";
execute(type1, type2, type3, table);
// Ci tests fail with "unconfigured columnfamily user"
check().that(userTableExists).before(5, MINUTES).becomesTrue();
}
@Test(groups = "short")
public void should_store_and_retrieve_with_prepared_statements() throws Exception {
int userId = 0;
PreparedStatement ins = session().prepare("INSERT INTO user(id, addr) VALUES (?, ?)");
PreparedStatement sel = session().prepare("SELECT * FROM user WHERE id=?");
UserType addrDef = cluster().getMetadata().getKeyspace(keyspace).getUserType(quote("\"User Address\""));
UserType phoneDef = cluster().getMetadata().getKeyspace(keyspace).getUserType("phone");
UDTValue phone1 = phoneDef.newValue().setString("alias", "home").setString("number", "0123548790");
UDTValue phone2 = phoneDef.newValue().setString("alias", "work").setString("number", "0698265251");
UDTValue addr = addrDef.newValue().setString("street", "1600 Pennsylvania Ave NW").setInt(quote("ZIP\""), 20500).setSet("phones", ImmutableSet.of(phone1, phone2));
session().execute(ins.bind(userId, addr));
Row r = session().execute(sel.bind(userId)).one();
assertThat(r.getInt("id")).isEqualTo(0);
}
@Test(groups = "short")
public void should_store_and_retrieve_with_simple_statements() throws Exception {
int userId = 1;
UserType addrDef = cluster().getMetadata().getKeyspace(keyspace).getUserType(quote("\"User Address\""));
UserType phoneDef = cluster().getMetadata().getKeyspace(keyspace).getUserType("phone");
UDTValue phone1 = phoneDef.newValue().setString("alias", "home").setString("number", "0123548790");
UDTValue phone2 = phoneDef.newValue().setString("alias", "work").setString("number", "0698265251");
UDTValue addr = addrDef.newValue().setString("street", "1600 Pennsylvania Ave NW").setInt(quote("ZIP\""), 20500).setSet("phones", ImmutableSet.of(phone1, phone2));
session().execute("INSERT INTO user(id, addr) VALUES (?, ?)", userId, addr);
Row r = session().execute("SELECT * FROM user WHERE id=?", userId).one();
assertThat(r.getInt("id")).isEqualTo(userId);
assertThat(r.getUDTValue("addr")).isEqualTo(addr);
}
@Test(groups = "short")
public void should_store_type_definitions_in_their_keyspace() throws Exception {
KeyspaceMetadata thisKeyspace = cluster().getMetadata().getKeyspace(this.keyspace);
// Types that don't exist don't have definitions
assertThat(thisKeyspace.getUserType("address1"))
.isNull();
assertThat(thisKeyspace.getUserType("phone1"))
.isNull();
// Types created by this test have definitions
assertThat(thisKeyspace.getUserType(quote("\"User Address\"")))
.isNotNull();
assertThat(thisKeyspace.getUserType("phone"))
.isNotNull();
// If we create another keyspace, it doesn't have the definitions of this keyspace
String otherKeyspaceName = this.keyspace + "_nonEx";
session().execute("CREATE KEYSPACE " + otherKeyspaceName + " " +
"WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor': '1'}");
KeyspaceMetadata otherKeyspace = cluster().getMetadata().getKeyspace(otherKeyspaceName);
assertThat(otherKeyspace.getUserType(quote("\"User Address\"")))
.isNull();
assertThat(otherKeyspace.getUserType("phone"))
.isNull();
}
@Test(groups = "short")
public void should_handle_UDT_with_many_fields() throws Exception {
int MAX_TEST_LENGTH = 1024;
// create the seed udt
StringBuilder sb = new StringBuilder();
for (int i = 0; i < MAX_TEST_LENGTH; ++i) {
sb.append(String.format("v_%s int", i));
if (i + 1 < MAX_TEST_LENGTH)
sb.append(",");
}
session().execute(String.format("CREATE TYPE lengthy_udt (%s)", sb.toString()));
// create a table with multiple sizes of udts
session().execute("CREATE TABLE lengthy_udt_table (k int PRIMARY KEY, v frozen<lengthy_udt>)");
// hold onto the UserType for future use
UserType udtDef = cluster().getMetadata().getKeyspace(keyspace).getUserType("lengthy_udt");
// verify inserts and reads
for (int i : Arrays.asList(0, 1, 2, 3, MAX_TEST_LENGTH)) {
// create udt
UDTValue createdUDT = udtDef.newValue();
for (int j = 0; j < i; ++j) {
createdUDT.setInt(j, j);
}
// write udt
session().execute("INSERT INTO lengthy_udt_table (k, v) VALUES (0, ?)", createdUDT);
// verify udt was written and read correctly
UDTValue r = session().execute("SELECT v FROM lengthy_udt_table WHERE k=0")
.one().getUDTValue("v");
assertThat(r.toString()).isEqualTo(createdUDT.toString());
}
}
@Test(groups = "short")
public void should_store_and_retrieve_UDT_containing_any_primitive_type() throws Exception {
// create UDT
List<String> alpha_type_list = new ArrayList<String>();
int startIndex = (int) 'a';
for (int i = 0; i < DATA_TYPE_PRIMITIVES.size(); i++) {
alpha_type_list.add(String.format("%s %s", Character.toString((char) (startIndex + i)),
DATA_TYPE_PRIMITIVES.get(i).getName()));
}
session().execute(String.format("CREATE TYPE alldatatypes (%s)", Joiner.on(',').join(alpha_type_list)));
session().execute("CREATE TABLE alldatatypes_table (a int PRIMARY KEY, b frozen<alldatatypes>)");
// insert UDT data
UserType alldatatypesDef = cluster().getMetadata().getKeyspace(keyspace).getUserType("alldatatypes");
UDTValue alldatatypes = alldatatypesDef.newValue();
for (int i = 0; i < DATA_TYPE_PRIMITIVES.size(); i++) {
DataType dataType = DATA_TYPE_PRIMITIVES.get(i);
String index = Character.toString((char) (startIndex + i));
Object sampleData = samples.get(dataType);
switch (dataType.getName()) {
case ASCII:
alldatatypes.setString(index, (String) sampleData);
break;
case BIGINT:
alldatatypes.setLong(index, (Long) sampleData);
break;
case BLOB:
alldatatypes.setBytes(index, (ByteBuffer) sampleData);
break;
case BOOLEAN:
alldatatypes.setBool(index, (Boolean) sampleData);
break;
case DECIMAL:
alldatatypes.setDecimal(index, (BigDecimal) sampleData);
break;
case DOUBLE:
alldatatypes.setDouble(index, (Double) sampleData);
break;
case DURATION:
alldatatypes.set(index, Duration.from(sampleData.toString()), Duration.class);
break;
case FLOAT:
alldatatypes.setFloat(index, (Float) sampleData);
break;
case INET:
alldatatypes.setInet(index, (InetAddress) sampleData);
break;
case TINYINT:
alldatatypes.setByte(index, (Byte) sampleData);
break;
case SMALLINT:
alldatatypes.setShort(index, (Short) sampleData);
break;
case INT:
alldatatypes.setInt(index, (Integer) sampleData);
break;
case TEXT:
alldatatypes.setString(index, (String) sampleData);
break;
case TIMESTAMP:
alldatatypes.setTimestamp(index, ((Date) sampleData));
break;
case DATE:
alldatatypes.setDate(index, ((LocalDate) sampleData));
break;
case TIME:
alldatatypes.setTime(index, ((Long) sampleData));
break;
case TIMEUUID:
alldatatypes.setUUID(index, (UUID) sampleData);
break;
case UUID:
alldatatypes.setUUID(index, (UUID) sampleData);
break;
case VARCHAR:
alldatatypes.setString(index, (String) sampleData);
break;
case VARINT:
alldatatypes.setVarint(index, (BigInteger) sampleData);
break;
}
}
PreparedStatement ins = session().prepare("INSERT INTO alldatatypes_table (a, b) VALUES (?, ?)");
session().execute(ins.bind(0, alldatatypes));
// retrieve and verify data
ResultSet rs = session().execute("SELECT * FROM alldatatypes_table");
List<Row> rows = rs.all();
assertThat(rows.size()).isEqualTo(1);
Row row = rows.get(0);
assertThat(row.getInt("a")).isEqualTo(0);
assertThat(row.getUDTValue("b")).isEqualTo(alldatatypes);
}
@Test(groups = "short")
public void should_store_and_retrieve_UDT_containing_collections_and_tuples() throws Exception {
// counters and durations are not allowed inside collections
DATA_TYPE_PRIMITIVES.remove(DataType.counter());
DATA_TYPE_PRIMITIVES.remove(DataType.duration());
// create UDT
List<String> alpha_type_list = new ArrayList<String>();
int startIndex = (int) 'a';
for (int i = 0; i < DATA_TYPE_NON_PRIMITIVE_NAMES.size(); i++)
for (int j = 0; j < DATA_TYPE_PRIMITIVES.size(); j++) {
String typeString;
if (DATA_TYPE_NON_PRIMITIVE_NAMES.get(i) == DataType.Name.MAP) {
typeString = (String.format("%s_%s %s<%s, %s>", Character.toString((char) (startIndex + i)),
Character.toString((char) (startIndex + j)), DATA_TYPE_NON_PRIMITIVE_NAMES.get(i),
DATA_TYPE_PRIMITIVES.get(j).getName(), DATA_TYPE_PRIMITIVES.get(j).getName()));
} else if (DATA_TYPE_NON_PRIMITIVE_NAMES.get(i) == DataType.Name.TUPLE) {
typeString = (String.format("%s_%s frozen<%s<%s>>", Character.toString((char) (startIndex + i)),
Character.toString((char) (startIndex + j)), DATA_TYPE_NON_PRIMITIVE_NAMES.get(i),
DATA_TYPE_PRIMITIVES.get(j).getName()));
} else {
typeString = (String.format("%s_%s %s<%s>", Character.toString((char) (startIndex + i)),
Character.toString((char) (startIndex + j)), DATA_TYPE_NON_PRIMITIVE_NAMES.get(i),
DATA_TYPE_PRIMITIVES.get(j).getName()));
}
alpha_type_list.add(typeString);
}
session().execute(String.format("CREATE TYPE allcollectiontypes (%s)", Joiner.on(',').join(alpha_type_list)));
session().execute("CREATE TABLE allcollectiontypes_table (a int PRIMARY KEY, b frozen<allcollectiontypes>)");
// insert UDT data
UserType allcollectiontypesDef = cluster().getMetadata().getKeyspace(keyspace).getUserType("allcollectiontypes");
UDTValue allcollectiontypes = allcollectiontypesDef.newValue();
for (int i = 0; i < DATA_TYPE_NON_PRIMITIVE_NAMES.size(); i++)
for (int j = 0; j < DATA_TYPE_PRIMITIVES.size(); j++) {
DataType.Name name = DATA_TYPE_NON_PRIMITIVE_NAMES.get(i);
DataType dataType = DATA_TYPE_PRIMITIVES.get(j);
String index = Character.toString((char) (startIndex + i)) + "_" + Character.toString((char) (startIndex + j));
Object sampleElement = samples.get(dataType);
switch (name) {
case LIST:
allcollectiontypes.setList(index, Lists.newArrayList(sampleElement));
break;
case SET:
allcollectiontypes.setSet(index, Sets.newHashSet(sampleElement));
break;
case MAP:
allcollectiontypes.setMap(index, ImmutableMap.of(sampleElement, sampleElement));
break;
case TUPLE:
allcollectiontypes.setTupleValue(index, cluster().getMetadata().newTupleType(dataType).newValue(sampleElement));
}
}
PreparedStatement ins = session().prepare("INSERT INTO allcollectiontypes_table (a, b) VALUES (?, ?)");
session().execute(ins.bind(0, allcollectiontypes));
// retrieve and verify data
ResultSet rs = session().execute("SELECT * FROM allcollectiontypes_table");
List<Row> rows = rs.all();
assertThat(rows.size()).isEqualTo(1);
Row row = rows.get(0);
assertThat(row.getInt("a")).isEqualTo(0);
assertThat(row.getUDTValue("b")).isEqualTo(allcollectiontypes);
}
@Test(groups = "short")
public void should_save_and_retrieve_nested_UDTs() throws Exception {
final int MAX_NESTING_DEPTH = 4;
// create UDT
session().execute("CREATE TYPE depth_0 (age int, name text)");
for (int i = 1; i <= MAX_NESTING_DEPTH; i++) {
session().execute(String.format("CREATE TYPE depth_%s (value frozen<depth_%s>)", String.valueOf(i), String.valueOf(i - 1)));
}
session().execute(String.format("CREATE TABLE nested_udt_table (a int PRIMARY KEY, b frozen<depth_0>, c frozen<depth_1>, d frozen<depth_2>, e frozen<depth_3>," +
"f frozen<depth_%s>)", MAX_NESTING_DEPTH));
// insert UDT data
KeyspaceMetadata keyspaceMetadata = cluster().getMetadata().getKeyspace(keyspace);
UserType depthZeroDef = keyspaceMetadata.getUserType("depth_0");
UDTValue depthZero = depthZeroDef.newValue().setInt("age", 42).setString("name", "Bob");
UserType depthOneDef = keyspaceMetadata.getUserType("depth_1");
UDTValue depthOne = depthOneDef.newValue().setUDTValue("value", depthZero);
UserType depthTwoDef = keyspaceMetadata.getUserType("depth_2");
UDTValue depthTwo = depthTwoDef.newValue().setUDTValue("value", depthOne);
UserType depthThreeDef = keyspaceMetadata.getUserType("depth_3");
UDTValue depthThree = depthThreeDef.newValue().setUDTValue("value", depthTwo);
UserType depthFourDef = keyspaceMetadata.getUserType("depth_4");
UDTValue depthFour = depthFourDef.newValue().setUDTValue("value", depthThree);
PreparedStatement ins = session().prepare("INSERT INTO nested_udt_table (a, b, c, d, e, f) VALUES (?, ?, ?, ?, ?, ?)");
session().execute(ins.bind(0, depthZero, depthOne, depthTwo, depthThree, depthFour));
// retrieve and verify data
ResultSet rs = session().execute("SELECT * FROM nested_udt_table");
List<Row> rows = rs.all();
assertThat(rows.size()).isEqualTo(1);
Row row = rows.get(0);
assertThat(row.getInt("a")).isEqualTo(0);
assertThat(row.getUDTValue("b")).isEqualTo(depthZero);
assertThat(row.getUDTValue("c")).isEqualTo(depthOne);
assertThat(row.getUDTValue("d")).isEqualTo(depthTwo);
assertThat(row.getUDTValue("e")).isEqualTo(depthThree);
assertThat(row.getUDTValue("f")).isEqualTo(depthFour);
}
@Test(groups = "short")
public void should_save_and_retrieve_UDTs_with_null_values() throws Exception {
// create UDT
session().execute("CREATE TYPE user_null_values (a text, b int, c uuid, d blob)");
session().execute("CREATE TABLE null_values_table (a int PRIMARY KEY, b frozen<user_null_values>)");
// insert UDT data
UserType userTypeDef = cluster().getMetadata().getKeyspace(keyspace).getUserType("user_null_values");
UDTValue userType = userTypeDef.newValue().setString("a", null).setInt("b", 0).setUUID("c", null).setBytes("d", null);
PreparedStatement ins = session().prepare("INSERT INTO null_values_table (a, b) VALUES (?, ?)");
session().execute(ins.bind(0, userType));
// retrieve and verify data
ResultSet rs = session().execute("SELECT * FROM null_values_table");
List<Row> rows = rs.all();
assertThat(rows.size()).isEqualTo(1);
Row row = rows.get(0);
assertThat(row.getInt("a")).isEqualTo(0);
assertThat(row.getUDTValue("b")).isEqualTo(userType);
// test empty strings
userType = userTypeDef.newValue().setString("a", "").setInt("b", 0).setUUID("c", null).setBytes("d", ByteBuffer.allocate(0));
session().execute(ins.bind(0, userType));
// retrieve and verify data
rs = session().execute("SELECT * FROM null_values_table");
rows = rs.all();
assertThat(rows.size()).isEqualTo(1);
row = rows.get(0);
assertThat(row.getInt("a")).isEqualTo(0);
assertThat(row.getUDTValue("b")).isEqualTo(userType);
}
@Test(groups = "short")
public void should_save_and_retrieve_UDTs_with_null_collections() throws Exception {
// create UDT
session().execute("CREATE TYPE user_null_collections (a List<text>, b Set<text>, c Map<text, text>, d frozen<Tuple<text>>)");
session().execute("CREATE TABLE null_collections_table (a int PRIMARY KEY, b frozen<user_null_collections>)");
// insert null UDT data
PreparedStatement ins = session().prepare("INSERT INTO null_collections_table (a, b) " +
"VALUES (0, { a: ?, b: ?, c: ?, d: ? })");
session().execute(ins.bind().setList(0, null).setSet(1, null).setMap(2, null).setTupleValue(3, null));
// retrieve and verify data
ResultSet rs = session().execute("SELECT * FROM null_collections_table");
List<Row> rows = rs.all();
assertThat(rows.size()).isEqualTo(1);
Row row = rows.get(0);
assertThat(row.getInt("a")).isEqualTo(0);
UserType userTypeDef = cluster().getMetadata().getKeyspace(keyspace).getUserType("user_null_collections");
UDTValue userType = userTypeDef.newValue().setList("a", null).setSet("b", null).setMap("c", null).setTupleValue("d", null);
assertThat(row.getUDTValue("b")).isEqualTo(userType);
// test missing UDT args
ins = session().prepare("INSERT INTO null_collections_table (a, b) " +
"VALUES (1, { a: ? })");
session().execute(ins.bind().setList(0, new ArrayList<Object>()));
// retrieve and verify data
rs = session().execute("SELECT * FROM null_collections_table");
rows = rs.all();
assertThat(rows.size()).isEqualTo(2);
row = rows.get(0);
assertThat(row.getInt("a")).isEqualTo(1);
userType = userTypeDef.newValue().setList(0, new ArrayList<Object>());
assertThat(row.getUDTValue("b")).isEqualTo(userType);
}
@Test(groups = "short")
public void should_indicate_user_type_is_frozen() {
session().execute("CREATE TABLE frozen_table(k int primary key, v frozen<type_for_frozen_test>)");
KeyspaceMetadata keyspaceMetadata = cluster().getMetadata().getKeyspace(this.keyspace);
assertThat(keyspaceMetadata.getUserType("type_for_frozen_test"))
.isNotFrozen();
DataType userType = keyspaceMetadata.getTable("frozen_table").getColumn("v").getType();
assertThat(userType).isFrozen();
assertThat(userType.toString()).isEqualTo("frozen<" + keyspace + ".type_for_frozen_test>");
// The frozen flag is not set for result set definitions (the protocol does not provide
// that information and it's not really useful in that situation). We always return false.
ResultSet rs = session().execute("SELECT v FROM frozen_table WHERE k = 1");
assertThat(rs.getColumnDefinitions().getType(0))
.isNotFrozen();
// Same thing for prepared statements
PreparedStatement pst = session().prepare("SELECT v FROM frozen_table WHERE k = ?");
assertThat(pst.getVariables().getType(0))
.isNotFrozen();
}
@Test(groups = "short")
@CassandraVersion(value = "3.6", description = "Non-frozen UDTs were introduced in C* 3.6")
public void should_indicate_user_type_is_not_frozen() {
session().execute("CREATE TABLE not_frozen_table(k int primary key, v type_for_frozen_test)");
KeyspaceMetadata keyspaceMetadata = cluster().getMetadata().getKeyspace(this.keyspace);
assertThat(keyspaceMetadata.getUserType("type_for_frozen_test"))
.isNotFrozen();
DataType userType = keyspaceMetadata.getTable("not_frozen_table").getColumn("v").getType();
assertThat(userType).isNotFrozen();
assertThat(userType.toString()).isEqualTo(keyspace + ".type_for_frozen_test");
ResultSet rs = session().execute("SELECT v FROM not_frozen_table WHERE k = 1");
assertThat(rs.getColumnDefinitions().getType(0))
.isNotFrozen();
PreparedStatement pst = session().prepare("SELECT v FROM not_frozen_table WHERE k = ?");
assertThat(pst.getVariables().getType(0))
.isNotFrozen();
}
}