package com.tesora.dve.sql.statement.ddl;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tesora.dve.common.catalog.CatalogEntity;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.lockmanager.LockType;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.parser.TranslatorUtils;
import com.tesora.dve.sql.schema.LockInfo;
import com.tesora.dve.sql.schema.Name;
import com.tesora.dve.sql.schema.PEAbstractTable;
import com.tesora.dve.sql.schema.Persistable;
import com.tesora.dve.sql.schema.QualifiedName;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.UnqualifiedName;
import com.tesora.dve.sql.schema.cache.CacheInvalidationRecord;
import com.tesora.dve.sql.schema.cache.InvalidationScope;
import com.tesora.dve.sql.schema.cache.SchemaCacheKey;
import com.tesora.dve.sql.transform.execution.CatalogModificationExecutionStep.Action;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.ListOfPairs;
import com.tesora.dve.sql.util.Pair;
public class RenameTableStatement extends DDLStatement {
public static final class IntermediateSchema {
private final Map<Name, PEAbstractTable<?>> tableLookupCache = new HashMap<Name, PEAbstractTable<?>>();
private final Map<Name, Boolean> tableStateCache = new HashMap<Name, Boolean>();
private final Map<Name, Name> renameActions = new LinkedHashMap<Name, Name>();
public Map<Name, Name> getRenameActions() {
return Collections.unmodifiableMap(this.renameActions);
}
public void addRenameAction(final SchemaContext sc, final Pair<Name, Name> namePair) {
final Pair<Name, Name> qualifiedNamePair = this.convertToQualifiedNames(sc, namePair);
this.addRenameAction(qualifiedNamePair.getFirst(), qualifiedNamePair.getSecond());
}
public int getNumRenameActions() {
return this.renameActions.size();
}
public void buildRenamePairs(final SchemaContext sc, final ListOfPairs<PEAbstractTable<?>, Name> renamePairs,
final Set<Pair<SchemaCacheKey<?>, InvalidationScope>> uniqueCacheKeys) {
for (final Map.Entry<Name, Name> renameAction : this.renameActions.entrySet()) {
final PEAbstractTable<?> sourceTable = this.tableLookupCache.get(renameAction.getValue());
final Name targetName = renameAction.getKey();
renamePairs.add(sourceTable, targetName);
if (sourceTable.getDatabase(sc) == null) continue; // temporary table, nothing to invalidate
uniqueCacheKeys.add(new Pair<SchemaCacheKey<?>, InvalidationScope>(sourceTable.getDatabase(sc).getCacheKey(), InvalidationScope.CASCADE));
}
}
public void addRenameAction(final Name source, final Name target) {
if (this.renameActions.containsKey(source)) {
final Name previous = this.renameActions.remove(source);
this.renameActions.put(target, previous);
} else {
this.renameActions.put(target, source);
}
}
public void clear() {
this.renameActions.clear();
this.tableStateCache.clear();
this.tableLookupCache.clear();
}
private Pair<Name, Name> convertToQualifiedNames(final SchemaContext sc, final Pair<Name, Name> namePair) {
Name source = namePair.getFirst();
Name target = namePair.getSecond();
final UnqualifiedName sourceDbName = TranslatorUtils.getDatabaseNameForObject(sc, source);
final UnqualifiedName targetDbName = TranslatorUtils.getDatabaseNameForObject(sc, target);
if (!sourceDbName.equals(targetDbName)) {
/* This is a DVE only limitation. */
throw new SchemaException(Pass.FIRST, "Moving tables between databases and persistent groups is not allowed.");
}
/*
* Qualifying the table names should help to avoid future database
* lookups.
*/
if (!source.isQualified()) {
source = toQualifiedTableName(sourceDbName, source.getUnqualified());
}
if (!target.isQualified()) {
target = toQualifiedTableName(targetDbName, target.getUnqualified());
}
cacheSourceTable(sc, source);
cacheTargetTable(sc, target);
return new Pair<Name, Name>(source, target);
}
private void cacheSourceTable(final SchemaContext sc, final Name sourceName) {
if (lookupTable(sc, sourceName)) {
this.tableStateCache.put(sourceName, false);
} else {
throw new SchemaException(Pass.FIRST, "No such table '" + sourceName + "'.");
}
}
private void cacheTargetTable(final SchemaContext sc, final Name targetName) {
if (!lookupTable(sc, targetName)) {
this.tableStateCache.put(targetName, true);
} else {
throw new SchemaException(Pass.FIRST, "Table '" + targetName + "' already exists.");
}
}
private boolean lookupTable(final SchemaContext sc, final Name tableName) {
if (this.tableLookupCache.containsKey(tableName)) {
return this.tableStateCache.get(tableName);
}
final PEAbstractTable<?> table = TranslatorUtils.getTable(sc, tableName, new LockInfo(LockType.EXCLUSIVE, "rename table"));
this.tableLookupCache.put(tableName, table);
final Boolean exists = (table != null);
this.tableStateCache.put(tableName, exists);
return exists;
}
}
public static RenameTableStatement buildRenameTableStatement(final SchemaContext sc, final List<Pair<Name, Name>> sourceTargetNamePairs) {
assert (!sourceTargetNamePairs.isEmpty());
final IntermediateSchema schema = new IntermediateSchema();
for (final Pair<Name, Name> namePair : sourceTargetNamePairs) {
schema.addRenameAction(sc, namePair);
}
final int numRenameActions = schema.getNumRenameActions();
final ListOfPairs<PEAbstractTable<?>, Name> renamePairs = new ListOfPairs<PEAbstractTable<?>, Name>(numRenameActions);
final Set<Pair<SchemaCacheKey<?>, InvalidationScope>> uniqueCacheKeys = new HashSet<Pair<SchemaCacheKey<?>, InvalidationScope>>(numRenameActions);
schema.buildRenamePairs(sc, renamePairs, uniqueCacheKeys);
final CacheInvalidationRecord invalidationRecord = new CacheInvalidationRecord(uniqueCacheKeys);
return new RenameTableStatement(sourceTargetNamePairs, renamePairs, invalidationRecord);
}
private static Name toQualifiedTableName(final UnqualifiedName dbName, final UnqualifiedName tableName) {
return new QualifiedName(dbName, tableName);
}
private static List<CatalogEntity> renameTables(final SchemaContext pc, final List<Pair<PEAbstractTable<?>, Name>> tableNamePairs) throws PEException {
pc.beginSaveContext(true);
try {
for (final Pair<PEAbstractTable<?>, Name> tableNamePair : tableNamePairs) {
final PEAbstractTable<?> table = tableNamePair.getFirst();
final Name newName = tableNamePair.getSecond();
table.persistTree(pc, true);
table.setName(newName.getUnqualified());
}
} finally {
pc.endSaveContext();
}
pc.beginSaveContext(true);
try {
for (final Pair<PEAbstractTable<?>, Name> tableNamePair : tableNamePairs) {
tableNamePair.getFirst().persistTree(pc);
}
return Functional.toList(pc.getSaveContext().getObjects());
} finally {
pc.endSaveContext();
}
}
private final List<Pair<Name, Name>> sourceTargetNamePairs;
private final List<Pair<PEAbstractTable<?>, Name>> tableNamePairs;
private final CacheInvalidationRecord invalidationRecord;
protected RenameTableStatement(final List<Pair<Name, Name>> sourceTargetNamePairs, final List<Pair<PEAbstractTable<?>, Name>> tableNamePairs,
final CacheInvalidationRecord invalidationRecord) {
super(false);
this.sourceTargetNamePairs = sourceTargetNamePairs;
this.tableNamePairs = tableNamePairs;
this.invalidationRecord = invalidationRecord;
}
public List<Pair<Name, Name>> getNamePairs() {
return Collections.unmodifiableList(this.sourceTargetNamePairs);
}
@Override
public List<CatalogEntity> getCatalogObjects(SchemaContext pc) throws PEException {
return renameTables(pc, this.tableNamePairs);
}
@Override
public Action getAction() {
return Action.ALTER;
}
@Override
public Persistable<?, ?> getRoot() {
return this.tableNamePairs.get(0).getFirst();
}
@Override
public CacheInvalidationRecord getInvalidationRecord(SchemaContext sc) {
return this.invalidationRecord;
}
}