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));
+     }
+ }

Reply via email to