Merge branch 'cassandra-2.2' into cassandra-3.0
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/db40ef83 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/db40ef83 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/db40ef83 Branch: refs/heads/trunk Commit: db40ef8398e34618086184487ffcc3310830cec7 Parents: 71bac92 85c7241 Author: Marcus Eriksson <marc...@apache.org> Authored: Tue Dec 15 10:25:33 2015 +0100 Committer: Marcus Eriksson <marc...@apache.org> Committed: Tue Dec 15 10:25:33 2015 +0100 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../cql3/statements/CreateTableStatement.java | 7 +- .../cql3/statements/CreateViewStatement.java | 1 + .../cql3/statements/TableAttributes.java | 19 ++++ .../operations/DropRecreateAndRestoreTest.java | 104 +++++++++++++++++++ 5 files changed, 130 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/db40ef83/CHANGES.txt ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/db40ef83/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java index 1363bee,1b3665c..c19f970 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java @@@ -37,35 -38,48 +37,37 @@@ import org.apache.cassandra.service.Cli import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.Event; -import org.apache.cassandra.utils.ByteBufferUtil; -/** A <code>CREATE TABLE</code> parsed from a CQL query statement. */ +/** A {@code CREATE TABLE} parsed from a CQL query statement. */ public class CreateTableStatement extends SchemaAlteringStatement { - public CellNameType comparator; - private AbstractType<?> defaultValidator; - private AbstractType<?> keyValidator; + private List<AbstractType<?>> keyTypes; + private List<AbstractType<?>> clusteringTypes; - private final List<ByteBuffer> keyAliases = new ArrayList<ByteBuffer>(); - private final List<ByteBuffer> columnAliases = new ArrayList<ByteBuffer>(); - private ByteBuffer valueAlias; + private final Map<ByteBuffer, CollectionType> collections = new HashMap<>(); + + private final List<ColumnIdentifier> keyAliases = new ArrayList<>(); + private final List<ColumnIdentifier> columnAliases = new ArrayList<>(); private boolean isDense; + private boolean isCompound; + private boolean hasCounters; // use a TreeMap to preserve ordering across JDK versions (see CASSANDRA-9492) - private final Map<ColumnIdentifier, AbstractType> columns = new TreeMap<>(new Comparator<ColumnIdentifier>() - { - public int compare(ColumnIdentifier o1, ColumnIdentifier o2) - { - return o1.bytes.compareTo(o2.bytes); - } - }); + private final Map<ColumnIdentifier, AbstractType> columns = new TreeMap<>((o1, o2) -> o1.bytes.compareTo(o2.bytes)); + private final Set<ColumnIdentifier> staticColumns; - private final CFPropDefs properties; + private final TableParams params; private final boolean ifNotExists; + private final UUID id; - public CreateTableStatement(CFName name, TableParams params, boolean ifNotExists, Set<ColumnIdentifier> staticColumns) - public CreateTableStatement(CFName name, CFPropDefs properties, boolean ifNotExists, Set<ColumnIdentifier> staticColumns, UUID id) ++ public CreateTableStatement(CFName name, TableParams params, boolean ifNotExists, Set<ColumnIdentifier> staticColumns, UUID id) { super(name); - this.properties = properties; + this.params = params; this.ifNotExists = ifNotExists; this.staticColumns = staticColumns; + this.id = id; - - 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); - }}); } public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException @@@ -109,49 -144,9 +111,50 @@@ } } + public CFMetaData.Builder metadataBuilder() + { + CFMetaData.Builder builder = CFMetaData.Builder.create(keyspace(), columnFamily(), isDense, isCompound, hasCounters); ++ builder.withId(id); + for (int i = 0; i < keyAliases.size(); i++) + builder.addPartitionKey(keyAliases.get(i), keyTypes.get(i)); + for (int i = 0; i < columnAliases.size(); i++) + builder.addClusteringColumn(columnAliases.get(i), clusteringTypes.get(i)); + + boolean isStaticCompact = !isDense && !isCompound; + for (Map.Entry<ColumnIdentifier, AbstractType> entry : columns.entrySet()) + { + ColumnIdentifier name = entry.getKey(); + // Note that for "static" no-clustering compact storage we use static for the defined columns + if (staticColumns.contains(name) || isStaticCompact) + builder.addStaticColumn(name, entry.getValue()); + else + builder.addRegularColumn(name, entry.getValue()); + } + + boolean isCompactTable = isDense || !isCompound; + if (isCompactTable) + { + CompactTables.DefaultNames names = CompactTables.defaultNameGenerator(builder.usedColumnNames()); + // Compact tables always have a clustering and a single regular value. + if (isStaticCompact) + { + builder.addClusteringColumn(names.defaultClusteringName(), UTF8Type.instance); + builder.addRegularColumn(names.defaultCompactValueName(), hasCounters ? CounterColumnType.instance : BytesType.instance); + } + else if (isDense && !builder.hasRegulars()) + { + // Even for dense, we might not have our regular column if it wasn't part of the declaration. If + // that's the case, add it but with a specific EmptyType so we can recognize that case later + builder.addRegularColumn(names.defaultCompactValueName(), EmptyType.instance); + } + } + + return builder; + } + /** * Returns a CFMetaData instance based on the parameters parsed from this - * <code>CREATE</code> statement, or defaults where applicable. + * {@code CREATE} 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 @@@ -210,10 -234,10 +213,10 @@@ properties.validate(); - CreateTableStatement stmt = new CreateTableStatement(cfName, properties, ifNotExists, staticColumns, properties.getId()); + TableParams params = properties.properties.asNewTableParams(); + - CreateTableStatement stmt = new CreateTableStatement(cfName, params, ifNotExists, staticColumns); ++ CreateTableStatement stmt = new CreateTableStatement(cfName, params, ifNotExists, staticColumns, properties.properties.getId()); - boolean hasCounters = false; - Map<ByteBuffer, CollectionType> definedMultiCellCollections = null; for (Map.Entry<ColumnIdentifier, CQL3Type.Raw> entry : definitions.entrySet()) { ColumnIdentifier id = entry.getKey(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/db40ef83/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java index 5d1fd45,0000000..4017ce6 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java @@@ -1,329 -1,0 +1,330 @@@ +/* + * 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.util.*; +import java.util.stream.Collectors; + +import com.google.common.collect.Iterables; + +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.config.ViewDefinition; +import org.apache.cassandra.cql3.*; +import org.apache.cassandra.cql3.restrictions.StatementRestrictions; +import org.apache.cassandra.cql3.selection.RawSelector; +import org.apache.cassandra.cql3.selection.Selectable; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.ReversedType; +import org.apache.cassandra.db.view.View; +import org.apache.cassandra.exceptions.AlreadyExistsException; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.exceptions.RequestValidationException; +import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.schema.TableParams; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.service.MigrationManager; +import org.apache.cassandra.thrift.ThriftValidation; +import org.apache.cassandra.transport.Event; + +public class CreateViewStatement extends SchemaAlteringStatement +{ + private final CFName baseName; + private final List<RawSelector> selectClause; + private final WhereClause whereClause; + private final List<ColumnIdentifier.Raw> partitionKeys; + private final List<ColumnIdentifier.Raw> clusteringKeys; + public final CFProperties properties = new CFProperties(); + private final boolean ifNotExists; + + public CreateViewStatement(CFName viewName, + CFName baseName, + List<RawSelector> selectClause, + WhereClause whereClause, + List<ColumnIdentifier.Raw> partitionKeys, + List<ColumnIdentifier.Raw> clusteringKeys, + boolean ifNotExists) + { + super(viewName); + this.baseName = baseName; + this.selectClause = selectClause; + this.whereClause = whereClause; + this.partitionKeys = partitionKeys; + this.clusteringKeys = clusteringKeys; + this.ifNotExists = ifNotExists; + } + + + public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException + { + if (!baseName.hasKeyspace()) + baseName.setKeyspace(keyspace(), true); + state.hasColumnFamilyAccess(keyspace(), baseName.getColumnFamily(), Permission.ALTER); + } + + public void validate(ClientState state) throws RequestValidationException + { + // We do validation in announceMigration to reduce doubling up of work + } + + private interface AddColumn { + void add(ColumnIdentifier identifier, AbstractType<?> type); + } + + private void add(CFMetaData baseCfm, Iterable<ColumnIdentifier> columns, AddColumn adder) + { + for (ColumnIdentifier column : columns) + { + AbstractType<?> type = baseCfm.getColumnDefinition(column).type; + if (properties.definedOrdering.containsKey(column)) + { + boolean desc = properties.definedOrdering.get(column); + if (!desc && type.isReversed()) + { + type = ((ReversedType)type).baseType; + } + else if (desc && !type.isReversed()) + { + type = ReversedType.getInstance(type); + } + } + adder.add(column, type); + } + } + + public Event.SchemaChange announceMigration(boolean isLocalOnly) throws RequestValidationException + { + // We need to make sure that: + // - primary key includes all columns in base table's primary key + // - make sure that the select statement does not have anything other than columns + // and their names match the base table's names + // - make sure that primary key does not include any collections + // - make sure there is no where clause in the select statement + // - make sure there is not currently a table or view + // - make sure baseTable gcGraceSeconds > 0 + + properties.validate(); + + if (properties.useCompactStorage) + throw new InvalidRequestException("Cannot use 'COMPACT STORAGE' when defining a materialized view"); + + // We enforce the keyspace because if the RF is different, the logic to wait for a + // specific replica would break + if (!baseName.getKeyspace().equals(keyspace())) + throw new InvalidRequestException("Cannot create a materialized view on a table in a separate keyspace"); + + CFMetaData cfm = ThriftValidation.validateColumnFamily(baseName.getKeyspace(), baseName.getColumnFamily()); + + if (cfm.isCounter()) + throw new InvalidRequestException("Materialized views are not supported on counter tables"); + if (cfm.isView()) + throw new InvalidRequestException("Materialized views cannot be created against other materialized views"); + + if (cfm.params.gcGraceSeconds == 0) + { + throw new InvalidRequestException(String.format("Cannot create materialized view '%s' for base table " + + "'%s' with gc_grace_seconds of 0, since this value is " + + "used to TTL undelivered updates. Setting gc_grace_seconds" + + " too low might cause undelivered updates to expire " + + "before being replayed.", cfName.getColumnFamily(), + baseName.getColumnFamily())); + } + + Set<ColumnIdentifier> included = new HashSet<>(); + for (RawSelector selector : selectClause) + { + Selectable.Raw selectable = selector.selectable; + if (selectable instanceof Selectable.WithFieldSelection.Raw) + throw new InvalidRequestException("Cannot select out a part of type when defining a materialized view"); + if (selectable instanceof Selectable.WithFunction.Raw) + throw new InvalidRequestException("Cannot use function when defining a materialized view"); + if (selectable instanceof Selectable.WritetimeOrTTL.Raw) + throw new InvalidRequestException("Cannot use function when defining a materialized view"); + ColumnIdentifier identifier = (ColumnIdentifier) selectable.prepare(cfm); + if (selector.alias != null) + throw new InvalidRequestException(String.format("Cannot alias column '%s' as '%s' when defining a materialized view", identifier.toString(), selector.alias.toString())); + + ColumnDefinition cdef = cfm.getColumnDefinition(identifier); + + if (cdef == null) + throw new InvalidRequestException("Unknown column name detected in CREATE MATERIALIZED VIEW statement : "+identifier); + + if (cdef.isStatic()) + ClientWarn.warn(String.format("Unable to include static column '%s' in Materialized View SELECT statement", identifier)); + else + included.add(identifier); + } + + Set<ColumnIdentifier.Raw> targetPrimaryKeys = new HashSet<>(); + for (ColumnIdentifier.Raw identifier : Iterables.concat(partitionKeys, clusteringKeys)) + { + if (!targetPrimaryKeys.add(identifier)) + throw new InvalidRequestException("Duplicate entry found in PRIMARY KEY: "+identifier); + + ColumnDefinition cdef = cfm.getColumnDefinition(identifier.prepare(cfm)); + + if (cdef == null) + throw new InvalidRequestException("Unknown column name detected in CREATE MATERIALIZED VIEW statement : "+identifier); + + if (cfm.getColumnDefinition(identifier.prepare(cfm)).type.isMultiCell()) + throw new InvalidRequestException(String.format("Cannot use MultiCell column '%s' in PRIMARY KEY of materialized view", identifier)); + + if (cdef.isStatic()) + throw new InvalidRequestException(String.format("Cannot use Static column '%s' in PRIMARY KEY of materialized view", identifier)); + } + + // build the select statement + Map<ColumnIdentifier.Raw, Boolean> orderings = Collections.emptyMap(); + SelectStatement.Parameters parameters = new SelectStatement.Parameters(orderings, false, true, false); + SelectStatement.RawStatement rawSelect = new SelectStatement.RawStatement(baseName, parameters, selectClause, whereClause, null); + + ClientState state = ClientState.forInternalCalls(); + state.setKeyspace(keyspace()); + + rawSelect.prepareKeyspace(state); + rawSelect.setBoundVariables(getBoundVariables()); + + ParsedStatement.Prepared prepared = rawSelect.prepare(true); + SelectStatement select = (SelectStatement) prepared.statement; + StatementRestrictions restrictions = select.getRestrictions(); + + if (!prepared.boundNames.isEmpty()) + throw new InvalidRequestException("Cannot use query parameters in CREATE MATERIALIZED VIEW statements"); + + if (!restrictions.nonPKRestrictedColumns(false).isEmpty()) + { + throw new InvalidRequestException(String.format( + "Non-primary key columns cannot be restricted in the SELECT statement used for materialized view " + + "creation (got restrictions on: %s)", + restrictions.nonPKRestrictedColumns(false).stream().map(def -> def.name.toString()).collect(Collectors.joining(", ")))); + } + + String whereClauseText = View.relationsToWhereClause(whereClause.relations); + + Set<ColumnIdentifier> basePrimaryKeyCols = new HashSet<>(); + for (ColumnDefinition definition : Iterables.concat(cfm.partitionKeyColumns(), cfm.clusteringColumns())) + basePrimaryKeyCols.add(definition.name); + + List<ColumnIdentifier> targetClusteringColumns = new ArrayList<>(); + List<ColumnIdentifier> targetPartitionKeys = new ArrayList<>(); + + // This is only used as an intermediate state; this is to catch whether multiple non-PK columns are used + boolean hasNonPKColumn = false; + for (ColumnIdentifier.Raw raw : partitionKeys) + hasNonPKColumn = getColumnIdentifier(cfm, basePrimaryKeyCols, hasNonPKColumn, raw, targetPartitionKeys, restrictions); + + for (ColumnIdentifier.Raw raw : clusteringKeys) + hasNonPKColumn = getColumnIdentifier(cfm, basePrimaryKeyCols, hasNonPKColumn, raw, targetClusteringColumns, restrictions); + + // We need to include all of the primary key columns from the base table in order to make sure that we do not + // overwrite values in the view. We cannot support "collapsing" the base table into a smaller number of rows in + // the view because if we need to generate a tombstone, we have no way of knowing which value is currently being + // used in the view and whether or not to generate a tombstone. In order to not surprise our users, we require + // that they include all of the columns. We provide them with a list of all of the columns left to include. + boolean missingClusteringColumns = false; + StringBuilder columnNames = new StringBuilder(); + List<ColumnIdentifier> includedColumns = new ArrayList<>(); + for (ColumnDefinition def : cfm.allColumns()) + { + ColumnIdentifier identifier = def.name; + + if ((included.isEmpty() || included.contains(identifier)) + && !targetClusteringColumns.contains(identifier) && !targetPartitionKeys.contains(identifier) + && !def.isStatic()) + { + includedColumns.add(identifier); + } + if (!def.isPrimaryKeyColumn()) continue; + + if (!targetClusteringColumns.contains(identifier) && !targetPartitionKeys.contains(identifier)) + { + if (missingClusteringColumns) + columnNames.append(','); + else + missingClusteringColumns = true; + columnNames.append(identifier); + } + } + if (missingClusteringColumns) + throw new InvalidRequestException(String.format("Cannot create Materialized View %s without primary key columns from base %s (%s)", + columnFamily(), baseName.getColumnFamily(), columnNames.toString())); + + if (targetPartitionKeys.isEmpty()) + throw new InvalidRequestException("Must select at least a column for a Materialized View"); + + if (targetClusteringColumns.isEmpty()) + throw new InvalidRequestException("No columns are defined for Materialized View other than primary key"); + + CFMetaData.Builder cfmBuilder = CFMetaData.Builder.createView(keyspace(), columnFamily()); + add(cfm, targetPartitionKeys, cfmBuilder::addPartitionKey); + add(cfm, targetClusteringColumns, cfmBuilder::addClusteringColumn); + add(cfm, includedColumns, cfmBuilder::addRegularColumn); ++ cfmBuilder.withId(properties.properties.getId()); + TableParams params = properties.properties.asNewTableParams(); + CFMetaData viewCfm = cfmBuilder.build().params(params); + ViewDefinition definition = new ViewDefinition(keyspace(), + columnFamily(), + Schema.instance.getId(keyspace(), baseName.getColumnFamily()), + baseName.getColumnFamily(), + included.isEmpty(), + rawSelect, + whereClauseText, + viewCfm); + + try + { + MigrationManager.announceNewView(definition, isLocalOnly); + return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily()); + } + catch (AlreadyExistsException e) + { + if (ifNotExists) + return null; + throw e; + } + } + + private static boolean getColumnIdentifier(CFMetaData cfm, + Set<ColumnIdentifier> basePK, + boolean hasNonPKColumn, + ColumnIdentifier.Raw raw, + List<ColumnIdentifier> columns, + StatementRestrictions restrictions) + { + ColumnIdentifier identifier = raw.prepare(cfm); + ColumnDefinition def = cfm.getColumnDefinition(identifier); + + boolean isPk = basePK.contains(identifier); + if (!isPk && hasNonPKColumn) + throw new InvalidRequestException(String.format("Cannot include more than one non-primary key column '%s' in materialized view partition key", identifier)); + + // We don't need to include the "IS NOT NULL" filter on a non-composite partition key + // because we will never allow a single partition key to be NULL + boolean isSinglePartitionKey = cfm.getColumnDefinition(identifier).isPartitionKey() + && cfm.partitionKeyColumns().size() == 1; + if (!isSinglePartitionKey && !restrictions.isRestricted(def)) + throw new InvalidRequestException(String.format("Primary key column '%s' is required to be filtered by 'IS NOT NULL'", identifier)); + + columns.add(identifier); + return !isPk; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/db40ef83/src/java/org/apache/cassandra/cql3/statements/TableAttributes.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/statements/TableAttributes.java index 9e7bbfe,0000000..c1a9d54 mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/statements/TableAttributes.java +++ b/src/java/org/apache/cassandra/cql3/statements/TableAttributes.java @@@ -1,179 -1,0 +1,198 @@@ +/* + * 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.util.Map; +import java.util.Set; ++import java.util.UUID; + +import com.google.common.collect.ImmutableSet; + ++import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.exceptions.SyntaxException; +import org.apache.cassandra.schema.*; +import org.apache.cassandra.schema.TableParams.Option; + +import static java.lang.String.format; + +public final class TableAttributes extends PropertyDefinitions +{ ++ private static final String KW_ID = "id"; + private static final Set<String> validKeywords; + private static final Set<String> obsoleteKeywords; + + static + { + ImmutableSet.Builder<String> validBuilder = ImmutableSet.builder(); + for (Option option : Option.values()) + validBuilder.add(option.toString()); ++ validBuilder.add(KW_ID); + validKeywords = validBuilder.build(); + obsoleteKeywords = ImmutableSet.of(); + } + + public void validate() + { + validate(validKeywords, obsoleteKeywords); + build(TableParams.builder()).validate(); + } + + public TableParams asNewTableParams() + { + return build(TableParams.builder()); + } + + public TableParams asAlteredTableParams(TableParams previous) + { ++ if (getId() != null) ++ throw new ConfigurationException("Cannot alter table id."); + return build(TableParams.builder(previous)); + } + ++ public UUID getId() throws ConfigurationException ++ { ++ String id = getSimple(KW_ID); ++ try ++ { ++ return id != null ? UUID.fromString(id) : null; ++ } ++ catch (IllegalArgumentException e) ++ { ++ throw new ConfigurationException("Invalid table id", e); ++ } ++ } ++ + private TableParams build(TableParams.Builder builder) + { + if (hasOption(Option.BLOOM_FILTER_FP_CHANCE)) + builder.bloomFilterFpChance(getDouble(Option.BLOOM_FILTER_FP_CHANCE)); + + if (hasOption(Option.CACHING)) + builder.caching(CachingParams.fromMap(getMap(Option.CACHING))); + + if (hasOption(Option.COMMENT)) + builder.comment(getString(Option.COMMENT)); + + if (hasOption(Option.COMPACTION)) + builder.compaction(CompactionParams.fromMap(getMap(Option.COMPACTION))); + + if (hasOption(Option.COMPRESSION)) + { + //crc_check_chance was "promoted" from a compression property to a top-level-property after #9839 + //so we temporarily accept it to be defined as a compression option, to maintain backwards compatibility + Map<String, String> compressionOpts = getMap(Option.COMPRESSION); + if (compressionOpts.containsKey(Option.CRC_CHECK_CHANCE.toString().toLowerCase())) + { + Double crcCheckChance = getDeprecatedCrcCheckChance(compressionOpts); + builder.crcCheckChance(crcCheckChance); + } + builder.compression(CompressionParams.fromMap(getMap(Option.COMPRESSION))); + } + + if (hasOption(Option.DCLOCAL_READ_REPAIR_CHANCE)) + builder.dcLocalReadRepairChance(getDouble(Option.DCLOCAL_READ_REPAIR_CHANCE)); + + if (hasOption(Option.DEFAULT_TIME_TO_LIVE)) + builder.defaultTimeToLive(getInt(Option.DEFAULT_TIME_TO_LIVE)); + + if (hasOption(Option.GC_GRACE_SECONDS)) + builder.gcGraceSeconds(getInt(Option.GC_GRACE_SECONDS)); + + if (hasOption(Option.MAX_INDEX_INTERVAL)) + builder.maxIndexInterval(getInt(Option.MAX_INDEX_INTERVAL)); + + if (hasOption(Option.MEMTABLE_FLUSH_PERIOD_IN_MS)) + builder.memtableFlushPeriodInMs(getInt(Option.MEMTABLE_FLUSH_PERIOD_IN_MS)); + + if (hasOption(Option.MIN_INDEX_INTERVAL)) + builder.minIndexInterval(getInt(Option.MIN_INDEX_INTERVAL)); + + if (hasOption(Option.READ_REPAIR_CHANCE)) + builder.readRepairChance(getDouble(Option.READ_REPAIR_CHANCE)); + + if (hasOption(Option.SPECULATIVE_RETRY)) + builder.speculativeRetry(SpeculativeRetryParam.fromString(getString(Option.SPECULATIVE_RETRY))); + + if (hasOption(Option.CRC_CHECK_CHANCE)) + builder.crcCheckChance(getDouble(Option.CRC_CHECK_CHANCE)); + + return builder.build(); + } + + private Double getDeprecatedCrcCheckChance(Map<String, String> compressionOpts) + { + String value = compressionOpts.get(Option.CRC_CHECK_CHANCE.toString().toLowerCase()); + try + { + return Double.parseDouble(value); + } + catch (NumberFormatException e) + { + throw new SyntaxException(String.format("Invalid double value %s for crc_check_chance.'", value)); + } + } + + private double getDouble(Option option) + { + String value = getString(option); + + try + { + return Double.parseDouble(value); + } + catch (NumberFormatException e) + { + throw new SyntaxException(format("Invalid double value %s for '%s'", value, option)); + } + } + + private int getInt(Option option) + { + String value = getString(option); + + try + { + return Integer.parseInt(value); + } + catch (NumberFormatException e) + { + throw new SyntaxException(String.format("Invalid integer value %s for '%s'", value, option)); + } + } + + private String getString(Option option) + { + String value = getSimple(option.toString()); + if (value == null) + throw new IllegalStateException(format("Option '%s' is absent", option)); + return value; + } + + private Map<String, String> getMap(Option option) + { + Map<String, String> value = getMap(option.toString()); + if (value == null) + throw new IllegalStateException(format("Option '%s' is absent", option)); + return value; + } + + private boolean hasOption(Option option) + { + return hasProperty(option.toString()); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/db40ef83/test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java index 0000000,4a3a51d..f491d24 mode 000000,100644..100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java @@@ -1,0 -1,105 +1,104 @@@ + /* + * 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.validation.operations; + + import java.io.File; + import java.util.List; + import java.util.UUID; + + import org.junit.Test; + + import org.apache.cassandra.config.DatabaseDescriptor; + import org.apache.cassandra.cql3.CQLTester; + import org.apache.cassandra.db.commitlog.CommitLog; + import org.apache.cassandra.exceptions.AlreadyExistsException; + import org.apache.cassandra.exceptions.ConfigurationException; + import org.apache.cassandra.exceptions.InvalidRequestException; + import org.apache.cassandra.io.util.FileUtils; + + public class DropRecreateAndRestoreTest extends CQLTester + { + @Test + public void testCreateWithIdRestore() throws Throwable + { + createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY(a, b))"); + + execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, 0); + execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1); + + + long time = System.currentTimeMillis(); + UUID id = currentTableMetadata().cfId; + assertRows(execute("SELECT * FROM %s"), row(0, 0, 0), row(0, 1, 1)); + Thread.sleep(5); + + execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 1, 0, 2); + execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 1, 1, 3); + assertRows(execute("SELECT * FROM %s"), row(1, 0, 2), row(1, 1, 3), row(0, 0, 0), row(0, 1, 1)); + + // Drop will flush and clean segments. Hard-link them so that they can be restored later. + List<String> segments = CommitLog.instance.getActiveSegmentNames(); + File logPath = new File(DatabaseDescriptor.getCommitLogLocation()); + for (String segment: segments) + FileUtils.createHardLink(new File(logPath, segment), new File(logPath, segment + ".save")); + + execute("DROP TABLE %s"); + + assertInvalidThrow(InvalidRequestException.class, "SELECT * FROM %s"); + + execute(String.format("CREATE TABLE %%s (a int, b int, c int, PRIMARY KEY(a, b)) WITH ID = %s", id)); + + // Restore saved segments + for (String segment: segments) + FileUtils.renameWithConfirm(new File(logPath, segment + ".save"), new File(logPath, segment)); + try + { + // Restore to point in time. + CommitLog.instance.archiver.restorePointInTime = time; - CommitLog.instance.resetUnsafe(true); - CommitLog.instance.recover(); ++ CommitLog.instance.resetUnsafe(false); + } + finally + { + CommitLog.instance.archiver.restorePointInTime = Long.MAX_VALUE; + } + + assertRows(execute("SELECT * FROM %s"), row(0, 0, 0), row(0, 1, 1)); + } + + @Test(expected = AlreadyExistsException.class) + public void testCreateWithIdDuplicate() throws Throwable + { + createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY(a, b))"); + UUID id = currentTableMetadata().cfId; + execute(String.format("CREATE TABLE %%s (a int, b int, c int, PRIMARY KEY(a, b)) WITH ID = %s", id)); + } + + @Test(expected = ConfigurationException.class) + public void testCreateWithIdInvalid() throws Throwable + { + createTableMayThrow(String.format("CREATE TABLE %%s (a int, b int, c int, PRIMARY KEY(a, b)) WITH ID = %s", 55)); + } + + @Test(expected = ConfigurationException.class) + public void testAlterWithId() throws Throwable + { + createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY(a, b))"); + UUID id = currentTableMetadata().cfId; + execute(String.format("ALTER TABLE %%s WITH ID = %s", id)); + } + }