| // 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.impala.analysis; |
| |
| import java.util.Map; |
| |
| import org.apache.impala.catalog.Column; |
| import org.apache.impala.catalog.ColumnStats; |
| import org.apache.impala.common.AnalysisException; |
| import org.apache.impala.thrift.TAlterTableParams; |
| import org.apache.impala.thrift.TAlterTableType; |
| import org.apache.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 Map<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, |
| Map<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(col.getType(), 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(col.getType(), 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; |
| } |
| } |