/*
* 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;
import java.nio.ByteBuffer;
import java.util.*;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.AbstractIterator;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.ColumnToCollectionType;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* Holds metadata on a CF preprocessed for use by CQL queries.
*/
public class CFDefinition implements Iterable<CFDefinition.Name>
{
public static final AbstractType<?> definitionType = UTF8Type.instance;
public final CFMetaData cfm;
// LinkedHashMap because the order does matter (it is the order in the composite type)
public final LinkedHashMap<ColumnIdentifier, Name> keys = new LinkedHashMap<ColumnIdentifier, Name>();
public final LinkedHashMap<ColumnIdentifier, Name> columns = new LinkedHashMap<ColumnIdentifier, Name>();
public final Name value;
// Keep metadata lexicographically ordered so that wildcard expansion have a deterministic order
public final Map<ColumnIdentifier, Name> metadata = new TreeMap<ColumnIdentifier, Name>();
public final boolean isComposite;
public final boolean hasCompositeKey;
// Note that isCompact means here that no componet of the comparator correspond to the column names
// defined in the CREATE TABLE QUERY. This is not exactly equivalent to the 'WITH COMPACT STORAGE'
// option when creating a table in that "static CF" without a composite type will have isCompact == false
// even though one must use 'WITH COMPACT STORAGE' to declare them.
public final boolean isCompact;
public final boolean hasCollections;
public CFDefinition(CFMetaData cfm)
{
this.cfm = cfm;
this.hasCompositeKey = cfm.getKeyValidator() instanceof CompositeType;
for (int i = 0; i < cfm.partitionKeyColumns().size(); ++i)
{
ColumnIdentifier id = new ColumnIdentifier(cfm.partitionKeyColumns().get(i).name, definitionType);
this.keys.put(id, new Name(cfm.ksName, cfm.cfName, id, Name.Kind.KEY_ALIAS, i, cfm.getKeyValidator().getComponents().get(i)));
}
this.isComposite = cfm.comparator instanceof CompositeType;
this.hasCollections = cfm.comparator.getComponents().get(cfm.comparator.componentsCount() - 1) instanceof ColumnToCollectionType;
this.isCompact = cfm.clusteringKeyColumns().size() == cfm.comparator.componentsCount();
for (int i = 0; i < cfm.clusteringKeyColumns().size(); ++i)
{
ColumnIdentifier id = new ColumnIdentifier(cfm.clusteringKeyColumns().get(i).name, definitionType);
this.columns.put(id, new Name(cfm.ksName, cfm.cfName, id, Name.Kind.COLUMN_ALIAS, i, cfm.comparator.getComponents().get(i)));
}
if (isCompact)
{
this.value = createValue(cfm);
}
else
{
this.value = null;
for (ColumnDefinition def : cfm.regularColumns())
{
ColumnIdentifier id = new ColumnIdentifier(def.name, cfm.getColumnDefinitionComparator(def));
this.metadata.put(id, new Name(cfm.ksName, cfm.cfName, id, Name.Kind.COLUMN_METADATA, def.getValidator()));
}
}
}
public ColumnToCollectionType getCollectionType()
{
if (!hasCollections)
return null;
CompositeType composite = (CompositeType)cfm.comparator;
return (ColumnToCollectionType)composite.types.get(composite.types.size() - 1);
}
private static Name createValue(CFMetaData cfm)
{
ColumnIdentifier alias = new ColumnIdentifier(cfm.compactValueColumn().name, definitionType);
// That's how we distinguish between 'no value alias because coming from thrift' and 'I explicitely did not
// define a value' (see CreateTableStatement)
return alias.key.equals(ByteBufferUtil.EMPTY_BYTE_BUFFER)
? null
: new Name(cfm.ksName, cfm.cfName, alias, Name.Kind.VALUE_ALIAS, cfm.getDefaultValidator());
}
public Name get(ColumnIdentifier name)
{
CFDefinition.Name kdef = keys.get(name);
if (kdef != null)
return kdef;
if (value != null && name.equals(value.name))
return value;
CFDefinition.Name def = columns.get(name);
if (def != null)
return def;
return metadata.get(name);
}
public Iterator<Name> iterator()
{
return new AbstractIterator<Name>()
{
private final Iterator<Name> keyIter = keys.values().iterator();
private final Iterator<Name> columnIter = columns.values().iterator();
private boolean valueDone;
private final Iterator<Name> metadataIter = metadata.values().iterator();
protected Name computeNext()
{
if (keyIter.hasNext())
return keyIter.next();
if (columnIter.hasNext())
return columnIter.next();
if (value != null && !valueDone)
{
valueDone = true;
return value;
}
if (metadataIter.hasNext())
return metadataIter.next();
return endOfData();
}
};
}
public ColumnNameBuilder getKeyNameBuilder()
{
return hasCompositeKey
? new CompositeType.Builder((CompositeType)cfm.getKeyValidator())
: new NonCompositeBuilder(cfm.getKeyValidator());
}
public ColumnNameBuilder getColumnNameBuilder()
{
return isComposite
? new CompositeType.Builder((CompositeType)cfm.comparator)
: new NonCompositeBuilder(cfm.comparator);
}
public static class Name extends ColumnSpecification
{
public static enum Kind
{
KEY_ALIAS, COLUMN_ALIAS, VALUE_ALIAS, COLUMN_METADATA
}
private Name(String ksName, String cfName, ColumnIdentifier name, Kind kind, AbstractType<?> type)
{
this(ksName, cfName, name, kind, -1, type);
}
private Name(String ksName, String cfName, ColumnIdentifier name, Kind kind, int position, AbstractType<?> type)
{
super(ksName, cfName, name, type);
this.kind = kind;
this.position = position;
}
public final Kind kind;
public final int position; // only make sense for KEY_ALIAS and COLUMN_ALIAS
@Override
public boolean equals(Object o)
{
if(!(o instanceof Name))
return false;
Name that = (Name)o;
return Objects.equal(ksName, that.ksName)
&& Objects.equal(cfName, that.cfName)
&& Objects.equal(name, that.name)
&& Objects.equal(type, that.type)
&& kind == that.kind
&& position == that.position;
}
@Override
public final int hashCode()
{
return Objects.hashCode(ksName, cfName, name, type, kind, position);
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(Joiner.on(", ").join(keys.values()));
if (!columns.isEmpty())
sb.append(", ").append(Joiner.on(", ").join(columns.values()));
sb.append(" => ");
if (value != null)
sb.append(value.name);
if (!metadata.isEmpty())
sb.append("{").append(Joiner.on(", ").join(metadata.values())).append(" }");
return sb.toString();
}
private static class NonCompositeBuilder implements ColumnNameBuilder
{
private final AbstractType<?> type;
private ByteBuffer columnName;
private NonCompositeBuilder(AbstractType<?> type)
{
this.type = type;
}
public NonCompositeBuilder add(ByteBuffer bb)
{
if (columnName != null)
throw new IllegalStateException("Column name is already constructed");
columnName = bb;
return this;
}
public NonCompositeBuilder add(ByteBuffer bb, Relation.Type op)
{
return add(bb);
}
public int componentCount()
{
return columnName == null ? 0 : 1;
}
public int remainingCount()
{
return columnName == null ? 1 : 0;
}
public ByteBuffer get(int i)
{
if (i < 0 || i >= (columnName == null ? 0 : 1))
throw new IllegalArgumentException();
return columnName;
}
public ByteBuffer build()
{
return columnName == null ? ByteBufferUtil.EMPTY_BYTE_BUFFER : columnName;
}
public ByteBuffer buildAsEndOfRange()
{
return build();
}
public NonCompositeBuilder copy()
{
NonCompositeBuilder newBuilder = new NonCompositeBuilder(type);
newBuilder.columnName = columnName;
return newBuilder;
}
public ByteBuffer getComponent(int i)
{
if (i != 0 || columnName == null)
throw new IllegalArgumentException();
return columnName;
}
}
}