/* * 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; // Keep static and regular columns lexicographically ordered so that wildcard expansion have a deterministic order private static final Comparator<ColumnIdentifier> identifierComparator = new Comparator<ColumnIdentifier>() { public int compare(ColumnIdentifier id1, ColumnIdentifier id2) { return ByteBufferUtil.compareUnsigned(id1.key, id2.key); } }; public final CFMetaData cfm; // LinkedHashMap because the order does matter (it is the order in the composite type) private final LinkedHashMap<ColumnIdentifier, Name> partitionKeys = new LinkedHashMap<ColumnIdentifier, Name>(); private final LinkedHashMap<ColumnIdentifier, Name> clusteringColumns = new LinkedHashMap<ColumnIdentifier, Name>(); private final Name compactValue; // Keep metadata lexicographically ordered so that wildcard expansion have a deterministic order private final Map<ColumnIdentifier, Name> staticColumns = new TreeMap<ColumnIdentifier, Name>(identifierComparator); private final Map<ColumnIdentifier, Name> regularColumns = new TreeMap<ColumnIdentifier, Name>(identifierComparator); 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.partitionKeys.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.clusteringColumns.put(id, new Name(cfm.ksName, cfm.cfName, id, Name.Kind.COLUMN_ALIAS, i, cfm.comparator.getComponents().get(i))); } if (isCompact) { this.compactValue = createValue(cfm); } else { this.compactValue = null; for (ColumnDefinition def : cfm.regularColumns()) { ColumnIdentifier id = new ColumnIdentifier(def.name, cfm.getColumnDefinitionComparator(def)); this.regularColumns.put(id, new Name(cfm.ksName, cfm.cfName, id, Name.Kind.COLUMN_METADATA, def.getValidator())); } for (ColumnDefinition def : cfm.staticColumns()) { ColumnIdentifier id = new ColumnIdentifier(def.name, cfm.getColumnDefinitionComparator(def)); this.staticColumns.put(id, new Name(cfm.ksName, cfm.cfName, id, Name.Kind.STATIC, 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 int partitionKeyCount() { return partitionKeys.size(); } public Collection<Name> partitionKeys() { return partitionKeys.values(); } public int clusteringColumnsCount() { return clusteringColumns.size(); } public Collection<Name> clusteringColumns() { return clusteringColumns.values(); } public Collection<Name> regularColumns() { return regularColumns.values(); } public Collection<Name> staticColumns() { return staticColumns.values(); } public Name compactValue() { return compactValue; } public Name get(ColumnIdentifier name) { CFDefinition.Name def = partitionKeys.get(name); if (def != null) return def; if (compactValue != null && name.equals(compactValue.name)) return compactValue; def = clusteringColumns.get(name); if (def != null) return def; def = regularColumns.get(name); if (def != null) return def; return staticColumns.get(name); } public Iterator<Name> iterator() { return new AbstractIterator<Name>() { private final Iterator<Name> keyIter = partitionKeys.values().iterator(); private final Iterator<Name> clusteringIter = clusteringColumns.values().iterator(); private boolean valueDone; private final Iterator<Name> staticIter = staticColumns.values().iterator(); private final Iterator<Name> regularIter = regularColumns.values().iterator(); protected Name computeNext() { if (keyIter.hasNext()) return keyIter.next(); if (clusteringIter.hasNext()) return clusteringIter.next(); if (compactValue != null && !valueDone) { valueDone = true; return compactValue; } if (staticIter.hasNext()) return staticIter.next(); if (regularIter.hasNext()) return regularIter.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, STATIC } 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); } public boolean isPrimaryKeyColumn() { return kind == Kind.KEY_ALIAS || kind == Kind.COLUMN_ALIAS; } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(Joiner.on(", ").join(partitionKeys.values())); if (!clusteringColumns.isEmpty()) sb.append(", ").append(Joiner.on(", ").join(clusteringColumns.values())); sb.append(" => "); if (compactValue != null) sb.append(compactValue.name); sb.append("{"); sb.append(Joiner.on(", ").join(staticColumns.values())); if (!staticColumns.isEmpty()) sb.append(", "); sb.append(Joiner.on(", ").join(regularColumns.values())); sb.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 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 ByteBuffer buildForRelation(Relation.Type op) { 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; } public int getLength() { return columnName == null ? 0 : columnName.remaining(); } } }