You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ma...@apache.org on 2015/12/15 10:29:53 UTC

[1/3] cassandra git commit: Allow CREATE TABLE WITH ID

Repository: cassandra
Updated Branches:
  refs/heads/cassandra-3.0 71bac92cf -> db40ef839


Allow CREATE TABLE WITH ID

Patch by Branimir Lambov; reviewed by marcuse for CASSANDRA-9179


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/cee35e42
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/cee35e42
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/cee35e42

Branch: refs/heads/cassandra-3.0
Commit: cee35e42d05f125bf274b9cefb7653c21ca4b676
Parents: 9b30d65
Author: Branimir Lambov <br...@datastax.com>
Authored: Tue Dec 8 11:59:09 2015 +0200
Committer: Marcus Eriksson <ma...@apache.org>
Committed: Tue Dec 15 09:58:42 2015 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../org/apache/cassandra/config/CFMetaData.java |   6 +-
 .../cql3/statements/AlterTableStatement.java    |   2 +
 .../cassandra/cql3/statements/CFPropDefs.java   |  23 +++-
 .../cql3/statements/CreateTableStatement.java   |   9 +-
 .../operations/DropRecreateAndRestoreTest.java  | 105 +++++++++++++++++++
 6 files changed, 136 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/cee35e42/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 091ac52..7f1d66b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.1.13
+ * Allow CREATE TABLE WITH ID (CASSANDRA-9179)
  * Make Stress compiles within eclipse (CASSANDRA-10807)
  * Cassandra Daemon should print JVM arguments (CASSANDRA-10764)
  * Allow cancellation of index summary redistribution (CASSANDRA-8805)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cee35e42/src/java/org/apache/cassandra/config/CFMetaData.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/CFMetaData.java b/src/java/org/apache/cassandra/config/CFMetaData.java
index 2939f09..bce5c72 100644
--- a/src/java/org/apache/cassandra/config/CFMetaData.java
+++ b/src/java/org/apache/cassandra/config/CFMetaData.java
@@ -470,12 +470,12 @@ public final class CFMetaData
      */
     public CFMetaData(String keyspace, String name, ColumnFamilyType type, CellNameType comp)
     {
-        this(keyspace, name, type, comp, UUIDGen.getTimeUUID());
+        this(keyspace, name, type, comp, null);
     }
 
-    private CFMetaData(String keyspace, String name, ColumnFamilyType type, CellNameType comp, UUID id)
+    public CFMetaData(String keyspace, String name, ColumnFamilyType type, CellNameType comp, UUID id)
     {
-        cfId = id;
+        cfId = id != null ? id : UUIDGen.getTimeUUID();
         ksName = keyspace;
         cfName = name;
         ksAndCFName = Pair.create(keyspace, name);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cee35e42/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
index 44bc2bc..42b7f7f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
@@ -87,6 +87,8 @@ public class AlterTableStatement extends SchemaAlteringStatement
             columnName = rawColumnName.prepare(cfm);
             def = cfm.getColumnDefinition(columnName);
         }
+        if (cfProps.getId() != null)
+            throw new ConfigurationException("Cannot alter table id.");
 
         switch (oType)
         {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cee35e42/src/java/org/apache/cassandra/cql3/statements/CFPropDefs.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CFPropDefs.java b/src/java/org/apache/cassandra/cql3/statements/CFPropDefs.java
index 343c0c0..17edd6d 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CFPropDefs.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CFPropDefs.java
@@ -17,10 +17,7 @@
  */
 package org.apache.cassandra.cql3.statements;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import org.apache.cassandra.cache.CachingOptions;
 import org.apache.cassandra.config.CFMetaData;
@@ -49,6 +46,8 @@ public class CFPropDefs extends PropertyDefinitions
     public static final String KW_COMPACTION = "compaction";
     public static final String KW_COMPRESSION = "compression";
 
+    public static final String KW_ID = "id";
+
     public static final String COMPACTION_STRATEGY_CLASS_KEY = "class";
 
     public static final Set<String> keywords = new HashSet<>();
@@ -69,6 +68,7 @@ public class CFPropDefs extends PropertyDefinitions
         keywords.add(KW_COMPACTION);
         keywords.add(KW_COMPRESSION);
         keywords.add(KW_MEMTABLE_FLUSH_PERIOD);
+        keywords.add(KW_ID);
 
         obsoleteKeywords.add("index_interval");
         obsoleteKeywords.add("replicate_on_write");
@@ -86,6 +86,15 @@ public class CFPropDefs extends PropertyDefinitions
 
         validate(keywords, obsoleteKeywords);
 
+        try
+        {
+            getId();
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new ConfigurationException("Invalid table id", e);
+        }
+
         Map<String, String> compactionOptions = getCompactionOptions();
         if (!compactionOptions.isEmpty())
         {
@@ -170,6 +179,12 @@ public class CFPropDefs extends PropertyDefinitions
         return getInt(KW_DEFAULT_TIME_TO_LIVE, 0);
     }
 
+    public UUID getId() throws SyntaxException
+    {
+        String id = getSimple(KW_ID);
+        return id != null ? UUID.fromString(id) : null;
+    }
+
     public void applyToCFMetadata(CFMetaData cfm) throws ConfigurationException, SyntaxException
     {
         if (hasProperty(KW_COMMENT))

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cee35e42/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
index 5c4e9e2..2c4b23b 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
@@ -64,13 +64,15 @@ public class CreateTableStatement extends SchemaAlteringStatement
     private final Set<ColumnIdentifier> staticColumns;
     private final CFPropDefs properties;
     private final boolean ifNotExists;
+    private final UUID id;
 
-    public CreateTableStatement(CFName name, CFPropDefs properties, boolean ifNotExists, Set<ColumnIdentifier> staticColumns)
+    public CreateTableStatement(CFName name, CFPropDefs properties, boolean ifNotExists, Set<ColumnIdentifier> staticColumns, UUID id)
     {
         super(name);
         this.properties = properties;
         this.ifNotExists = ifNotExists;
         this.staticColumns = staticColumns;
+        this.id = id;
 
         try
         {
@@ -146,7 +148,8 @@ public class CreateTableStatement extends SchemaAlteringStatement
         newCFMD = new CFMetaData(keyspace(),
                                  columnFamily(),
                                  ColumnFamilyType.Standard,
-                                 comparator);
+                                 comparator,
+                                 id);
         applyPropertiesTo(newCFMD);
         return newCFMD;
     }
@@ -204,7 +207,7 @@ public class CreateTableStatement extends SchemaAlteringStatement
 
             properties.validate();
 
-            CreateTableStatement stmt = new CreateTableStatement(cfName, properties, ifNotExists, staticColumns);
+            CreateTableStatement stmt = new CreateTableStatement(cfName, properties, ifNotExists, staticColumns, properties.getId());
 
             boolean hasCounters = false;
             Map<ByteBuffer, CollectionType> definedMultiCellCollections = null;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/cee35e42/test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java
new file mode 100644
index 0000000..385ed3d
--- /dev/null
+++ b/test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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();
+            CommitLog.instance.recover();
+        }
+        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));
+    }
+}


