impala-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tarmstr...@apache.org
Subject [12/23] incubator-impala git commit: IMPALA-3369: Add ALTER TABLE SET COLUMN STATS statement.
Date Wed, 01 Jun 2016 06:32:31 GMT
IMPALA-3369: Add ALTER TABLE SET COLUMN STATS statement.

Adds a new command to manually set the table-level column stats.

Syntax:
ALTER TABLE [<db_name>.]<tbl_name> SET COLUMN STATS <col_name>
('statsKey'='val','statsKey2',='val2')

Valid values for 'statsKey': numDVs, numNulls, avgSize, maxSize

The 'val' portion needs to be a number appropriate for the given stats
key (e.g., a long for numDVs, a float for avgSize).

The special value of '-1' is allowed to reset stats to 'unknown'.

The keys as well as the values are specified as string literals to be
consistent with the existing DDL for setting TBLPROPERTIES/SERDEPROPERTIES,
in particular, setting the 'numRows' table/partition property.

Testing: Ran the tests locally on exhaustive. Did private runs
on core/hdfs and core/S3.

Change-Id: I45cd8aa7241ea962788ba9ca7d0bbfd864c4304f
Reviewed-on: http://gerrit.cloudera.org:8080/3189
Reviewed-by: Alex Behm <alex.behm@cloudera.com>
Tested-by: Internal Jenkins


Project: http://git-wip-us.apache.org/repos/asf/incubator-impala/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-impala/commit/77da3834
Tree: http://git-wip-us.apache.org/repos/asf/incubator-impala/tree/77da3834
Diff: http://git-wip-us.apache.org/repos/asf/incubator-impala/diff/77da3834

Branch: refs/heads/master
Commit: 77da3834ffed32a365e64aad66f9bd4863bc14ee
Parents: d16e832
Author: Alex Behm <alex.behm@cloudera.com>
Authored: Mon May 23 11:01:39 2016 -0700
Committer: Tim Armstrong <tarmstrong@cloudera.com>
Committed: Tue May 31 23:32:11 2016 -0700

----------------------------------------------------------------------
 common/thrift/JniCatalog.thrift                 |   5 +-
 fe/src/main/cup/sql-parser.cup                  |  13 +-
 .../analysis/AlterTableSetColumnStats.java      | 155 +++++++++++++++++++
 .../impala/analysis/AlterTableStmt.java         |   8 +-
 .../cloudera/impala/catalog/ColumnStats.java    |  80 +++++++++-
 .../com/cloudera/impala/catalog/KuduTable.java  |  25 ++-
 .../com/cloudera/impala/common/RuntimeEnv.java  |  11 +-
 .../impala/service/CatalogOpExecutor.java       |  23 ++-
 .../impala/analysis/AnalyzeDDLTest.java         | 145 ++++++++++++++++-
 .../cloudera/impala/analysis/ParserTest.java    |  12 ++
 .../com/cloudera/impala/testutil/TestUtils.java |  18 +--
 .../QueryTest/alter-table-set-column-stats.test | 151 ++++++++++++++++++
 tests/metadata/test_ddl.py                      |   8 +
 13 files changed, 606 insertions(+), 48 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/common/thrift/JniCatalog.thrift
