blob: 0f1afa7a6529368de309723e61fcf899be6dcdba [file] [log] [blame]
// 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.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.hive.metastore.api.SQLForeignKey;
import org.apache.hadoop.hive.metastore.api.SQLPrimaryKey;
import org.apache.impala.authorization.Privilege;
import org.apache.impala.catalog.Column;
import org.apache.impala.catalog.FeFsTable;
import org.apache.impala.catalog.FeTable;
import org.apache.impala.catalog.HdfsStorageDescriptor;
import org.apache.impala.catalog.RowFormat;
import org.apache.impala.catalog.Type;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.common.FileSystemUtil;
import org.apache.impala.common.Pair;
import org.apache.impala.thrift.TAccessEvent;
import org.apache.impala.thrift.TCatalogObjectType;
import org.apache.impala.thrift.THdfsFileFormat;
import org.apache.impala.thrift.TQueryOptions;
import org.apache.impala.thrift.TSortingOrder;
import org.apache.impala.util.AcidUtils;
import org.apache.impala.util.MetaStoreUtil;
import org.apache.thrift.TException;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* Represents the table parameters in a CREATE TABLE statement. These parameters
* correspond to the following clauses in a CREATE TABLE statement:
* - EXTERNAL
* - IF NOT EXISTS
* - PARTITIONED BY
* - PARTITION BY
* - ROWFORMAT
* - FILEFORMAT
* - COMMENT
* - SERDEPROPERTIES
* - TBLPROPERTIES
* - LOCATION
* - CACHED IN
* - SORT BY
*/
class TableDef {
// Name of the new table
private final TableName tableName_;
// List of column definitions
private final List<ColumnDef> columnDefs_ = new ArrayList<>();
// Names of primary key columns. Populated by the parser. An empty value doesn't
// mean no primary keys were specified as the columnDefs_ could contain primary keys.
private final List<String> primaryKeyColNames_ = new ArrayList<>();
// If true, the table's data will be preserved if dropped.
private final boolean isExternal_;
// If true, no errors are thrown if the table already exists.
private final boolean ifNotExists_;
// Partitioning parameters.
private final TableDataLayout dataLayout_;
/////////////////////////////////////////
// BEGIN: Members that need to be reset()
// Authoritative list of primary key column definitions populated during analysis.
private final List<ColumnDef> primaryKeyColDefs_ = new ArrayList<>();
// Hive primary keys and foreign keys structures populated during analysis.
List<SQLPrimaryKey> sqlPrimaryKeys_ = new ArrayList<>();
List<SQLForeignKey> sqlForeignKeys_ = new ArrayList<>();
public List<SQLPrimaryKey> getSqlPrimaryKeys() {
return sqlPrimaryKeys_;
}
public List<SQLForeignKey> getSqlForeignKeys() {
return sqlForeignKeys_;
}
// True if analyze() has been called.
private boolean isAnalyzed_ = false;
// Generated Kudu properties set during analysis.
private Map<String, String> generatedKuduProperties_ = new HashMap<>();
// END: Members that need to be reset()
/////////////////////////////////////////
/**
* Set of table options. These options are grouped together for convenience while
* parsing CREATE TABLE statements. They are typically found at the end of CREATE
* TABLE statements.
*/
static class Options {
// Optional list of columns to sort data by when inserting into this table.
final List<String> sortCols;
// Comment to attach to the table
final String comment;
// Custom row format of the table. Leave null to specify default row format.
final RowFormat rowFormat;
// Key/values to persist with table serde metadata.
final Map<String, String> serdeProperties;
// File format of the table
final THdfsFileFormat fileFormat;
// The HDFS location of where the table data will stored.
final HdfsUri location;
// The HDFS caching op that should be applied to this table.
final HdfsCachingOp cachingOp;
// Key/values to persist with table metadata.
final Map<String, String> tblProperties;
// Sorting order for SORT BY queries.
final TSortingOrder sortingOrder;
Options(Pair<List<String>, TSortingOrder> sortProperties, String comment,
RowFormat rowFormat, Map<String, String> serdeProperties,
THdfsFileFormat fileFormat, HdfsUri location, HdfsCachingOp cachingOp,
Map<String, String> tblProperties, TQueryOptions queryOptions) {
this.sortCols = sortProperties.first;
this.sortingOrder = sortProperties.second;
this.comment = comment;
this.rowFormat = rowFormat;
Preconditions.checkNotNull(serdeProperties);
this.serdeProperties = serdeProperties;
// The file format passed via STORED AS <file format> has a higher precedence than
// the one set in query options.
this.fileFormat = (fileFormat != null) ?
fileFormat : queryOptions.getDefault_file_format();
this.location = location;
this.cachingOp = cachingOp;
Preconditions.checkNotNull(tblProperties);
this.tblProperties = tblProperties;
}
public Options(String comment, TQueryOptions queryOptions) {
// Passing null to file format so that it uses the file format from the query option
// if specified, otherwise it will use the default file format, which is TEXT.
this(new Pair<>(ImmutableList.of(), TSortingOrder.LEXICAL), comment,
RowFormat.DEFAULT_ROW_FORMAT, new HashMap<>(), /* file format */null, null,
null, new HashMap<>(), queryOptions);
}
}
private Options options_;
/**
* Primary Key attributes grouped together to be populated by the parser.
* Currently only defined for HDFS tables.
*/
static class PrimaryKey {
// Primary key table name
final TableName pkTableName;
// Primary Key columns
final List<String> primaryKeyColNames;
// Primary Key constraint name
final String pkConstraintName;
// Constraints
final boolean relyCstr;
final boolean validateCstr;
final boolean enableCstr;
public PrimaryKey(TableName pkTableName, List<String> primaryKeyColNames,
String pkConstraintName, boolean relyCstr,
boolean validateCstr, boolean enableCstr) {
this.pkTableName = pkTableName;
this.primaryKeyColNames = primaryKeyColNames;
this.pkConstraintName = pkConstraintName;
this.relyCstr = relyCstr;
this.validateCstr = validateCstr;
this.enableCstr = enableCstr;
}
public TableName getPkTableName() {
return pkTableName;
}
public List<String> getPrimaryKeyColNames() {
return primaryKeyColNames;
}
public String getPkConstraintName() {
return pkConstraintName;
}
public boolean isRelyCstr() {
return relyCstr;
}
public boolean isValidateCstr() {
return validateCstr;
}
public boolean isEnableCstr() {
return enableCstr;
}
}
/**
* Foreign Key attributes grouped together to be populated by the parser.
* Currently only defined for HDFS tables. An FK definition is of the form
* "foreign key(col1, col2) references pk_tbl(col3, col4)"
*/
static class ForeignKey {
// Primary key table
final TableName pkTableName;
// Primary key cols
final List<String> primaryKeyColNames;
// Foreign key cols
final List<String> foreignKeyColNames;
// Name of fk
final String fkConstraintName;
// Fully qualified pk name. Set during analysis.
TableName fullyQualifiedPkTableName;
// Constraints
final boolean relyCstr;
final boolean validateCstr;
final boolean enableCstr;
ForeignKey(TableName pkTableName, List<String> primaryKeyColNames,
List<String> foreignKeyColNames, String fkName, boolean relyCstr,
boolean validateCstr, boolean enableCstr) {
this.pkTableName = pkTableName;
this.primaryKeyColNames = primaryKeyColNames;
this.foreignKeyColNames = foreignKeyColNames;
this.relyCstr = relyCstr;
this.validateCstr = validateCstr;
this.enableCstr = enableCstr;
this.fkConstraintName = fkName;
}
public TableName getPkTableName() {
return pkTableName;
}
public List<String> getPrimaryKeyColNames() {
return primaryKeyColNames;
}
public List<String> getForeignKeyColNames() {
return foreignKeyColNames;
}
public String getFkConstraintName() {
return fkConstraintName;
}
public TableName getFullyQualifiedPkTableName() {
return fullyQualifiedPkTableName;
}
public boolean isRelyCstr() {
return relyCstr;
}
public boolean isValidateCstr() {
return validateCstr;
}
public boolean isEnableCstr() {
return enableCstr;
}
}
// A TableDef will have only one primary key.
private PrimaryKey primaryKey_;
// There maybe multiple foreign keys for a TableDef forming multiple PK-FK
// relationships.
private List<ForeignKey> foreignKeysList_ = new ArrayList<>();
// Result of analysis.
private TableName fqTableName_;
TableDef(TableName tableName, boolean isExternal, boolean ifNotExists) {
tableName_ = tableName;
isExternal_ = isExternal;
ifNotExists_ = ifNotExists;
dataLayout_ = TableDataLayout.createEmptyLayout();
}
public void reset() {
primaryKeyColDefs_.clear();
columnDefs_.clear();
isAnalyzed_ = false;
generatedKuduProperties_.clear();
}
public TableName getTblName() {
return fqTableName_ != null ? fqTableName_ : tableName_;
}
public String getTbl() { return tableName_.getTbl(); }
public boolean isAnalyzed() { return isAnalyzed_; }
List<ColumnDef> getColumnDefs() { return columnDefs_; }
List<String> getColumnNames() { return ColumnDef.toColumnNames(columnDefs_); }
List<Type> getColumnTypes() {
return columnDefs_.stream().map(col -> col.getType()).collect(Collectors.toList());
}
public void setPrimaryKey(TableDef.PrimaryKey primaryKey_) {
this.primaryKey_ = primaryKey_;
}
List<String> getPartitionColumnNames() {
return ColumnDef.toColumnNames(getPartitionColumnDefs());
}
List<ColumnDef> getPartitionColumnDefs() {
return dataLayout_.getPartitionColumnDefs();
}
boolean isKuduTable() { return options_.fileFormat == THdfsFileFormat.KUDU; }
List<String> getPrimaryKeyColumnNames() { return primaryKeyColNames_; }
List<ColumnDef> getPrimaryKeyColumnDefs() { return primaryKeyColDefs_; }
boolean isExternal() { return isExternal_; }
boolean getIfNotExists() { return ifNotExists_; }
Map<String, String> getGeneratedKuduProperties() { return generatedKuduProperties_; }
void putGeneratedKuduProperty(String key, String value) {
Preconditions.checkNotNull(key);
generatedKuduProperties_.put(key, value);
}
List<KuduPartitionParam> getKuduPartitionParams() {
return dataLayout_.getKuduPartitionParams();
}
void setOptions(Options options) {
Preconditions.checkNotNull(options);
options_ = options;
}
List<String> getSortColumns() { return options_.sortCols; }
String getComment() { return options_.comment; }
Map<String, String> getTblProperties() { return options_.tblProperties; }
HdfsCachingOp getCachingOp() { return options_.cachingOp; }
HdfsUri getLocation() { return options_.location; }
Map<String, String> getSerdeProperties() { return options_.serdeProperties; }
THdfsFileFormat getFileFormat() { return options_.fileFormat; }
RowFormat getRowFormat() { return options_.rowFormat; }
TSortingOrder getSortingOrder() { return options_.sortingOrder; }
List<ForeignKey> getForeignKeysList() { return foreignKeysList_; }
/**
* Analyzes the parameters of a CREATE TABLE statement.
*/
void analyze(Analyzer analyzer) throws AnalysisException {
if (isAnalyzed_) return;
Preconditions.checkState(tableName_ != null && !tableName_.isEmpty());
fqTableName_ = analyzer.getFqTableName(getTblName());
fqTableName_.analyze();
analyzeAcidProperties(analyzer);
analyzeColumnDefs(analyzer);
analyzePrimaryKeys();
analyzeForeignKeys(analyzer);
if (analyzer.dbContainsTable(getTblName().getDb(), getTbl(), Privilege.CREATE)
&& !getIfNotExists()) {
throw new AnalysisException(Analyzer.TBL_ALREADY_EXISTS_ERROR_MSG + getTblName());
}
analyzer.addAccessEvent(new TAccessEvent(fqTableName_.toString(),
TCatalogObjectType.TABLE, Privilege.CREATE.toString()));
Preconditions.checkNotNull(options_);
analyzeOptions(analyzer);
isAnalyzed_ = true;
}
/**
* Analyzes table and partition column definitions, checking whether all column
* names are unique.
*/
private void analyzeColumnDefs(Analyzer analyzer) throws AnalysisException {
Set<String> colNames = new HashSet<>();
for (ColumnDef colDef: columnDefs_) {
colDef.analyze(analyzer);
if (!colNames.add(colDef.getColName().toLowerCase())) {
throw new AnalysisException("Duplicate column name: " + colDef.getColName());
}
if (!isKuduTable() && colDef.hasKuduOptions()) {
throw new AnalysisException(String.format("Unsupported column options for " +
"file format '%s': '%s'", getFileFormat().name(), colDef.toString()));
}
}
for (ColumnDef colDef: getPartitionColumnDefs()) {
colDef.analyze(analyzer);
if (!colDef.getType().supportsTablePartitioning()) {
throw new AnalysisException(
String.format("Type '%s' is not supported as partition-column type " +
"in column: %s", colDef.getType().toSql(), colDef.getColName()));
}
if (!colNames.add(colDef.getColName().toLowerCase())) {
throw new AnalysisException("Duplicate column name: " + colDef.getColName());
}
}
}
/**
* Analyzes the primary key columns. Checks if the specified primary key columns exist
* in the table column definitions and if composite primary keys are properly defined
* using the PRIMARY KEY (col,..col) clause.
*/
private void analyzePrimaryKeys() throws AnalysisException {
for (ColumnDef colDef: columnDefs_) {
if (colDef.isPrimaryKey()) primaryKeyColDefs_.add(colDef);
}
if (primaryKeyColDefs_.size() > 1) {
throw new AnalysisException("Multiple primary keys specified. " +
"Composite primary keys can be specified using the " +
"PRIMARY KEY (col1, col2, ...) syntax at the end of the column definition.");
}
if (primaryKeyColNames_.isEmpty()) {
if (primaryKey_ == null || primaryKey_.getPrimaryKeyColNames().isEmpty()) {
return;
} else {
primaryKeyColNames_.addAll(primaryKey_.getPrimaryKeyColNames());
}
}
if (!primaryKeyColDefs_.isEmpty()) {
throw new AnalysisException("Multiple primary keys specified. " +
"Composite primary keys can be specified using the " +
"PRIMARY KEY (col1, col2, ...) syntax at the end of the column definition.");
}
Map<String, ColumnDef> colDefsByColName = ColumnDef.mapByColumnNames(columnDefs_);
int keySeq = 1;
String constraintName = null;
for (String colName: primaryKeyColNames_) {
colName = colName.toLowerCase();
ColumnDef colDef = colDefsByColName.remove(colName);
if (colDef == null) {
if (ColumnDef.toColumnNames(primaryKeyColDefs_).contains(colName)) {
throw new AnalysisException(String.format("Column '%s' is listed multiple " +
"times as a PRIMARY KEY.", colName));
}
throw new AnalysisException(String.format(
"PRIMARY KEY column '%s' does not exist in the table", colName));
}
if (colDef.isExplicitNullable()) {
throw new AnalysisException("Primary key columns cannot be nullable: " +
colDef.toString());
}
// HDFS Table specific analysis.
if (primaryKey_ != null) {
// We do not support enable and validate for primary keys.
if (primaryKey_.isEnableCstr()) {
throw new AnalysisException("ENABLE feature is not supported yet.");
}
if (primaryKey_.isValidateCstr()) {
throw new AnalysisException("VALIDATE feature is not supported yet.");
}
// All primary keys in a composite key should have the same constraint name. This
// is necessary because of HIVE-16603. See IMPALA-9188 for details.
if (constraintName == null) {
constraintName = generateConstraintName();
}
// Each column of a primary key definition will be an SQLPrimaryKey.
sqlPrimaryKeys_.add(new SQLPrimaryKey(getTblName().getDb(), getTbl(),
colDef.getColName(), keySeq++, constraintName, primaryKey_.enableCstr,
primaryKey_.validateCstr, primaryKey_.relyCstr));
}
primaryKeyColDefs_.add(colDef);
}
}
private void analyzeForeignKeys(Analyzer analyzer) throws AnalysisException {
if (foreignKeysList_ == null || foreignKeysList_.size() == 0) return;
for (ForeignKey fk: foreignKeysList_) {
// Foreign Key and Primary Key columns don't match.
if (fk.getForeignKeyColNames().size() != fk.getPrimaryKeyColNames().size()){
throw new AnalysisException("The number of foreign key columns should be same" +
" as the number of parent key columns.");
}
String parentDb = fk.getPkTableName().getDb();
if (parentDb == null) {
parentDb = analyzer.getDefaultDb();
}
fk.fullyQualifiedPkTableName = new TableName(parentDb, fk.pkTableName.getTbl());
//Check if parent table exits
if (!analyzer.dbContainsTable(parentDb, fk.getPkTableName().getTbl(),
Privilege.VIEW_METADATA)) {
throw new AnalysisException("Parent table not found: "
+ analyzer.getFqTableName(fk.getPkTableName()));
}
//Check for primary key cols in parent table
FeTable parentTable = analyzer.getTable(fk.getPkTableName(),
Privilege.VIEW_METADATA);
if (!(parentTable instanceof FeFsTable)) {
throw new AnalysisException("Foreign keys on non-HDFS parent tables are not "
+ "supported.");
}
for (String pkCol : fk.getPrimaryKeyColNames()) {
// TODO: Check column types of parent table and child tables match. Currently HMS
// API fails if they don't, it's good to fail early during analysis here.
if (!parentTable.getColumnNames().contains(pkCol.toLowerCase())) {
throw new AnalysisException("Parent column not found: " + pkCol.toLowerCase());
}
// Hive has a bug that prevents foreign keys from being added when pk column is
// not part of primary key. This can be confusing. Till this bug is fixed, we
// will not allow foreign keys definition on such columns.
try {
if (!((FeFsTable) parentTable).getPrimaryKeyColumnNames().contains(pkCol)) {
throw new AnalysisException(String.format("Parent column %s is not part of "
+ "primary key.", pkCol));
}
} catch (TException e) {
// In local catalog mode, we do not aggressively load PK/FK information, a
// call to getPrimaryKeyColumnNames() will try to selectively load PK/FK
// information. Hence, TException is thrown only in local catalog mode.
throw new AnalysisException("Failed to get primary key columns for "
+ fk.pkTableName);
}
}
// We do not support ENABLE and VALIDATE.
if (fk.isEnableCstr()) {
throw new AnalysisException("ENABLE feature is not supported yet.");
}
if (fk.isValidateCstr()) {
throw new AnalysisException("VALIDATE feature is not supported yet.");
}
String constraintName = null;
for (int i = 0; i < fk.getForeignKeyColNames().size(); i++) {
if (fk.getFkConstraintName() == null) {
if (i == 0){
constraintName = generateConstraintName();
}
} else {
constraintName = fk.getFkConstraintName();
}
SQLForeignKey sqlForeignKey = new SQLForeignKey();
sqlForeignKey.setPktable_db(parentDb);
sqlForeignKey.setPktable_name(fk.getPkTableName().getTbl());
sqlForeignKey.setFktable_db(getTblName().getDb());
sqlForeignKey.setFktable_name(getTbl());
sqlForeignKey.setPkcolumn_name(fk.getPrimaryKeyColNames().get(i).toLowerCase());
sqlForeignKey.setFk_name(constraintName);
sqlForeignKey.setKey_seq(i+1);
sqlForeignKey.setFkcolumn_name(fk.getForeignKeyColNames().get(i).toLowerCase());
sqlForeignKey.setRely_cstr(fk.isRelyCstr());
getSqlForeignKeys().add(sqlForeignKey);
}
}
}
/**
* Utility method to generate a unique constraint name when user does not specify one.
* TODO: Collisions possible? HMS doesn't have an API to query existing constraint
* names.
*/
private String generateConstraintName() {
return UUID.randomUUID().toString();
}
/**
* Analyzes the list of columns in 'sortCols' against the columns of 'table' and
* returns their matching positions in the table's columns. Each column of 'sortCols'
* must occur in 'table' as a non-partitioning column. 'table' must be an HDFS table.
* If there are errors during the analysis, this will throw an AnalysisException.
*/
public static List<Integer> analyzeSortColumns(List<String> sortCols, FeTable table,
TSortingOrder sortingOrder) throws AnalysisException {
Preconditions.checkState(table instanceof FeFsTable);
List<Type> columnTypes = table.getNonClusteringColumns().stream().map(
col -> col.getType()).collect(Collectors.toList());
return analyzeSortColumns(sortCols,
Column.toColumnNames(table.getNonClusteringColumns()),
Column.toColumnNames(table.getClusteringColumns()), columnTypes, sortingOrder);
}
/**
* Analyzes the list of columns in 'sortCols' and returns their matching positions in
* 'tableCols'. Each column must occur in 'tableCols' and must not occur in
* 'partitionCols'. If there are errors during the analysis, this will throw an
* AnalysisException.
*/
public static List<Integer> analyzeSortColumns(List<String> sortCols,
List<String> tableCols, List<String> partitionCols, List<Type> columnTypes,
TSortingOrder sortingOrder) throws AnalysisException {
// The index of each sort column in the list of table columns.
Set<Integer> colIdxs = new LinkedHashSet<>();
int numColumns = 0;
for (String sortColName: sortCols) {
++numColumns;
// Make sure it's not a partition column.
if (partitionCols.contains(sortColName)) {
throw new AnalysisException(String.format("SORT BY column list must not " +
"contain partition column: '%s'", sortColName));
}
// Determine the index of each sort column in the list of table columns.
boolean foundColumn = false;
for (int j = 0; j < tableCols.size(); ++j) {
if (tableCols.get(j).equalsIgnoreCase(sortColName)) {
if (colIdxs.contains(j)) {
throw new AnalysisException(String.format("Duplicate column in SORT BY " +
"list: %s", sortColName));
}
colIdxs.add(j);
foundColumn = true;
break;
}
}
if (!foundColumn) {
throw new AnalysisException(String.format("Could not find SORT BY column " +
"'%s' in table.", sortColName));
}
}
// Analyzing Z-Order specific constraints
if (sortingOrder == TSortingOrder.ZORDER) {
if (numColumns == 1) {
throw new AnalysisException(String.format("SORT BY ZORDER with 1 column is " +
"equivalent to SORT BY. Please, use the latter, if that was your " +
"intention."));
}
List<? extends Type> notSupportedTypes = Arrays.asList(Type.STRING, Type.VARCHAR,
Type.FLOAT, Type.DOUBLE);
for (Integer position : colIdxs) {
Type colType = columnTypes.get(position);
if (notSupportedTypes.stream().anyMatch(type -> colType.matchesType(type))) {
throw new AnalysisException(String.format("SORT BY ZORDER does not support "
+ "column types: %s", String.join(", ",
notSupportedTypes.stream().map(type -> type.toString())
.collect(Collectors.toList()))));
}
}
}
Preconditions.checkState(numColumns == colIdxs.size());
return Lists.newArrayList(colIdxs);
}
private void analyzeOptions(Analyzer analyzer) throws AnalysisException {
MetaStoreUtil.checkShortPropertyMap("Property", options_.tblProperties);
MetaStoreUtil.checkShortPropertyMap("Serde property", options_.serdeProperties);
if (options_.location != null) {
options_.location.analyze(analyzer, Privilege.ALL, FsAction.READ_WRITE);
}
if (options_.cachingOp != null) {
options_.cachingOp.analyze(analyzer);
if (options_.cachingOp.shouldCache() && options_.location != null &&
!FileSystemUtil.isPathCacheable(options_.location.getPath())) {
throw new AnalysisException(String.format("Location '%s' cannot be cached. " +
"Please retry without caching: CREATE TABLE ... UNCACHED",
options_.location));
}
}
// Analyze 'skip.header.line.format' property.
AlterTableSetTblProperties.analyzeSkipHeaderLineCount(options_.tblProperties);
analyzeRowFormat(analyzer);
String sortByKey = AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS;
String sortOrderKey = AlterTableSortByStmt.TBL_PROP_SORT_ORDER;
if (options_.tblProperties.containsKey(sortByKey)) {
throw new AnalysisException(String.format("Table definition must not contain the " +
"%s table property. Use SORT BY (...) instead.", sortByKey));
}
if (options_.tblProperties.containsKey(sortOrderKey)) {
throw new AnalysisException(String.format("Table definition must not contain the " +
"%s table property. Use SORT BY %s (...) instead.", sortOrderKey,
options_.sortingOrder.toString()));
}
// Analyze sort columns.
if (options_.sortCols == null) return;
if (isKuduTable()) {
throw new AnalysisException(String.format("SORT BY is not supported for Kudu "+
"tables."));
}
analyzeSortColumns(options_.sortCols, getColumnNames(), getPartitionColumnNames(),
getColumnTypes(), options_.sortingOrder);
}
private void analyzeRowFormat(Analyzer analyzer) throws AnalysisException {
if (options_.rowFormat == null) return;
if (isKuduTable()) {
throw new AnalysisException(String.format(
"ROW FORMAT cannot be specified for file format %s.", options_.fileFormat));
}
Byte fieldDelim = analyzeRowFormatValue(options_.rowFormat.getFieldDelimiter());
Byte lineDelim = analyzeRowFormatValue(options_.rowFormat.getLineDelimiter());
Byte escapeChar = analyzeRowFormatValue(options_.rowFormat.getEscapeChar());
if (options_.fileFormat == THdfsFileFormat.TEXT) {
if (fieldDelim == null) fieldDelim = HdfsStorageDescriptor.DEFAULT_FIELD_DELIM;
if (lineDelim == null) lineDelim = HdfsStorageDescriptor.DEFAULT_LINE_DELIM;
if (escapeChar == null) escapeChar = HdfsStorageDescriptor.DEFAULT_ESCAPE_CHAR;
if (fieldDelim.equals(lineDelim)) {
throw new AnalysisException("Field delimiter and line delimiter have same " +
"value: byte " + fieldDelim);
}
if (fieldDelim.equals(escapeChar)) {
analyzer.addWarning("Field delimiter and escape character have same value: " +
"byte " + fieldDelim + ". Escape character will be ignored");
}
if (lineDelim.equals(escapeChar)) {
analyzer.addWarning("Line delimiter and escape character have same value: " +
"byte " + lineDelim + ". Escape character will be ignored");
}
}
}
private Byte analyzeRowFormatValue(String value) throws AnalysisException {
if (value == null) return null;
Byte byteVal = HdfsStorageDescriptor.parseDelim(value);
if (byteVal == null) {
throw new AnalysisException("ESCAPED BY values and LINE/FIELD " +
"terminators must be specified as a single character or as a decimal " +
"value in the range [-128:127]: " + value);
}
return byteVal;
}
/**
* Analyzes Hive ACID related properties.
* Can change table properties based on query options.
*/
private void analyzeAcidProperties(Analyzer analyzer) throws AnalysisException {
if (isExternal_) {
if (AcidUtils.isTransactionalTable(options_.tblProperties)) {
throw new AnalysisException("EXTERNAL tables cannot be transactional");
}
return;
}
if (options_.fileFormat == THdfsFileFormat.KUDU) {
if (AcidUtils.isTransactionalTable(options_.tblProperties)) {
throw new AnalysisException("Kudu tables cannot be transactional");
}
return;
}
AcidUtils.setTransactionalProperties(options_.tblProperties,
analyzer.getQueryOptions().getDefault_transactional_type());
// Disallow creation of full ACID table.
analyzer.ensureTableNotFullAcid(options_.tblProperties, fqTableName_.toString());
}
}