UserType.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.cql3.functions.types;
import java.util.*;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import org.apache.cassandra.transport.ProtocolVersion;
/**
* A User Defined Type (UDT).
*
* <p>A UDT is a essentially a named collection of fields (with a name and a type).
*/
public class UserType extends DataType implements Iterable<UserType.Field>
{
private final String keyspace;
private final String typeName;
private final boolean frozen;
private final ProtocolVersion protocolVersion;
// can be null, if this object is being constructed from a response message
// see Responses.Result.Rows.Metadata.decode()
private final CodecRegistry codecRegistry;
// Note that we don't expose the order of fields, from an API perspective this is a map
// of String->Field, but internally we care about the order because the serialization format
// of UDT expects a particular order.
final Field[] byIdx;
// For a given name, we can only have one field with that name, so we don't need a int[] in
// practice. However, storing one element arrays save allocations in UDTValue.getAllIndexesOf
// implementation.
final Map<String, int[]> byName;
private UserType(
Name name,
String keyspace,
String typeName,
boolean frozen,
ProtocolVersion protocolVersion,
CodecRegistry codecRegistry,
Field[] byIdx,
Map<String, int[]> byName)
{
super(name);
this.keyspace = keyspace;
this.typeName = typeName;
this.frozen = frozen;
this.protocolVersion = protocolVersion;
this.codecRegistry = codecRegistry;
this.byIdx = byIdx;
this.byName = byName;
}
UserType(
String keyspace,
String typeName,
boolean frozen,
Collection<Field> fields,
ProtocolVersion protocolVersion,
CodecRegistry codecRegistry)
{
this(
DataType.Name.UDT,
keyspace,
typeName,
frozen,
protocolVersion,
codecRegistry,
fields.toArray(new Field[fields.size()]),
mapByName(fields));
}
private static ImmutableMap<String, int[]> mapByName(Collection<Field> fields)
{
ImmutableMap.Builder<String, int[]> builder = new ImmutableMap.Builder<>();
int i = 0;
for (Field field : fields)
{
builder.put(field.getName(), new int[]{ i });
i += 1;
}
return builder.build();
}
/**
* Returns a new empty value for this user type definition.
*
* @return an empty value for this user type definition.
*/
public UDTValue newValue()
{
return new UDTValue(this);
}
/**
* The name of the keyspace this UDT is part of.
*
* @return the name of the keyspace this UDT is part of.
*/
public String getKeyspace()
{
return keyspace;
}
/**
* The name of this user type.
*
* @return the name of this user type.
*/
public String getTypeName()
{
return typeName;
}
/**
* Returns the number of fields in this UDT.
*
* @return the number of fields in this UDT.
*/
public int size()
{
return byIdx.length;
}
/**
* Returns whether this UDT contains a given field.
*
* @param name the name to check. Note that {@code name} obey the usual CQL identifier rules: it
* should be quoted if it denotes a case sensitive identifier (you can use {@link
* Metadata#quote} for the quoting).
* @return {@code true} if this UDT contains a field named {@code name}, {@code false} otherwise.
*/
public boolean contains(String name)
{
return byName.containsKey(Metadata.handleId(name));
}
/**
* Returns an iterator over the fields of this UDT.
*
* @return an iterator over the fields of this UDT.
*/
@Override
public Iterator<Field> iterator()
{
return Iterators.forArray(byIdx);
}
/**
* Returns the type of a given field.
*
* @param name the name of the field. Note that {@code name} obey the usual CQL identifier rules:
* it should be quoted if it denotes a case sensitive identifier (you can use {@link
* Metadata#quote} for the quoting).
* @return the type of field {@code name} if this UDT has a field of this name, {@code null}
* otherwise.
* @throws IllegalArgumentException if {@code name} is not a field of this UDT definition.
*/
DataType getFieldType(String name)
{
int[] idx = byName.get(Metadata.handleId(name));
if (idx == null)
throw new IllegalArgumentException(name + " is not a field defined in this definition");
return byIdx[idx[0]].getType();
}
@Override
public boolean isFrozen()
{
return frozen;
}
public UserType copy(boolean newFrozen)
{
if (newFrozen == frozen)
{
return this;
}
else
{
return new UserType(
name, keyspace, typeName, newFrozen, protocolVersion, codecRegistry, byIdx, byName);
}
}
@Override
public int hashCode()
{
int result = name.hashCode();
result = 31 * result + keyspace.hashCode();
result = 31 * result + typeName.hashCode();
result = 31 * result + Arrays.hashCode(byIdx);
return result;
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof UserType)) return false;
UserType other = (UserType) o;
// Note: we don't test byName because it's redundant with byIdx in practice,
// but also because the map holds 'int[]' which don't have proper equal.
return name.equals(other.name)
&& keyspace.equals(other.keyspace)
&& typeName.equals(other.typeName)
&& Arrays.equals(byIdx, other.byIdx);
}
/**
* Return the protocol version that has been used to deserialize this UDT, or that will be used to
* serialize it. In most cases this should be the version currently in use by the cluster instance
* that this UDT belongs to, as reported by {@code ProtocolOptions#getProtocolVersion()}.
*
* @return the protocol version that has been used to deserialize this UDT, or that will be used
* to serialize it.
*/
ProtocolVersion getProtocolVersion()
{
return protocolVersion;
}
CodecRegistry getCodecRegistry()
{
return codecRegistry;
}
@Override
public String toString()
{
String str =
Metadata.quoteIfNecessary(getKeyspace()) + '.' + Metadata.quoteIfNecessary(getTypeName());
return isFrozen() ? "frozen<" + str + '>' : str;
}
@Override
public String asFunctionParameterString()
{
return Metadata.quoteIfNecessary(getTypeName());
}
/**
* A UDT field.
*/
public static class Field
{
private final String name;
private final DataType type;
Field(String name, DataType type)
{
this.name = name;
this.type = type;
}
/**
* Returns the name of the field.
*
* @return the name of the field.
*/
public String getName()
{
return name;
}
/**
* Returns the type of the field.
*
* @return the type of the field.
*/
public DataType getType()
{
return type;
}
@Override
public final int hashCode()
{
return Arrays.hashCode(new Object[]{ name, type });
}
@Override
public final boolean equals(Object o)
{
if (!(o instanceof Field)) return false;
Field other = (Field) o;
return name.equals(other.name) && type.equals(other.type);
}
@Override
public String toString()
{
return Metadata.quoteIfNecessary(name) + ' ' + type;
}
}
}