----------------------------------------------------------------------
diff --git a/common/thrift/JniCatalog.thrift b/common/thrift/JniCatalog.thrift
index 5d700cf..5f2bd9d 100644
--- a/common/thrift/JniCatalog.thrift
+++ b/common/thrift/JniCatalog.thrift
@@ -63,7 +63,7 @@ enum TAlterTableType {
   SET_FILE_FORMAT,
   SET_LOCATION,
   SET_TBL_PROPERTIES,
-  // Used internally by the COMPUTE STATS DDL command.
+  // Used internally by COMPUTE STATS and by ALTER TABLE SET COLUMN STATS.
   UPDATE_STATS,
   SET_CACHED,
   RECOVER_PARTITIONS,
@@ -282,7 +282,8 @@ struct TAlterTableSetLocationParams {
 }
 
 // Parameters for updating the table and/or column statistics
-// of a table. Used internally by a COMPUTE STATS command.
+// of a table. Used by ALTER TABLE SET COLUMN STATS, and internally by
+// a COMPUTE STATS command.
 struct TAlterTableUpdateStatsParams {
   // Fully qualified name of the table to be updated.
   1: required CatalogObjects.TTableName table_name

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/main/cup/sql-parser.cup
----------------------------------------------------------------------
diff --git a/fe/src/main/cup/sql-parser.cup b/fe/src/main/cup/sql-parser.cup
index e6d2f59..62a2b80 100644
--- a/fe/src/main/cup/sql-parser.cup
+++ b/fe/src/main/cup/sql-parser.cup
@@ -891,12 +891,19 @@ alter_tbl_stmt ::=
     table_property_type:target LPAREN properties_map:properties RPAREN
   {: RESULT = new AlterTableSetTblProperties(table, partition, target, properties); :}
   | KW_ALTER KW_TABLE table_name:table opt_partition_spec:partition KW_SET
+    KW_COLUMN KW_STATS IDENT:col LPAREN properties_map:map RPAREN
+  {:
+    // The opt_partition_spec is used to avoid conflicts even though
+    // a partition clause does not make sense for this stmt. If a partition
+    // is given, manually throw a parse error.
+    if (partition != null) parser.parseError("set", SqlParserSymbols.KW_SET);
+    RESULT = new AlterTableSetColumnStats(table, col, map);
+  :}
+  | KW_ALTER KW_TABLE table_name:table opt_partition_spec:partition KW_SET
     cache_op_val:cache_op
   {:
     // Ensure a parser error is thrown for ALTER statements if no cache op is specified.
-    if (cache_op == null) {
-      parser.parseError("set", SqlParserSymbols.KW_SET);
-    }
+    if (cache_op == null) parser.parseError("set", SqlParserSymbols.KW_SET);
     RESULT = new AlterTableSetCachedStmt(table, partition, cache_op);
   :}
   | KW_ALTER KW_TABLE table_name:table KW_RECOVER KW_PARTITIONS

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/main/java/com/cloudera/impala/analysis/AlterTableSetColumnStats.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/AlterTableSetColumnStats.java b/fe/src/main/java/com/cloudera/impala/analysis/AlterTableSetColumnStats.java
new file mode 100644
index 0000000..1c2d97a
--- /dev/null
+++ b/fe/src/main/java/com/cloudera/impala/analysis/AlterTableSetColumnStats.java
@@ -0,0 +1,155 @@
+// Copyright 2016 Cloudera Inc.
+//
+// Licensed 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 com.cloudera.impala.analysis;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.cloudera.impala.catalog.Column;
+import com.cloudera.impala.catalog.ColumnStats;
+import com.cloudera.impala.common.AnalysisException;
+import com.cloudera.impala.thrift.TAlterTableParams;
+import com.cloudera.impala.thrift.TAlterTableType;
+import com.cloudera.impala.thrift.TAlterTableUpdateStatsParams;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+
+/**
+* Represents an ALTER TABLE [<dbName>.]<tableName> SET COLUMN STATS <colName>
+* ('statsKey'='val','statsKey2',='val2') statement.
+*
+* The keys as well as the values are specified as string literals to be consistent
+* with the existing DDL for setting TBLPROPERTIES/SERDEPROPERTIES, in particular,
+* setting the 'numRows' table/partition property.
+*
+* Stats key comparisons are case-insensitive.
+*/
+public class AlterTableSetColumnStats extends AlterTableStmt {
+  private final String colName_;
+  private final HashMap<String, String> statsMap_;
+
+  // Complete column stats reflecting this alteration. Existing stats values
+  // are preserved. Result of analysis.
+  private ColumnStats colStats_;
+
+  public AlterTableSetColumnStats(TableName tableName, String colName,
+      HashMap<String, String> statsMap) {
+    super(tableName);
+    colName_ = colName;
+    statsMap_ = statsMap;
+  }
+
+  @Override
+  public void analyze(Analyzer analyzer) throws AnalysisException {
+    super.analyze(analyzer);
+
+    Column col = getTargetTable().getColumn(colName_);
+    if (col == null) {
+      throw new AnalysisException(
+          String.format("Column '%s' does not exist in table: %s",
+              colName_, getTargetTable().getFullName()));
+    }
+    // Cannot update stats on partition columns because the HMS has no entries
+    // for them, and the stats can be computed directly from the metadata.
+    if (col.getPosition() < getTargetTable().getNumClusteringCols()) {
+      throw new AnalysisException(
+          "Updating the stats of a partition column is not allowed: " + colName_);
+    }
+    // Cannot update the stats if they are not supported for the column's type.
+    if (!ColumnStats.isSupportedColType(col.getType())) {
+      throw new AnalysisException(String.format(
+          "Statistics for column '%s' are not supported because " +
+          "it has type '%s'.", col.getName(), col.getType().toSql()));
+    }
+
+    // Copy the existing stats and then change the values according to the
+    // stats map of this stmt. The existing stats are first copied to preserve
+    // those stats values that are not changed by this stmt because all stats
+    // values are updated when altering the stats in the HMS.
+    colStats_ = col.getStats().clone();
+    for (Map.Entry<String, String> entry: statsMap_.entrySet()) {
+      ColumnStats.StatsKey statsKey = ColumnStats.StatsKey.fromString(entry.getKey());
+      if (statsKey == null) {
+        throw new AnalysisException(String.format(
+            "Invalid column stats key: %s\nValid keys are: %s",
+            entry.getKey(), Joiner.on(',').join(ColumnStats.StatsKey.values())));
+      }
+      setStatsValue(statsKey, entry.getValue(), col, colStats_);
+    }
+  }
+
+  /**
+   * Updates the given column stats based on statsKey and statsValue.
+   * Throws an AnalysisException if the statsValue is invalid or not applicable to the
+   * column (e.g., trying to update the avg/max size of a fixed-length column).
+   */
+  private void setStatsValue(ColumnStats.StatsKey statsKey, String statsValue,
+      Column col, ColumnStats stats) throws AnalysisException {
+    // Updating max/avg size is only allowed for variable length columns.
+    if (col.getType().isFixedLengthType()
+        && (statsKey == ColumnStats.StatsKey.AVG_SIZE
+            || statsKey == ColumnStats.StatsKey.MAX_SIZE)) {
+      throw new AnalysisException(String.format(
+          "Cannot update the '%s' stats of column '%s' with type '%s'.\n" +
+          "Changing '%s' is only allowed for variable-length columns.",
+          statsKey, col.getName(), col.getType().toSql(), statsKey));
+    }
+
+    if (statsKey == ColumnStats.StatsKey.NUM_DISTINCT_VALUES ||
+        statsKey == ColumnStats.StatsKey.NUM_NULLS ||
+        statsKey == ColumnStats.StatsKey.MAX_SIZE) {
+      Long statsVal = null;
+      try {
+        statsVal = Long.parseLong(statsValue);
+      } catch (Exception e) {
+      }
+      if (statsVal == null || statsVal < -1) {
+        throw new AnalysisException(String.format(
+            "Invalid stats value '%s' for column stats key: %s\n" +
+            "Expected a positive integer or -1 for unknown.",
+            statsValue, statsKey));
+      }
+      stats.update(statsKey, statsVal);
+    } else if (statsKey == ColumnStats.StatsKey.AVG_SIZE) {
+      Float statsVal = null;
+      try {
+        statsVal = Float.parseFloat(statsValue);
+      } catch (Exception e) {
+      }
+      if (statsVal == null || (statsVal < 0 && statsVal != -1) ||
+          statsVal.isNaN() || statsVal.isInfinite()) {
+        throw new AnalysisException(String.format(
+            "Invalid stats value '%s' for column stats key: %s\n" +
+            "Expected a positive floating-point number or -1 for unknown.",
+            statsValue, statsKey));
+      }
+      stats.update(statsKey, statsVal);
+    } else {
+      Preconditions.checkState(false, "Unhandled StatsKey value: " + statsKey);
+    }
+  }
+
+  @Override
+  public TAlterTableParams toThrift() {
+   TAlterTableParams params = super.toThrift();
+   params.setAlter_type(TAlterTableType.UPDATE_STATS);
+   TAlterTableUpdateStatsParams updateStatsParams =
+       new TAlterTableUpdateStatsParams();
+   updateStatsParams.setTable_name(getTargetTable().getTableName().toThrift());
+   updateStatsParams.putToColumn_stats(colName_.toString(), colStats_.toThrift());
+   params.setUpdate_stats_params(updateStatsParams);
+   return params;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/main/java/com/cloudera/impala/analysis/AlterTableStmt.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/analysis/AlterTableStmt.java b/fe/src/main/java/com/cloudera/impala/analysis/AlterTableStmt.java
index 961cbc2..9335bbd 100644
--- a/fe/src/main/java/com/cloudera/impala/analysis/AlterTableStmt.java
+++ b/fe/src/main/java/com/cloudera/impala/analysis/AlterTableStmt.java
@@ -67,7 +67,10 @@ public abstract class AlterTableStmt extends StatementBase {
   @Override
   public void analyze(Analyzer analyzer) throws AnalysisException {
     table_ = analyzer.getTable(tableName_, Privilege.ALTER);
-    if (table_ instanceof KuduTable && !KuduTable.alterTableAllowed(this)) {
+    if (table_ instanceof KuduTable
+        && !(this instanceof AlterTableSetTblProperties)
+        && !(this instanceof AlterTableSetColumnStats)
+        && !(this instanceof AlterTableOrViewRenameStmt)) {
       throw new AnalysisException(String.format(
           "ALTER TABLE not allowed on Kudu table: %s", table_.getFullName()));
     }
@@ -75,7 +78,8 @@ public abstract class AlterTableStmt extends StatementBase {
       throw new AnalysisException(String.format(
           "ALTER TABLE not allowed on a view: %s", table_.getFullName()));
     }
-    if (table_ instanceof DataSourceTable) {
+    if (table_ instanceof DataSourceTable
+        && !(this instanceof AlterTableSetColumnStats)) {
       throw new AnalysisException(String.format(
           "ALTER TABLE not allowed on a table produced by a data source: %s",
           table_.getFullName()));

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/main/java/com/cloudera/impala/catalog/ColumnStats.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/catalog/ColumnStats.java b/fe/src/main/java/com/cloudera/impala/catalog/ColumnStats.java
index 5493c3e..b865ef3 100644
--- a/fe/src/main/java/com/cloudera/impala/catalog/ColumnStats.java
+++ b/fe/src/main/java/com/cloudera/impala/catalog/ColumnStats.java
@@ -47,6 +47,31 @@ public class ColumnStats {
       PrimitiveType.STRING, PrimitiveType.TIMESTAMP, PrimitiveType.TINYINT,
       PrimitiveType.DECIMAL);
 
+  public enum StatsKey {
+    NUM_DISTINCT_VALUES("numDVs"),
+    NUM_NULLS("numNulls"),
+    AVG_SIZE("avgSize"),
+    MAX_SIZE("maxSize");
+
+    private final String name_;
+
+    private StatsKey(String name) { name_ = name; }
+
+    /**
+     * Returns the StatsKey whose name matches 'key'. The comparison is
+     * case insensitive. Returns null if there is no matching StatsKey.
+     */
+    public static StatsKey fromString(String key) {
+      for (StatsKey k: values()) {
+        if (key.equalsIgnoreCase(k.name_)) return k;
+      }
+      return null;
+    }
+
+    @Override
+    public String toString() { return name_; }
+  }
+
   // in bytes: excludes serialization overhead
   private double avgSize_;
   // in bytes; includes serialization overhead.
@@ -60,6 +85,17 @@ public class ColumnStats {
   }
 
   /**
+   * C'tor for clone().
+   */
+  private ColumnStats(ColumnStats other) {
+    avgSize_ = other.avgSize_;
+    avgSerializedSize_ = other.avgSerializedSize_;
+    maxSize_ = other.maxSize_;
+    numDistinctValues_ = other.numDistinctValues_;
+    numNulls_ = other.numNulls_;
+  }
+
+  /**
    * Initializes all column stats values as "unknown". For fixed-length type
    * (those which don't need additional storage besides the slot they occupy),
    * sets avgSerializedSize and maxSize to their slot size.
@@ -118,13 +154,14 @@ public class ColumnStats {
     return this;
   }
 
-  public void setAvgSerializedSize(float avgSize) { this.avgSerializedSize_ = avgSize; }
-  public void setMaxSize(long maxSize) { this.maxSize_ = maxSize; }
+  public void setAvgSize(float avgSize) { avgSize_ = avgSize; }
+  public void setAvgSerializedSize(float avgSize) { avgSerializedSize_ = avgSize; }
+  public void setMaxSize(long maxSize) { maxSize_ = maxSize; }
   public long getNumDistinctValues() { return numDistinctValues_; }
   public void setNumDistinctValues(long numDistinctValues) {
     this.numDistinctValues_ = numDistinctValues;
   }
-  public void setNumNulls(long numNulls) { this.numNulls_ = numNulls; }
+  public void setNumNulls(long numNulls) { numNulls_ = numNulls; }
   public double getAvgSerializedSize() { return avgSerializedSize_; }
   public double getAvgSize() { return avgSize_; }
   public long getMaxSize() { return maxSize_; }
@@ -216,6 +253,40 @@ public class ColumnStats {
   }
 
   /**
+   * Sets the member corresponding to the given stats key to 'value'.
+   * Requires that the given value is of a type appropriate for the
+   * member being set. Throws if that is not the case.
+   */
+  public void update(StatsKey key, Number value) {
+    Preconditions.checkNotNull(key);
+    Preconditions.checkNotNull(value);
+    if (key == StatsKey.AVG_SIZE) {
+      Preconditions.checkArgument(value instanceof Float);
+    } else {
+      Preconditions.checkArgument(value instanceof Long);
+    }
+    switch (key) {
+      case NUM_DISTINCT_VALUES: {
+        numDistinctValues_ = (Long) value;
+        break;
+      }
+      case NUM_NULLS: {
+        numNulls_ = (Long) value;
+        break;
+      }
+      case AVG_SIZE: {
+        avgSize_ = (Float) value;
+        break;
+      }
+      case MAX_SIZE: {
+        maxSize_ = (Long) value;
+        break;
+      }
+      default: Preconditions.checkState(false);
+    }
+  }
+
+  /**
    * Returns true if the given PrimitiveType supports column stats updates.
    */
   public static boolean isSupportedColType(Type colType) {
@@ -254,4 +325,7 @@ public class ColumnStats {
         .add("numNulls_", numNulls_)
         .toString();
   }
+
+  @Override
+  public ColumnStats clone() { return new ColumnStats(this); }
 }

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/main/java/com/cloudera/impala/catalog/KuduTable.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/catalog/KuduTable.java b/fe/src/main/java/com/cloudera/impala/catalog/KuduTable.java
index b1d11bc..81e63f1 100644
--- a/fe/src/main/java/com/cloudera/impala/catalog/KuduTable.java
+++ b/fe/src/main/java/com/cloudera/impala/catalog/KuduTable.java
@@ -18,11 +18,16 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+
 import javax.xml.bind.DatatypeConverter;
 
-import com.cloudera.impala.analysis.AlterTableOrViewRenameStmt;
-import com.cloudera.impala.analysis.AlterTableSetTblProperties;
-import com.cloudera.impala.analysis.AlterTableStmt;
+import org.apache.hadoop.hive.metastore.HiveMetaStoreClient;
+import org.apache.hadoop.hive.metastore.api.FieldSchema;
+import org.apache.hadoop.hive.metastore.api.hive_metastoreConstants;
+import org.apache.log4j.Logger;
+import org.kududb.client.KuduClient;
+import org.kududb.client.LocatedTablet;
+
 import com.cloudera.impala.common.ImpalaRuntimeException;
 import com.cloudera.impala.thrift.TCatalogObjectType;
 import com.cloudera.impala.thrift.TColumn;
@@ -32,21 +37,14 @@ import com.cloudera.impala.thrift.TResultSetMetadata;
 import com.cloudera.impala.thrift.TTable;
 import com.cloudera.impala.thrift.TTableDescriptor;
 import com.cloudera.impala.thrift.TTableType;
-import com.cloudera.impala.util.TResultRowBuilder;
 import com.cloudera.impala.util.KuduUtil;
+import com.cloudera.impala.util.TResultRowBuilder;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import org.apache.hadoop.hive.metastore.HiveMetaStoreClient;
-import org.apache.hadoop.hive.metastore.TableType;
-import org.apache.hadoop.hive.metastore.api.FieldSchema;
-import org.apache.hadoop.hive.metastore.api.hive_metastoreConstants;
-import org.apache.log4j.Logger;
-import org.kududb.client.KuduClient;
-import org.kududb.client.LocatedTablet;
 
 /**
  * Impala representation of a Kudu table.
@@ -92,11 +90,6 @@ public class KuduTable extends Table {
   // The set of columns that are key columns in Kudu.
   private ImmutableList<String> kuduKeyColumnNames_;
 
-  public static boolean alterTableAllowed(AlterTableStmt stmt) {
-    return stmt instanceof AlterTableSetTblProperties ||
-        stmt instanceof AlterTableOrViewRenameStmt;
-  }
-
   protected KuduTable(TableId id, org.apache.hadoop.hive.metastore.api.Table msTable,
       Db db, String name, String owner) {
     super(id, msTable, db, name, owner);

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/main/java/com/cloudera/impala/common/RuntimeEnv.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/common/RuntimeEnv.java b/fe/src/main/java/com/cloudera/impala/common/RuntimeEnv.java
index 6f562e5..91b93ef 100644
--- a/fe/src/main/java/com/cloudera/impala/common/RuntimeEnv.java
+++ b/fe/src/main/java/com/cloudera/impala/common/RuntimeEnv.java
@@ -14,14 +14,12 @@
 
 package com.cloudera.impala.common;
 
-import java.lang.System;
-
-import com.cloudera.impala.thrift.TStartupOptions;
-import com.cloudera.impala.service.FeSupport;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.cloudera.impala.service.FeSupport;
+import com.cloudera.impala.thrift.TStartupOptions;
+
 /**
  * Contains runtime-specific parameters such as the number of CPU cores. Currently only
  * used in Plan cost estimation. The static RuntimeEnv members can be set so that tests
@@ -63,5 +61,8 @@ public class RuntimeEnv {
   public void setTestEnv(boolean v) { isTestEnv_ = v; }
   public boolean isTestEnv() { return isTestEnv_; }
   public boolean computeLineage() { return computeLineage_; }
+  public boolean isKuduSupported() {
+    return "true".equals(System.getenv("KUDU_IS_SUPPORTED"));
+  }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/main/java/com/cloudera/impala/service/CatalogOpExecutor.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/com/cloudera/impala/service/CatalogOpExecutor.java b/fe/src/main/java/com/cloudera/impala/service/CatalogOpExecutor.java
index ac21665..602f65a 100644
--- a/fe/src/main/java/com/cloudera/impala/service/CatalogOpExecutor.java
+++ b/fe/src/main/java/com/cloudera/impala/service/CatalogOpExecutor.java
@@ -23,7 +23,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import com.cloudera.impala.thrift.TDistributeParam;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.hive.common.StatsSetupConst;
 import org.apache.hadoop.hive.conf.HiveConf;
@@ -80,10 +79,10 @@ import com.cloudera.impala.catalog.TableLoadingException;
 import com.cloudera.impala.catalog.TableNotFoundException;
 import com.cloudera.impala.catalog.Type;
 import com.cloudera.impala.catalog.View;
-import com.cloudera.impala.common.FileSystemUtil;
 import com.cloudera.impala.catalog.delegates.DdlDelegate;
 import com.cloudera.impala.catalog.delegates.KuduDdlDelegate;
 import com.cloudera.impala.catalog.delegates.UnsupportedOpDelegate;
+import com.cloudera.impala.common.FileSystemUtil;
 import com.cloudera.impala.common.ImpalaException;
 import com.cloudera.impala.common.ImpalaRuntimeException;
 import com.cloudera.impala.common.InternalException;
@@ -119,6 +118,7 @@ import com.cloudera.impala.thrift.TCreateTableParams;
 import com.cloudera.impala.thrift.TDatabase;
 import com.cloudera.impala.thrift.TDdlExecRequest;
 import com.cloudera.impala.thrift.TDdlExecResponse;
+import com.cloudera.impala.thrift.TDistributeParam;
 import com.cloudera.impala.thrift.TDropDataSourceParams;
 import com.cloudera.impala.thrift.TDropDbParams;
 import com.cloudera.impala.thrift.TDropFunctionParams;
@@ -543,13 +543,20 @@ public class CatalogOpExecutor {
   }
 
   /**
-   * Alters an existing table's table and column statistics. Partitions are updated
+   * Alters an existing table's table and/or column statistics. Partitions are updated
    * in batches of size 'MAX_PARTITION_UPDATES_PER_RPC'.
    */
   private void alterTableUpdateStats(Table table, TAlterTableUpdateStatsParams params,
       TDdlExecResponse resp) throws ImpalaException {
     Preconditions.checkState(Thread.holdsLock(table));
-    Preconditions.checkState(params.isSetPartition_stats() && params.isSetTable_stats());
+    if (params.isSetTable_stats()) {
+      // Updating table and column stats via COMPUTE STATS.
+      Preconditions.checkState(
+          params.isSetPartition_stats() && params.isSetTable_stats());
+    } else {
+      // Only changing column stats via ALTER TABLE SET COLUMN STATS.
+      Preconditions.checkState(params.isSetColumn_stats());
+    }
 
     TableName tableName = table.getTableName();
     Preconditions.checkState(tableName != null && tableName.isFullyQualified());
@@ -569,13 +576,15 @@ public class CatalogOpExecutor {
     }
 
     MetaStoreClient msClient = catalog_.getMetaStoreClient();
-    int numTargetedPartitions;
+    int numTargetedPartitions = 0;
     int numUpdatedColumns = 0;
     try {
       // Update the table and partition row counts based on the query results.
       List<HdfsPartition> modifiedParts = Lists.newArrayList();
-      numTargetedPartitions = updateTableStats(table, params, msTbl, partitions,
-          modifiedParts);
+      if (params.isSetTable_stats()) {
+        numTargetedPartitions = updateTableStats(table, params, msTbl, partitions,
+            modifiedParts);
+      }
 
       ColumnStatistics colStats = null;
       if (params.isSetColumn_stats()) {

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/test/java/com/cloudera/impala/analysis/AnalyzeDDLTest.java
----------------------------------------------------------------------
diff --git a/fe/src/test/java/com/cloudera/impala/analysis/AnalyzeDDLTest.java b/fe/src/test/java/com/cloudera/impala/analysis/AnalyzeDDLTest.java
index e349ca7..796e8eb 100644
--- a/fe/src/test/java/com/cloudera/impala/analysis/AnalyzeDDLTest.java
+++ b/fe/src/test/java/com/cloudera/impala/analysis/AnalyzeDDLTest.java
@@ -19,6 +19,7 @@ import static org.junit.Assert.assertTrue;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.UUID;
 
 import org.apache.commons.lang3.StringUtils;
@@ -32,7 +33,7 @@ import org.junit.Test;
 
 import com.cloudera.impala.catalog.ArrayType;
 import com.cloudera.impala.catalog.CatalogException;
-import com.cloudera.impala.analysis.CreateTableStmt;
+import com.cloudera.impala.catalog.ColumnStats;
 import com.cloudera.impala.catalog.DataSource;
 import com.cloudera.impala.catalog.DataSourceTable;
 import com.cloudera.impala.catalog.PrimitiveType;
@@ -42,8 +43,10 @@ import com.cloudera.impala.catalog.StructType;
 import com.cloudera.impala.catalog.Type;
 import com.cloudera.impala.common.AnalysisException;
 import com.cloudera.impala.common.FileSystemUtil;
+import com.cloudera.impala.common.RuntimeEnv;
 import com.cloudera.impala.testutil.TestUtils;
 import com.cloudera.impala.util.MetaStoreUtil;
+import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 
@@ -563,6 +566,146 @@ public class AnalyzeDDLTest extends AnalyzerTest {
         "Partition spec does not exist: (year=9999, month=1).");
   }
 
+  @Test
+  public void TestAlterTableSetColumnStats() {
+    // Contains entries of the form 'statsKey'='statsValue' for every
+    // stats key. A dummy value is used for 'statsValue'.
+    List<String> testKeyValues = Lists.newArrayList();
+    for (ColumnStats.StatsKey statsKey: ColumnStats.StatsKey.values()) {
+      testKeyValues.add(String.format("'%s'='10'", statsKey));
+    }
+    // Test updating all stats keys individually.
+    for (String kv: testKeyValues) {
+      AnalyzesOk(String.format(
+          "alter table functional.alltypes set column stats string_col (%s)", kv));
+      // Stats key is case-insensitive.
+      AnalyzesOk(String.format(
+          "alter table functional.alltypes set column stats string_col (%s)",
+          kv.toLowerCase()));
+      AnalyzesOk(String.format(
+          "alter table functional.alltypes set column stats string_col (%s)",
+          kv.toUpperCase()));
+    }
+    // Test updating all stats keys at once in a single statement.
+    AnalyzesOk(String.format(
+        "alter table functional.alltypes set column stats string_col (%s)",
+        Joiner.on(",").join(testKeyValues)));
+    // Test setting all stats keys to -1 (unknown).
+    for (ColumnStats.StatsKey statsKey:  ColumnStats.StatsKey.values()) {
+      AnalyzesOk(String.format(
+          "alter table functional.alltypes set column stats string_col ('%s'='-1')",
+          statsKey));
+    }
+    // Duplicate stats keys are valid. The last entry is used.
+    AnalyzesOk("alter table functional.alltypes set column stats " +
+        "int_col ('numDVs'='2','numDVs'='3')");
+
+    // Test updating stats on all scalar types.
+    for (Type t: Type.getSupportedTypes()) {
+      if (t.isNull()) continue;
+      Preconditions.checkState(t.isScalarType());
+      String typeStr = t.getPrimitiveType().toString();
+      if (t.getPrimitiveType() == PrimitiveType.CHAR ||
+          t.getPrimitiveType() == PrimitiveType.VARCHAR) {
+        typeStr += "(60)";
+      }
+      String tblName = "t_" + t.getPrimitiveType();
+      addTestTable(String.format("create table %s (c %s)", tblName, typeStr));
+      AnalyzesOk(String.format(
+          "alter table %s set column stats c ('%s'='100','%s'='10')",
+          tblName, ColumnStats.StatsKey.NUM_DISTINCT_VALUES,
+          ColumnStats.StatsKey.NUM_NULLS));
+      // Test setting stats values to -1 (unknown).
+      AnalyzesOk(String.format(
+          "alter table %s set column stats c ('%s'='-1','%s'='-1')",
+          tblName, ColumnStats.StatsKey.NUM_DISTINCT_VALUES,
+          ColumnStats.StatsKey.NUM_NULLS));
+    }
+
+    // Setting stats works on all table types.
+    AnalyzesOk("alter table functional_hbase.alltypes set column stats " +
+        "int_col ('numNulls'='2')");
+    AnalyzesOk("alter table functional.alltypes_datasource set column stats " +
+        "int_col ('numDVs'='2')");
+    if (RuntimeEnv.INSTANCE.isKuduSupported()) {
+      AnalyzesOk("alter table functional_kudu.testtbl set column stats " +
+          "name ('numNulls'='2')");
+    }
+
+    // Table does not exist.
+    AnalysisError("alter table bad_tbl set column stats int_col ('numNulls'='2')",
+        "Table does not exist: default.bad_tbl");
+    // Column does not exist.
+    AnalysisError(
+        "alter table functional.alltypes set column stats bad_col ('numNulls'='2')",
+        "Column 'bad_col' does not exist in table: functional.alltypes");
+
+    // Cannot set column stats of a view.
+    AnalysisError(
+        "alter table functional.alltypes_view set column stats int_col ('numNulls'='2')",
+        "ALTER TABLE not allowed on a view: functional.alltypes_view");
+    // Cannot set column stats of partition columns.
+    AnalysisError(
+        "alter table functional.alltypes set column stats month ('numDVs'='10')",
+        "Updating the stats of a partition column is not allowed: month");
+    // Cannot set the size of a fixed-length column.
+    AnalysisError(
+        "alter table functional.alltypes set column stats int_col ('maxSize'='10')",
+        "Cannot update the 'maxSize' stats of column 'int_col' with type 'INT'.\n" +
+        "Changing 'maxSize' is only allowed for variable-length columns.");
+    AnalysisError(
+        "alter table functional.alltypes set column stats int_col ('avgSize'='10')",
+        "Cannot update the 'avgSize' stats of column 'int_col' with type 'INT'.\n" +
+        "Changing 'avgSize' is only allowed for variable-length columns.");
+    // Cannot set column stats of complex-typed columns.
+    AnalysisError(
+        "alter table functional.allcomplextypes set column stats int_array_col " +
+        "('numNulls'='10')",
+        "Statistics for column 'int_array_col' are not supported because " +
+        "it has type 'ARRAY<INT>'.");
+    AnalysisError(
+        "alter table functional.allcomplextypes set column stats int_map_col " +
+        "('numDVs'='10')",
+        "Statistics for column 'int_map_col' are not supported because " +
+        "it has type 'MAP<STRING,INT>'.");
+    AnalysisError(
+        "alter table functional.allcomplextypes set column stats int_struct_col " +
+        "('numDVs'='10')",
+        "Statistics for column 'int_struct_col' are not supported because " +
+        "it has type 'STRUCT<f1:INT,f2:INT>'.");
+
+    // Invalid stats key.
+    AnalysisError(
+        "alter table functional.alltypes set column stats int_col ('badKey'='10')",
+        "Invalid column stats key: badKey");
+    AnalysisError(
+        "alter table functional.alltypes set column stats " +
+        "int_col ('numDVs'='10',''='10')",
+        "Invalid column stats key: ");
+    // Invalid long stats values.
+    AnalysisError(
+        "alter table functional.alltypes set column stats int_col ('numDVs'='bad')",
+        "Invalid stats value 'bad' for column stats key: numDVs");
+    AnalysisError(
+        "alter table functional.alltypes set column stats int_col ('numDVs'='-10')",
+        "Invalid stats value '-10' for column stats key: numDVs");
+    // Invalid float stats values.
+    AnalysisError(
+        "alter table functional.alltypes set column stats string_col ('avgSize'='bad')",
+        "Invalid stats value 'bad' for column stats key: avgSize");
+    AnalysisError(
+        "alter table functional.alltypes set column stats string_col ('avgSize'='-1.5')",
+        "Invalid stats value '-1.5' for column stats key: avgSize");
+    AnalysisError(
+        "alter table functional.alltypes set column stats string_col ('avgSize'='-0.5')",
+        "Invalid stats value '-0.5' for column stats key: avgSize");
+    AnalysisError(
+        "alter table functional.alltypes set column stats string_col ('avgSize'='NaN')",
+        "Invalid stats value 'NaN' for column stats key: avgSize");
+    AnalysisError(
+        "alter table functional.alltypes set column stats string_col ('avgSize'='inf')",
+        "Invalid stats value 'inf' for column stats key: avgSize");
+  }
 
   @Test
   public void TestAlterTableSetAvroProperties() {

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/test/java/com/cloudera/impala/analysis/ParserTest.java
----------------------------------------------------------------------
diff --git a/fe/src/test/java/com/cloudera/impala/analysis/ParserTest.java b/fe/src/test/java/com/cloudera/impala/analysis/ParserTest.java
index f503d69..f39f902 100644
--- a/fe/src/test/java/com/cloudera/impala/analysis/ParserTest.java
+++ b/fe/src/test/java/com/cloudera/impala/analysis/ParserTest.java
@@ -2097,6 +2097,18 @@ public class ParserTest {
       }
     }
 
+    // Test SET COLUMN STATS.
+    ParsesOk("ALTER TABLE Foo SET COLUMN STATS col ('numDVs'='10')");
+    ParsesOk("ALTER TABLE Foo SET COLUMN STATS col ('numDVs'='10','maxSize'='20')");
+    ParsesOk("ALTER TABLE TestDb.Foo SET COLUMN STATS col ('avgSize'='20')");
+    ParserError("ALTER TABLE SET COLUMN STATS col ('numDVs'='10'");
+    ParserError("ALTER TABLE Foo SET COLUMN STATS ('numDVs'='10'");
+    ParserError("ALTER TABLE Foo SET COLUMN STATS col");
+    ParserError("ALTER TABLE Foo SET COLUMN STATS col ()");
+    ParserError("ALTER TABLE Foo SET COLUMN STATS col (numDVs='10')");
+    ParserError("ALTER TABLE Foo SET COLUMN STATS col ('numDVs'=10)");
+    ParserError("ALTER TABLE Foo PARTITION (p=1) SET COLUMN STATS col ('avgSize'='20')");
+
     for (String cacheClause: Lists.newArrayList("UNCACHED", "CACHED in 'pool'",
         "CACHED in 'pool' with replication = 4")) {
       ParsesOk("ALTER TABLE Foo SET " + cacheClause);

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/fe/src/test/java/com/cloudera/impala/testutil/TestUtils.java
----------------------------------------------------------------------
diff --git a/fe/src/test/java/com/cloudera/impala/testutil/TestUtils.java b/fe/src/test/java/com/cloudera/impala/testutil/TestUtils.java
index 44070c7..7f51502 100644
--- a/fe/src/test/java/com/cloudera/impala/testutil/TestUtils.java
+++ b/fe/src/test/java/com/cloudera/impala/testutil/TestUtils.java
@@ -1,27 +1,28 @@
 // Copyright (c) 2012 Cloudera, Inc. All rights reserved.
 
 package com.cloudera.impala.testutil;
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
-import java.util.Scanner;
 import java.util.Map;
-import java.io.StringWriter;
-import java.io.StringReader;
+import java.util.Scanner;
 
 import javax.json.Json;
-import javax.json.stream.JsonGenerator;
-import javax.json.JsonReader;
 import javax.json.JsonObject;
+import javax.json.JsonReader;
 import javax.json.JsonWriter;
 import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGenerator;
 
+import org.junit.Assume;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.junit.Assume;
 
 import com.cloudera.impala.catalog.Catalog;
+import com.cloudera.impala.common.RuntimeEnv;
 import com.cloudera.impala.thrift.TClientRequest;
 import com.cloudera.impala.thrift.TNetworkAddress;
 import com.cloudera.impala.thrift.TQueryCtx;
@@ -29,7 +30,6 @@ import com.cloudera.impala.thrift.TQueryOptions;
 import com.cloudera.impala.thrift.TSessionState;
 import com.cloudera.impala.thrift.TSessionType;
 import com.cloudera.impala.thrift.TUniqueId;
-
 import com.google.common.collect.Maps;
 
 public class TestUtils {
@@ -52,7 +52,7 @@ public class TestUtils {
   static class PathFilter implements ResultFilter {
     private final static String PATH_FILTER = "-*\\d+--\\d+_\\d+.*$";
     private final static String PORT_FILTER = "//\\w+(\\.\\w+)?(\\.\\w+)?:\\d+";
-    private String filterKey_;
+    private final String filterKey_;
 
     public PathFilter(String prefix) { filterKey_ = prefix; }
 
@@ -270,6 +270,6 @@ public class TestUtils {
   }
 
   public static void assumeKuduIsSupported() {
-    Assume.assumeTrue("true".equals(System.getenv("KUDU_IS_SUPPORTED")));
+    Assume.assumeTrue(RuntimeEnv.INSTANCE.isKuduSupported());
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/testdata/workloads/functional-query/queries/QueryTest/alter-table-set-column-stats.test
----------------------------------------------------------------------
diff --git a/testdata/workloads/functional-query/queries/QueryTest/alter-table-set-column-stats.test
b/testdata/workloads/functional-query/queries/QueryTest/alter-table-set-column-stats.test
new file mode 100644
index 0000000..c3d07c2
--- /dev/null
+++ b/testdata/workloads/functional-query/queries/QueryTest/alter-table-set-column-stats.test
@@ -0,0 +1,151 @@
+====
+---- QUERY
+create external table alltypes_clone like functional_parquet.alltypes
+location '/test-warehouse/alltypes_parquet';
+alter table alltypes_clone recover partitions;
+====
+---- QUERY
+# Set various column stats.
+alter table alltypes_clone set column stats double_col ('numDVs'='2');
+alter table alltypes_clone set column stats timestamp_col ('numNulls'='9');
+alter table alltypes_clone set column stats int_col ('numDVs'='100','numNulls'='20');
+alter table alltypes_clone set column stats string_col ('maxSize'='555','avgSize'='60');
+====
+---- QUERY
+show column stats alltypes_clone
+---- LABELS
+COLUMN, TYPE, #DISTINCT VALUES, #NULLS, MAX SIZE, AVG SIZE
+---- RESULTS
+'id','INT',-1,-1,4,4
+'bool_col','BOOLEAN',-1,-1,1,1
+'tinyint_col','TINYINT',-1,-1,1,1
+'smallint_col','SMALLINT',-1,-1,2,2
+'int_col','INT',100,20,4,4
+'bigint_col','BIGINT',-1,-1,8,8
+'float_col','FLOAT',-1,-1,4,4
+'double_col','DOUBLE',2,-1,8,8
+'date_string_col','STRING',-1,-1,-1,-1
+'string_col','STRING',-1,-1,555,60
+'timestamp_col','TIMESTAMP',-1,9,16,16
+'year','INT',2,0,4,4
+'month','INT',12,0,4,4
+---- TYPES
+STRING, STRING, BIGINT, BIGINT, INT, DOUBLE
+====
+---- QUERY
+# Make sure compute stats still works.
+compute stats alltypes_clone
+---- RESULTS
+'Updated 24 partition(s) and 11 column(s).'
+---- TYPES
+STRING
+====
+---- QUERY
+# Reset the column stats to an unknown state by setting the values to -1
+alter table alltypes_clone set column stats double_col ('numDVs'='-1');
+alter table alltypes_clone set column stats timestamp_col ('numNulls'='-1');
+alter table alltypes_clone set column stats int_col ('numDVs'='-1','numNulls'='-1');
+alter table alltypes_clone set column stats string_col ('maxSize'='-1','avgSize'='-1');
+====
+---- QUERY
+show column stats alltypes_clone
+---- LABELS
+COLUMN, TYPE, #DISTINCT VALUES, #NULLS, MAX SIZE, AVG SIZE
+---- RESULTS
+'id','INT',7505,-1,4,4
+'bool_col','BOOLEAN',2,-1,1,1
+'tinyint_col','TINYINT',10,-1,1,1
+'smallint_col','SMALLINT',10,-1,2,2
+'int_col','INT',-1,-1,4,4
+'bigint_col','BIGINT',10,-1,8,8
+'float_col','FLOAT',10,-1,4,4
+'double_col','DOUBLE',-1,-1,8,8
+'date_string_col','STRING',736,-1,8,8
+'string_col','STRING',10,-1,-1,-1
+'timestamp_col','TIMESTAMP',7554,-1,16,16
+'year','INT',2,0,4,4
+'month','INT',12,0,4,4
+---- TYPES
+STRING, STRING, BIGINT, BIGINT, INT, DOUBLE
+====
+---- QUERY
+# Also alter a few 'numRows' parameters to make sure manually setting all stats works.
+alter table alltypes_clone partition(year=2009,month=2) set tblproperties('numRows'='280');
+alter table alltypes_clone set tblproperties('numRows'='7300');
+====
+---- QUERY
+# Check that we can query the table.
+select id, int_col, double_col, string_col, timestamp_col from alltypes_clone
+where year = 2009 and month between 2 and 3 and int_col = 9 and id between 300 and 400
+---- RESULTS
+319,9,90.89999999999999,'9',2009-02-01 00:09:00.360000000
+329,9,90.89999999999999,'9',2009-02-02 00:19:00.810000000
+339,9,90.89999999999999,'9',2009-02-03 00:29:01.260000000
+349,9,90.89999999999999,'9',2009-02-04 00:39:01.710000000
+359,9,90.89999999999999,'9',2009-02-05 00:49:02.160000000
+369,9,90.89999999999999,'9',2009-02-06 00:59:02.610000000
+379,9,90.89999999999999,'9',2009-02-07 01:09:03.600000000
+389,9,90.89999999999999,'9',2009-02-08 01:19:03.510000000
+399,9,90.89999999999999,'9',2009-02-09 01:29:03.960000000
+---- TYPES
+INT, INT, DOUBLE, STRING, TIMESTAMP
+====
+---- QUERY
+# Similar test on an HBase table.
+create external table alltypes_hbase_clone like functional_hbase.alltypes
+====
+---- QUERY
+alter table alltypes_hbase_clone set column stats double_col ('numDVs'='2');
+alter table alltypes_hbase_clone set column stats timestamp_col ('numNulls'='9');
+alter table alltypes_hbase_clone set column stats int_col ('numDVs'='100','numNulls'='20');
+alter table alltypes_hbase_clone set column stats string_col ('maxSize'='555','avgSize'='60');
+====
+---- QUERY
+show column stats alltypes_hbase_clone
+---- LABELS
+COLUMN, TYPE, #DISTINCT VALUES, #NULLS, MAX SIZE, AVG SIZE
+---- RESULTS
+'id','INT',-1,-1,4,4
+'bool_col','BOOLEAN',-1,-1,1,1
+'tinyint_col','TINYINT',-1,-1,1,1
+'smallint_col','SMALLINT',-1,-1,2,2
+'int_col','INT',100,20,4,4
+'bigint_col','BIGINT',-1,-1,8,8
+'float_col','FLOAT',-1,-1,4,4
+'double_col','DOUBLE',2,-1,8,8
+'date_string_col','STRING',-1,-1,-1,-1
+'string_col','STRING',-1,-1,555,60
+'timestamp_col','TIMESTAMP',-1,9,16,16
+'year','INT',-1,-1,4,4
+'month','INT',-1,-1,4,4
+---- TYPES
+STRING, STRING, BIGINT, BIGINT, INT, DOUBLE
+====
+---- QUERY
+# Reset the column stats to an unknown state by setting the values to -1
+alter table alltypes_hbase_clone set column stats double_col ('numDVs'='-1');
+alter table alltypes_hbase_clone set column stats timestamp_col ('numNulls'='-1');
+alter table alltypes_hbase_clone set column stats int_col ('numDVs'='-1','numNulls'='-1');
+alter table alltypes_hbase_clone set column stats string_col ('maxSize'='-1','avgSize'='-1');
+====
+---- QUERY
+show column stats alltypes_hbase_clone
+---- LABELS
+COLUMN, TYPE, #DISTINCT VALUES, #NULLS, MAX SIZE, AVG SIZE
+---- RESULTS
+'id','INT',-1,-1,4,4
+'bool_col','BOOLEAN',-1,-1,1,1
+'tinyint_col','TINYINT',-1,-1,1,1
+'smallint_col','SMALLINT',-1,-1,2,2
+'int_col','INT',-1,-1,4,4
+'bigint_col','BIGINT',-1,-1,8,8
+'float_col','FLOAT',-1,-1,4,4
+'double_col','DOUBLE',-1,-1,8,8
+'date_string_col','STRING',-1,-1,-1,-1
+'string_col','STRING',-1,-1,-1,-1
+'timestamp_col','TIMESTAMP',-1,-1,16,16
+'year','INT',-1,-1,4,4
+'month','INT',-1,-1,4,4
+---- TYPES
+STRING, STRING, BIGINT, BIGINT, INT, DOUBLE
+====

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/77da3834/tests/metadata/test_ddl.py
----------------------------------------------------------------------
diff --git a/tests/metadata/test_ddl.py b/tests/metadata/test_ddl.py
index 6179d1a..9cb5ce5 100644
--- a/tests/metadata/test_ddl.py
+++ b/tests/metadata/test_ddl.py
@@ -318,6 +318,14 @@ class TestDdlStatements(ImpalaTestSuite):
     self.run_test_case('QueryTest/alter-table', vector, use_db='alter_table_test_db',
         multiple_impalad=self._use_multiple_impalad(vector))
 
+  @pytest.mark.execute_serially
+  @SkipIf.not_default_fs
+  def test_alter_set_column_stats(self, vector):
+    self._create_db('alter_table_test_db', sync=True)
+    self.run_test_case('QueryTest/alter-table-set-column-stats',
+        vector, use_db='alter_table_test_db',
+        multiple_impalad=self._use_multiple_impalad(vector))
+
   @SkipIfLocal.hdfs_client
   @pytest.mark.execute_serially
   def test_drop_partition_with_purge(self, vector):



Mime
View raw message