[3/3] cassandra git commit: Merge branch 'cassandra-2.2' into cassandra-3.0

Posted by ma...@apache.org.
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/cassandra-3.0
Commit: db40ef8398e34618086184487ffcc3310830cec7
Parents: 71bac92 85c7241
Author: Marcus Eriksson <ma...@apache.org>
Authored: Tue Dec 15 10:25:33 2015 +0100
Committer: Marcus Eriksson <ma...@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));
+     }
+ }


[2/3] cassandra git commit: Merge branch 'cassandra-2.1' into cassandra-2.2

Posted by ma...@apache.org.
Merge branch 'cassandra-2.1' into cassandra-2.2


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/85c72418
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/85c72418
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/85c72418

Branch: refs/heads/cassandra-3.0
Commit: 85c724183a2caee3a9bb73441d27b3cbce7207c4
Parents: 263763a cee35e4
Author: Marcus Eriksson <ma...@apache.org>
Authored: Tue Dec 15 10:01:20 2015 +0100
Committer: Marcus Eriksson <ma...@apache.org>
Committed: Tue Dec 15 10:06:44 2015 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../org/apache/cassandra/config/CFMetaData.java |   4 +-
 .../cql3/statements/AlterTableStatement.java    |   2 +
 .../cassandra/cql3/statements/CFPropDefs.java   |  24 ++++-
 .../cql3/statements/CreateTableStatement.java   |   9 +-
 .../operations/DropRecreateAndRestoreTest.java  | 105 +++++++++++++++++++
 6 files changed, 135 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/85c72418/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index 592ba0a,7f1d66b..c9074fc
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,10 -1,5 +1,11 @@@
 -2.1.13
 +2.2.5
 + * Add property to allow listening on broadcast interface (CASSANDRA-9748)
 + * Fix regression in split size on CqlInputFormat (CASSANDRA-10835)
 + * Better handling of SSL connection errors inter-node (CASSANDRA-10816)
 + * Disable reloading of GossipingPropertyFileSnitch (CASSANDRA-9474)
 + * Verify tables in pseudo-system keyspaces at startup (CASSANDRA-10761)
 +Merged from 2.1:
+  * Allow CREATE TABLE WITH ID (CASSANDRA-9179)
   * Make Stress compiles within eclipse (CASSANDRA-10807)
   * Cassandra Daemon should print JVM arguments (CASSANDRA-10764)
   * Allow cancellation of index summary redistribution (CASSANDRA-8805)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/85c72418/src/java/org/apache/cassandra/config/CFMetaData.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/85c72418/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/85c72418/src/java/org/apache/cassandra/cql3/statements/CFPropDefs.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/85c72418/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
index 7810a8a,2c4b23b..1b3665c
--- a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java
@@@ -71,13 -72,21 +72,14 @@@ public class CreateTableStatement exten
          this.properties = properties;
          this.ifNotExists = ifNotExists;
          this.staticColumns = staticColumns;
+         this.id = id;
  
 -        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);
 -        }
 +        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

http://git-wip-us.apache.org/repos/asf/cassandra/blob/85c72418/test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/cql3/validation/operations/DropRecreateAndRestoreTest.java
index 0000000,385ed3d..4a3a51d
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,105 @@@
+ /*
+  * 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();
++            CommitLog.instance.resetUnsafe(true);
+             CommitLog.instance.recover();
+         }
+         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));
+     }
+ }