/* * 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.statements; import java.nio.ByteBuffer; import java.util.*; import org.apache.cassandra.exceptions.*; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.*; import org.apache.cassandra.db.ColumnFamilyType; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.AlreadyExistsException; import org.apache.cassandra.io.compress.CompressionParameters; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.thrift.CqlResult; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; /** A <code>CREATE TABLE</code> parsed from a CQL query statement. */ public class CreateTableStatement extends SchemaAlteringStatement { public AbstractType<?> comparator; private AbstractType<?> defaultValidator; private AbstractType<?> keyValidator; private final List<ByteBuffer> keyAliases = new ArrayList<ByteBuffer>(); private final List<ByteBuffer> columnAliases = new ArrayList<ByteBuffer>(); private ByteBuffer valueAlias; private final Map<ColumnIdentifier, AbstractType> columns = new HashMap<ColumnIdentifier, AbstractType>(); private final CFPropDefs properties; private final boolean ifNotExists; public CreateTableStatement(CFName name, CFPropDefs properties, boolean ifNotExists) { super(name); this.properties = properties; this.ifNotExists = ifNotExists; try { if (!this.properties.hasProperty(CFPropDefs.KW_COMPRESSION) && CFMetaData.DEFAULT_COMPRESSOR != null) this.properties.addProperty(CFPropDefs.KW_COMPRESSION, new HashMap<String, String>() {{ put(CompressionParameters.SSTABLE_COMPRESSION, CFMetaData.DEFAULT_COMPRESSOR); }}); } catch (SyntaxException e) { throw new AssertionError(e); } } public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException { state.hasKeyspaceAccess(keyspace(), Permission.CREATE); } public void validate(ClientState state) { // validated in announceMigration() } // Column definitions private Map<ByteBuffer, ColumnDefinition> getColumns() { Map<ByteBuffer, ColumnDefinition> columnDefs = new HashMap<ByteBuffer, ColumnDefinition>(); Integer componentIndex = null; if (comparator instanceof CompositeType) { CompositeType ct = (CompositeType) comparator; componentIndex = ct.types.get(ct.types.size() - 1) instanceof ColumnToCollectionType ? ct.types.size() - 2 : ct.types.size() - 1; } for (Map.Entry<ColumnIdentifier, AbstractType> col : columns.entrySet()) { columnDefs.put(col.getKey().key, ColumnDefinition.regularDef(col.getKey().key, col.getValue(), componentIndex)); } return columnDefs; } public void announceMigration() throws RequestValidationException { try { MigrationManager.announceNewColumnFamily(getCFMetaData()); } catch (AlreadyExistsException e) { if (!ifNotExists) throw e; } } public ResultMessage.SchemaChange.Change changeType() { return ResultMessage.SchemaChange.Change.CREATED; } /** * Returns a CFMetaData instance based on the parameters parsed from this * <code>CREATE</code> statement, or defaults where applicable. * * @return a CFMetaData instance corresponding to the values parsed from this statement * @throws InvalidRequestException on failure to validate parsed parameters */ public CFMetaData getCFMetaData() throws RequestValidationException { CFMetaData newCFMD; newCFMD = new CFMetaData(keyspace(), columnFamily(), ColumnFamilyType.Standard, comparator, null); applyPropertiesTo(newCFMD); return newCFMD; } public void applyPropertiesTo(CFMetaData cfmd) throws RequestValidationException { cfmd.defaultValidator(defaultValidator) .keyValidator(keyValidator) .columnMetadata(getColumns()); 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 static class RawStatement extends CFStatement { private final Map<ColumnIdentifier, CQL3Type> definitions = new HashMap<ColumnIdentifier, CQL3Type>(); public final CFPropDefs properties = new CFPropDefs(); private final List<List<ColumnIdentifier>> keyAliases = new ArrayList<List<ColumnIdentifier>>(); private final List<ColumnIdentifier> columnAliases = new ArrayList<ColumnIdentifier>(); private final Map<ColumnIdentifier, Boolean> definedOrdering = new LinkedHashMap<ColumnIdentifier, Boolean>(); // Insertion ordering is important private boolean useCompactStorage; private final Multiset<ColumnIdentifier> definedNames = HashMultiset.create(1); private final boolean ifNotExists; public RawStatement(CFName name, boolean ifNotExists) { super(name); this.ifNotExists = ifNotExists; } /** * Transform this raw statement into a CreateTableStatement. */ public ParsedStatement.Prepared prepare() throws RequestValidationException { // Column family name if (!columnFamily().matches("\\w+")) throw new InvalidRequestException(String.format("\"%s\" is not a valid column family name (must be alphanumeric character only: [0-9A-Za-z]+)", columnFamily())); if (columnFamily().length() > Schema.NAME_LENGTH) throw new InvalidRequestException(String.format("Column family names shouldn't be more than %s characters long (got \"%s\")", Schema.NAME_LENGTH, columnFamily())); for (Multiset.Entry<ColumnIdentifier> entry : definedNames.entrySet()) if (entry.getCount() > 1) throw new InvalidRequestException(String.format("Multiple definition of identifier %s", entry.getElement())); properties.validate(); CreateTableStatement stmt = new CreateTableStatement(cfName, properties, ifNotExists); Map<ByteBuffer, CollectionType> definedCollections = null; for (Map.Entry<ColumnIdentifier, CQL3Type> entry : definitions.entrySet()) { ColumnIdentifier id = entry.getKey(); CQL3Type pt = entry.getValue(); if (pt.isCollection()) { if (definedCollections == null) definedCollections = new HashMap<ByteBuffer, CollectionType>(); definedCollections.put(id.key, (CollectionType)pt.getType()); } stmt.columns.put(id, pt.getType()); // we'll remove what is not a column below } if (keyAliases.isEmpty()) throw new InvalidRequestException("No PRIMARY KEY specifed (exactly one required)"); else if (keyAliases.size() > 1) throw new InvalidRequestException("Multiple PRIMARY KEYs specifed (exactly one required)"); List<ColumnIdentifier> kAliases = keyAliases.get(0); List<AbstractType<?>> keyTypes = new ArrayList<AbstractType<?>>(kAliases.size()); for (ColumnIdentifier alias : kAliases) { stmt.keyAliases.add(alias.key); AbstractType<?> t = getTypeAndRemove(stmt.columns, alias); if (t instanceof CounterColumnType) throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", alias)); keyTypes.add(t); } stmt.keyValidator = keyTypes.size() == 1 ? keyTypes.get(0) : CompositeType.getInstance(keyTypes); // Handle column aliases if (columnAliases.isEmpty()) { if (useCompactStorage) { // There should remain some column definition since it is a non-composite "static" CF if (stmt.columns.isEmpty()) throw new InvalidRequestException("No definition found that is not part of the PRIMARY KEY"); if (definedCollections != null) throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE"); stmt.comparator = CFDefinition.definitionType; } else { List<AbstractType<?>> types = new ArrayList<AbstractType<?>>(definedCollections == null ? 1 : 2); types.add(CFDefinition.definitionType); if (definedCollections != null) types.add(ColumnToCollectionType.getInstance(definedCollections)); stmt.comparator = CompositeType.getInstance(types); } } else { // If we use compact storage and have only one alias, it is a // standard "dynamic" CF, otherwise it's a composite if (useCompactStorage && columnAliases.size() == 1) { if (definedCollections != null) throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE"); stmt.columnAliases.add(columnAliases.get(0).key); stmt.comparator = getTypeAndRemove(stmt.columns, columnAliases.get(0)); if (stmt.comparator instanceof CounterColumnType) throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", stmt.columnAliases.get(0))); } else { List<AbstractType<?>> types = new ArrayList<AbstractType<?>>(columnAliases.size() + 1); for (ColumnIdentifier t : columnAliases) { stmt.columnAliases.add(t.key); AbstractType<?> type = getTypeAndRemove(stmt.columns, t); if (type instanceof CounterColumnType) throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", t.key)); types.add(type); } if (useCompactStorage) { if (definedCollections != null) throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE"); } else { // For sparse, we must add the last UTF8 component // and the collection type if there is one types.add(CFDefinition.definitionType); if (definedCollections != null) types.add(ColumnToCollectionType.getInstance(definedCollections)); } if (types.isEmpty()) throw new IllegalStateException("Nonsensical empty parameter list for CompositeType"); stmt.comparator = CompositeType.getInstance(types); } } if (useCompactStorage && !stmt.columnAliases.isEmpty()) { if (stmt.columns.isEmpty()) { // The only value we'll insert will be the empty one, so the default validator don't matter stmt.defaultValidator = BytesType.instance; // We need to distinguish between // * I'm upgrading from thrift so the valueAlias is null // * I've defined my table with only a PK (and the column value will be empty) // So, we use an empty valueAlias (rather than null) for the second case stmt.valueAlias = ByteBufferUtil.EMPTY_BYTE_BUFFER; } else { if (stmt.columns.size() > 1) throw new InvalidRequestException(String.format("COMPACT STORAGE with composite PRIMARY KEY allows no more than one column not part of the PRIMARY KEY (got: %s)", StringUtils.join(stmt.columns.keySet(), ", "))); Map.Entry<ColumnIdentifier, AbstractType> lastEntry = stmt.columns.entrySet().iterator().next(); stmt.defaultValidator = lastEntry.getValue(); stmt.valueAlias = lastEntry.getKey().key; stmt.columns.remove(lastEntry.getKey()); } } else { // For compact, we are in the "static" case, so we need at least one column defined. For non-compact however, having // just the PK is fine since we have CQL3 row marker. if (useCompactStorage && stmt.columns.isEmpty()) throw new InvalidRequestException("COMPACT STORAGE with non-composite PRIMARY KEY require one column not part of the PRIMARY KEY, none given"); // There is no way to insert/access a column that is not defined for non-compact storage, so // the actual validator don't matter much (except that we want to recognize counter CF as limitation apply to them). stmt.defaultValidator = !stmt.columns.isEmpty() && (stmt.columns.values().iterator().next() instanceof CounterColumnType) ? CounterColumnType.instance : BytesType.instance; } // If we give a clustering order, we must explicitly do so for all aliases and in the order of the PK if (!definedOrdering.isEmpty()) { if (definedOrdering.size() > columnAliases.size()) throw new InvalidRequestException("Only clustering key columns can be defined in CLUSTERING ORDER directive"); int i = 0; for (ColumnIdentifier id : definedOrdering.keySet()) { ColumnIdentifier c = columnAliases.get(i); if (!id.equals(c)) { if (definedOrdering.containsKey(c)) throw new InvalidRequestException(String.format("The order of columns in the CLUSTERING ORDER directive must be the one of the clustering key (%s must appear before %s)", c, id)); else throw new InvalidRequestException(String.format("Missing CLUSTERING ORDER for column %s", c)); } ++i; } } return new ParsedStatement.Prepared(stmt); } private AbstractType<?> getTypeAndRemove(Map<ColumnIdentifier, AbstractType> columns, ColumnIdentifier t) throws InvalidRequestException { AbstractType type = columns.get(t); if (type == null) throw new InvalidRequestException(String.format("Unknown definition %s referenced in PRIMARY KEY", t)); if (type instanceof CollectionType) throw new InvalidRequestException(String.format("Invalid collection type for PRIMARY KEY component %s", t)); columns.remove(t); Boolean isReversed = definedOrdering.get(t); return isReversed != null && isReversed ? ReversedType.getInstance(type) : type; } public void addDefinition(ColumnIdentifier def, CQL3Type type) { definedNames.add(def); definitions.put(def, type); } public void addKeyAliases(List<ColumnIdentifier> aliases) { keyAliases.add(aliases); } public void addColumnAlias(ColumnIdentifier alias) { columnAliases.add(alias); } public void setOrdering(ColumnIdentifier alias, boolean reversed) { definedOrdering.put(alias, reversed); } public void setCompactStorage() { useCompactStorage = true; } public void checkAccess(ClientState state) { throw new UnsupportedOperationException(); } public CqlResult execute(ClientState state, List<ByteBuffer> variables) { throw new UnsupportedOperationException(); } } }