@SuppressWarnings("unchecked")
  public <K, V> Map<K, V> getMap(int i, Class<K> keysClass, Class<V> valuesClass) {
    DataType type = metadata.getType(i);
    if (type.getName() != DataType.Name.MAP)
      throw new InvalidTypeException(
          String.format("Column %s is not of map type", metadata.getName(i)));

    Class<?> expectedKeysClass = type.getTypeArguments().get(0).getName().javaType;
    Class<?> expectedValuesClass = type.getTypeArguments().get(1).getName().javaType;
    if (!keysClass.isAssignableFrom(expectedKeysClass)
        || !valuesClass.isAssignableFrom(expectedValuesClass))
      throw new InvalidTypeException(
          String.format(
              "Column %s is a map of %s->%s (CQL type %s), cannot be retrieve as a map of %s->%s",
              metadata.getName(i),
              expectedKeysClass,
              expectedValuesClass,
              type,
              keysClass,
              valuesClass));

    ByteBuffer value = data.get(i);
    if (value == null) return Collections.<K, V>emptyMap();

    return Collections.unmodifiableMap(Codec.<Map<K, V>>getCodec(type).compose(value));
  }
  public void udtSerDeserTest(int version) throws Exception {
    ListType<?> lt = ListType.getInstance(Int32Type.instance, true);
    SetType<?> st = SetType.getInstance(UTF8Type.instance, true);
    MapType<?, ?> mt = MapType.getInstance(UTF8Type.instance, LongType.instance, true);

    UserType udt =
        new UserType(
            "ks",
            bb("myType"),
            Arrays.asList(bb("f1"), bb("f2"), bb("f3"), bb("f4")),
            Arrays.asList(LongType.instance, lt, st, mt));

    Map<ColumnIdentifier, Term.Raw> value = new HashMap<>();
    value.put(ci("f1"), lit(42));
    value.put(ci("f2"), new Lists.Literal(Arrays.<Term.Raw>asList(lit(3), lit(1))));
    value.put(ci("f3"), new Sets.Literal(Arrays.<Term.Raw>asList(lit("foo"), lit("bar"))));
    value.put(
        ci("f4"),
        new Maps.Literal(
            Arrays.<Pair<Term.Raw, Term.Raw>>asList(
                Pair.<Term.Raw, Term.Raw>create(lit("foo"), lit(24)),
                Pair.<Term.Raw, Term.Raw>create(lit("bar"), lit(12)))));

    UserTypes.Literal u = new UserTypes.Literal(value);
    Term t = u.prepare("ks", columnSpec("myValue", udt));

    QueryOptions options = QueryOptions.DEFAULT;
    if (version == 2)
      options =
          QueryOptions.fromProtocolV2(ConsistencyLevel.ONE, Collections.<ByteBuffer>emptyList());
    else if (version != 3) throw new AssertionError("Invalid protocol version for test");

    ByteBuffer serialized = t.bindAndGet(options);

    ByteBuffer[] fields = udt.split(serialized);

    assertEquals(4, fields.length);

    assertEquals(bytes(42L), fields[0]);

    // Note that no matter what the protocol version has been used in bindAndGet above, the
    // collections inside
    // a UDT should alway be serialized with version 3 of the protocol. Which is why we don't use
    // 'version'
    // on purpose below.

    assertEquals(
        Arrays.asList(3, 1), lt.getSerializer().deserializeForNativeProtocol(fields[1], 3));

    LinkedHashSet<String> s = new LinkedHashSet<>();
    s.addAll(Arrays.asList("bar", "foo"));
    assertEquals(s, st.getSerializer().deserializeForNativeProtocol(fields[2], 3));

    LinkedHashMap<String, Long> m = new LinkedHashMap<>();
    m.put("bar", 12L);
    m.put("foo", 24L);
    assertEquals(m, mt.getSerializer().deserializeForNativeProtocol(fields[3], 3));
  }
  @SuppressWarnings("unchecked")
  public <T> Set<T> getSet(int i, Class<T> elementsClass) {
    DataType type = metadata.getType(i);
    if (type.getName() != DataType.Name.SET)
      throw new InvalidTypeException(
          String.format("Column %s is not of set type", metadata.getName(i)));

    Class<?> expectedClass = type.getTypeArguments().get(0).getName().javaType;
    if (!elementsClass.isAssignableFrom(expectedClass))
      throw new InvalidTypeException(
          String.format(
              "Column %s is a set of %s (CQL type %s), cannot be retrieve as a set of %s",
              metadata.getName(i), expectedClass, type, elementsClass));

    ByteBuffer value = data.get(i);
    if (value == null) return Collections.<T>emptySet();

    return Collections.unmodifiableSet(Codec.<Set<T>>getCodec(type).compose(value));
  }
  @SuppressWarnings("unchecked")
  public <T> List<T> getList(int i, Class<T> elementsClass) {
    DataType type = metadata.getType(i);
    if (type.getName() != DataType.Name.LIST)
      throw new InvalidTypeException(
          String.format("Column %s is not of list type", metadata.getName(i)));

    Class<?> expectedClass = type.getTypeArguments().get(0).getName().javaType;
    if (!elementsClass.isAssignableFrom(expectedClass))
      throw new InvalidTypeException(
          String.format(
              "Column %s is a list of %s (CQL type %s), cannot be retrieve as a list of %s",
              metadata.getName(i), expectedClass, type, elementsClass));

    ByteBuffer value = data.get(i);
    if (value == null) return Collections.<T>emptyList();

    // TODO: we could avoid the getCodec call if we kept a reference to the original message.
    return Collections.unmodifiableList(Codec.<List<T>>getCodec(type).compose(value));
  }
  public void applyPropertiesTo(CFMetaData cfmd) throws RequestValidationException {
    cfmd.defaultValidator(defaultValidator)
        .keyValidator(keyValidator)
        .columnMetadata(getColumns())
        .setDense(isDense);

    cfmd.addColumnMetadataFromAliases(
        keyAliases, keyValidator, ColumnDefinition.Type.PARTITION_KEY);
    cfmd.addColumnMetadataFromAliases(
        columnAliases, comparator, ColumnDefinition.Type.CLUSTERING_KEY);
    if (valueAlias != null)
      cfmd.addColumnMetadataFromAliases(
          Collections.<ByteBuffer>singletonList(valueAlias),
          defaultValidator,
          ColumnDefinition.Type.COMPACT_VALUE);

    properties.applyToCFMetadata(cfmd);
  }
  public RowMutation mutationForKey(
      CFDefinition cfDef,
      ByteBuffer key,
      ColumnNameBuilder builder,
      boolean isRange,
      UpdateParameters params,
      ColumnGroupMap group)
      throws InvalidRequestException {
    QueryProcessor.validateKey(key);
    RowMutation rm = new RowMutation(cfDef.cfm.ksName, key);
    ColumnFamily cf = rm.addOrGet(columnFamily());

    if (columns.isEmpty() && builder.componentCount() == 0) {
      // No columns, delete the row
      cf.delete(new DeletionInfo(params.timestamp, params.localDeletionTime));
    } else {
      if (isRange) {
        ByteBuffer start = builder.copy().build();
        ByteBuffer end = builder.buildAsEndOfRange();
        QueryProcessor.validateColumnName(start); // If start is good, end is too
        cf.addAtom(params.makeRangeTombstone(start, end));
      } else {
        // Delete specific columns
        if (cfDef.isCompact) {
          ByteBuffer columnName = builder.build();
          QueryProcessor.validateColumnName(columnName);
          cf.addColumn(params.makeTombstone(columnName));
        } else {
          Iterator<Pair<CFDefinition.Name, Term>> iter = toRemove.iterator();
          while (iter.hasNext()) {
            Pair<CFDefinition.Name, Term> p = iter.next();
            CFDefinition.Name column = p.left;

            if (column.type.isCollection()) {
              CollectionType validator = (CollectionType) column.type;
              Term keySelected = p.right;

              if (keySelected == null) {
                // Delete the whole collection
                ByteBuffer start = builder.copy().add(column.name.key).build();
                QueryProcessor.validateColumnName(start);
                ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
                ByteBuffer end = b.add(column.name.key).buildAsEndOfRange();
                cf.addAtom(params.makeRangeTombstone(start, end));
              } else {
                builder.add(column.name.key);
                List<Term> args = Collections.singletonList(keySelected);

                Operation op;
                switch (validator.kind) {
                  case LIST:
                    op = ListOperation.DiscardKey(args);
                    break;
                  case SET:
                    op = SetOperation.Discard(args);
                    break;
                  case MAP:
                    op = MapOperation.DiscardKey(keySelected);
                    break;
                  default:
                    throw new InvalidRequestException("Unknown collection type: " + validator.kind);
                }

                op.execute(
                    cf,
                    builder,
                    validator,
                    params,
                    group == null ? null : group.getCollection(column.name.key));
              }
            } else {
              ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
              ByteBuffer columnName = b.add(column.name.key).build();
              QueryProcessor.validateColumnName(columnName);
              cf.addColumn(params.makeTombstone(columnName));
            }
          }
        }
      }
    }

    return rm;
  }