blob: 2d48bec2b6f9440feec1fcd02aaeae1ba50d5574 [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.List;
import org.apache.impala.authorization.Privilege;
import org.apache.impala.catalog.FeTable;
import org.apache.impala.catalog.FeView;
import org.apache.impala.catalog.TableLoadingException;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.thrift.TAccessEvent;
import org.apache.impala.thrift.TCatalogObjectType;
import org.apache.impala.thrift.TDropTableOrViewParams;
import org.apache.impala.thrift.TTableName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
/**
* Represents a DROP TABLE/VIEW [IF EXISTS] statement
*/
public class DropTableOrViewStmt extends StatementBase {
private static final Logger LOG = LoggerFactory.getLogger(DropTableOrViewStmt.class);
protected final TableName tableName_;
protected final boolean ifExists_;
// True if we are dropping a table. False if we are dropping a view.
protected final boolean dropTable_;
// Setting this value causes dropped tables to be permanently
// deleted. For example, for hdfs tables it skips the trash directory
protected final boolean purgeTable_;
// Set during analysis
protected String dbName_;
// Server name needed for privileges. Set during analysis.
private String serverName_;
/**
* Constructor for building the DROP TABLE/VIEW statement
*/
public DropTableOrViewStmt(TableName tableName, boolean ifExists,
boolean dropTable, boolean purgeTable) {
tableName_ = Preconditions.checkNotNull(tableName);
ifExists_ = ifExists;
dropTable_ = dropTable;
purgeTable_ = purgeTable;
// PURGE with a view is not allowed.
Preconditions.checkState(!(!dropTable_ && purgeTable_));
}
@Override
public String toSql(ToSqlOptions options) {
StringBuilder sb = new StringBuilder("DROP " + ((dropTable_) ? "TABLE " : "VIEW "));
if (ifExists_) sb.append("IF EXISTS ");
if (tableName_.getDb() != null) sb.append(tableName_.getDb() + ".");
sb.append(tableName_.getTbl());
if (purgeTable_) sb.append(" PURGE");
return sb.toString();
}
public TDropTableOrViewParams toThrift() {
TDropTableOrViewParams params = new TDropTableOrViewParams();
params.setTable_name(new TTableName(getDb(), getTbl()));
params.setIf_exists(ifExists_);
params.setPurge(purgeTable_);
params.setIs_table(dropTable_);
params.setServer_name(serverName_);
return params;
}
@Override
public void collectTableRefs(List<TableRef> tblRefs) {
tblRefs.add(new TableRef(tableName_.toPath(), null));
}
/**
* 1. Checks that the user has privileges to DROP the given table/view
* 2. Checks that the database and table exists
* 3. Checks that the table type (TABLE/VIEW) matches the DROP TABLE/VIEW statement
* Note: Do not analyze tableName because we prefer to report an error indicating
* that the table/view does not exist even if the table/view name is invalid.
*/
@Override
public void analyze(Analyzer analyzer) throws AnalysisException {
dbName_ = analyzer.getTargetDbName(tableName_);
// Set the servername here if authorization is enabled because analyzer_ is not
// available in the toThrift() method.
serverName_ = analyzer.getServerName();
try {
// Fetch the table owner information, without registering any privileges.
FeTable table = analyzer.getTableNoThrow(dbName_, tableName_.getTbl());
String tblOwnerUser = table == null ? null : table.getOwnerUser();
if (ifExists_) {
// Start with ANY privilege in case of IF EXISTS, and register DROP privilege
// later only if the table exists. See IMPALA-8851 for more explanation.
analyzer.registerPrivReq(builder ->
builder.allOf(Privilege.ANY)
.onTable(dbName_, getTbl(), tblOwnerUser)
.build());
if (table == null) return;
}
// Register the DROP privilege on the table.
table = analyzer.getTable(tableName_, /* add access event */ true,
/* add column-level privilege */ false, Privilege.DROP);
Preconditions.checkNotNull(table);
if (table instanceof FeView && dropTable_) {
// DROP VIEW IF EXISTS 'table' succeeds, similarly to Hive, but unlike postgres.
if (ifExists_) return;
throw new AnalysisException(String.format(
"DROP TABLE not allowed on a view: %s.%s", dbName_, getTbl()));
}
if (!(table instanceof FeView) && !dropTable_) {
// DROP TABLE IF EXISTS 'view' succeeds, similarly to Hive, but unlike postgres.
if (ifExists_) return;
throw new AnalysisException(String.format(
"DROP VIEW not allowed on a table: %s.%s", dbName_, getTbl()));
}
if (dropTable_) {
// To drop a view needs not write capabilities, only checks for tables.
analyzer.checkTableCapability(table, Analyzer.OperationType.WRITE);
analyzer.ensureTableNotFullAcid(table);
}
} catch (TableLoadingException e) {
// We should still try to DROP tables that failed to load, so that tables that are
// in a bad state, eg. deleted externally from Kudu, can be dropped.
// We still need an access event - we don't know if this is a TABLE or a VIEW, so
// we set it as TABLE as VIEW loading is unlikely to fail and even if it does
// TABLE -> VIEW is a small difference.
analyzer.addAccessEvent(new TAccessEvent(
analyzer.getFqTableName(tableName_).toString(), TCatalogObjectType.TABLE,
Privilege.DROP.toString()));
LOG.info("Ignoring TableLoadingException for {}", tableName_);
}
}
/**
* Can only be called after analysis. Returns the name of the database that
* the target drop table resides in.
*/
public String getDb() {
Preconditions.checkNotNull(dbName_);
return dbName_;
}
public String getTbl() { return tableName_.getTbl(); }
public boolean isDropTable() { return dropTable_; }
}