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.ArrayList;
import java.util.Collections;
import java.util.List;
import com.tesora.dve.common.catalog.CatalogDAO;
import com.tesora.dve.common.catalog.CatalogEntity;
import com.tesora.dve.db.DBResultConsumer;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.lockmanager.LockType;
import com.tesora.dve.queryplan.ExecutionState;
import com.tesora.dve.queryplan.QueryStepOperation;
import com.tesora.dve.queryplan.QueryStepDDLGeneralOperation.DDLCallback;
import com.tesora.dve.queryplan.QueryStepDDLNestedOperation.NestedOperationDDLCallback;
import com.tesora.dve.server.connectionmanager.SSConnection;
import com.tesora.dve.server.messaging.SQLCommand;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.expression.TableKey;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.schema.DistributionVector;
import com.tesora.dve.sql.schema.LockInfo;
import com.tesora.dve.sql.schema.PEColumn;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.UnqualifiedName;
import com.tesora.dve.sql.schema.UnresolvedRangeDistributionVector;
import com.tesora.dve.sql.schema.PEAbstractTable.TableCacheKey;
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.schema.modifiers.AutoincTableModifier;
import com.tesora.dve.sql.statement.ddl.alter.AlterTableAction;
import com.tesora.dve.sql.statement.ddl.alter.RenameTableAction;
import com.tesora.dve.sql.statement.dml.AliasInformation;
import com.tesora.dve.sql.statement.dml.InsertIntoSelectStatement;
import com.tesora.dve.sql.statement.dml.ProjectingStatement;
import com.tesora.dve.sql.statement.dml.SelectStatement;
import com.tesora.dve.sql.transform.behaviors.BehaviorConfiguration;
import com.tesora.dve.sql.transform.execution.ComplexDDLExecutionStep;
import com.tesora.dve.sql.transform.execution.ExecutionSequence;
import com.tesora.dve.sql.transform.execution.CatalogModificationExecutionStep.Action;
import com.tesora.dve.sql.transform.execution.IdentityConnectionValuesMap;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.ListOfPairs;
import com.tesora.dve.sql.util.UnaryFunction;
import com.tesora.dve.worker.WorkerGroup;
// we use a multistep plan for this. the steps are:
// create a mangled name based on the existing table name, call this newmang
// create a new table like old table named newmang
// insert into newmang select * from table for update
// drop table
// rename newmang to table name
//
// of course, if the new dist vect is the same as the old dist vect, do nothing
// also, eventually we will need to take a lock - probably before the first create
//
// for now - if there are fks referencing this table - don't allow the alter
// likewise if this table has fks - don't allow the alter
// we can relax this restriction later
public class AlterTableDistributionStatement extends PEAlterStatement<PETable> {
CacheInvalidationRecord record;
DistributionVector nv;
public AlterTableDistributionStatement(SchemaContext sc, PETable target,
DistributionVector inv) {
super(target, true);
record = new CacheInvalidationRecord(target.getCacheKey(),InvalidationScope.CASCADE);
if (inv instanceof UnresolvedRangeDistributionVector) {
UnresolvedRangeDistributionVector unres = (UnresolvedRangeDistributionVector) inv;
inv = unres.resolve(sc, target.getPersistentStorage(sc));
}
this.nv = inv;
}
public DistributionVector getNewVector() {
return nv;
}
@Override
protected PETable modify(SchemaContext sc, PETable backing) throws PEException {
// TODO Auto-generated method stub
return null;
}
@Override
public CacheInvalidationRecord getInvalidationRecord(SchemaContext sc) {
return record;
}
@Override
public void normalize(SchemaContext sc) {
super.normalize(sc);
// check for fks within target, also fks referencing target - we disallow this for now
PETable targ = getTarget();
if (!targ.getForeignKeys(sc).isEmpty()) {
throw new SchemaException(Pass.NORMALIZE, "No current support for altering distribution of table with foreign keys");
}
if (!targ.getReferencingTables().isEmpty()) {
throw new SchemaException(Pass.NORMALIZE, "No current support for altering distribution of table with referring foreign keys");
}
if (nv.isContainer())
// we mostly can't do this unless there is a tenant on the connection
throw new SchemaException(Pass.NORMALIZE, "No current support for altering a table into a container");
if (targ.getDistributionVector(sc).isContainer())
throw new SchemaException(Pass.NORMALIZE, "No current support for altering the distribution of a table in a container");
}
// we have our own stmt because we have nontrivial planning involved
@Override
public void plan(SchemaContext sc, ExecutionSequence es, BehaviorConfiguration config) throws PEException {
normalize(sc);
// the dist vect is already in the appropriate context, let's get to work
// only one ddl step can actually be executed using the current
PETable oldTab = getTarget();
TableKey oldTableKey = TableKey.make(sc, oldTab,sc.getNextTable());
long now = System.currentTimeMillis();
String mangledNewName = oldTab.getName().getUnquotedName().get() + now;
PETable newTab = oldTab.recreate(sc, oldTab.getDeclaration(), new LockInfo(LockType.EXCLUSIVE, "alter distribution"));
newTab.setName(new UnqualifiedName(mangledNewName));
DistributionVector adv = nv.adapt(sc, newTab);
newTab.setDistributionVector(sc, adv);
if (oldTab.hasAutoInc()) {
// figure out the autoinc value, copy it over
long nextVal = oldTableKey.readAutoIncrBlock(sc);
if (nextVal > 1)
newTab.getModifiers().setModifier(new AutoincTableModifier(nextVal));
}
newTab.setDeclaration(sc, newTab);
TableKey newTableKey = TableKey.make(sc,newTab,sc.getNextTable());
// new table in hand, I can create it
PECreateTableStatement createNew = new PECreateTableStatement(newTab,false);
createNew.plan(sc,es, config);
// a callback here to do the actual copy
es.append(new ComplexDDLExecutionStep(oldTab.getPEDatabase(sc),oldTab.getStorageGroup(sc),
null,Action.ALTER,new CopyTable((TableCacheKey)oldTab.getCacheKey(),
(TableCacheKey)PETable.getTableKey(oldTab.getPEDatabase(sc), newTab.getName()))));
@SuppressWarnings("unchecked")
PEDropTableStatement dropTab = new PEDropTableStatement(sc,Collections.singletonList(oldTableKey),Collections.EMPTY_LIST,null,oldTableKey.isUserlandTemporaryTable());
dropTab.plan(sc,es, config);
AlterTableAction ata = new RenameTableAction(oldTab.getName(),false);
PEAlterTableStatement alter = new PEAlterTableStatement(sc,newTableKey,Collections.singletonList(ata));
es.append(new ComplexDDLExecutionStep(oldTab.getPEDatabase(sc),oldTab.getStorageGroup(sc),
null,Action.ALTER,new RenameTable(sc,alter)));
}
private static class CopyTable extends NestedOperationDDLCallback {
TableCacheKey otck;
TableCacheKey ntck;
List<QueryStepOperation> dml;
CacheInvalidationRecord record;
public CopyTable(TableCacheKey oldTable, TableCacheKey newTable) {
otck = oldTable;
ntck = newTable;
ListOfPairs<SchemaCacheKey<?>,InvalidationScope> pairs = new ListOfPairs<SchemaCacheKey<?>,InvalidationScope>();
pairs.add(otck,InvalidationScope.CASCADE);
pairs.add(ntck,InvalidationScope.CASCADE);
record = new CacheInvalidationRecord(pairs);
}
@Override
public void inTxn(SSConnection conn, WorkerGroup wg) throws PEException {
// just create a new schema context
SchemaContext sc = SchemaContext.createContext(conn);
sc.forceMutableSource();
// lookup both tables
PETable oldTab = sc.getSource().find(sc, otck).asTable();
PETable newTab = sc.getSource().find(sc, ntck).asTable();
final TableInstance oti = new TableInstance(oldTab,oldTab.getName(),
new UnqualifiedName("copyfrom"),sc.getNextTable(),true);
final TableInstance nti = new TableInstance(newTab,newTab.getName(),
new UnqualifiedName("copyto"),sc.getNextTable(),true);
List<ExpressionNode> proj = Functional.apply(oldTab.getColumns(sc),new UnaryFunction<ExpressionNode,PEColumn>() {
@Override
public ExpressionNode evaluate(PEColumn object) {
return new ColumnInstance(object,oti);
}
});
List<ExpressionNode> nproj = Functional.apply(newTab.getColumns(sc), new UnaryFunction<ExpressionNode,PEColumn>() {
@Override
public ExpressionNode evaluate(PEColumn object) {
return new ColumnInstance(object,nti);
}
});
SelectStatement ss = new SelectStatement(new AliasInformation())
.setTables(oti).setProjection(proj).setLocking();
ss.getDerivedInfo().addLocalTable(oti.getTableKey());
InsertIntoSelectStatement iiss = new InsertIntoSelectStatement(
nti,
nproj,ss,false,
null,
new AliasInformation(),
null);
iiss.getDerivedInfo().addNestedStatements(Collections.singleton((ProjectingStatement)ss));
iiss.getDerivedInfo().addLocalTable(nti.getTableKey());
iiss.normalize(sc);
ExecutionSequence es = new ExecutionSequence(null);
iiss.plan(sc,es, sc.getBehaviorConfiguration());
List<QueryStepOperation> steps = new ArrayList<QueryStepOperation>();
es.schedule(null, steps, null, sc, new IdentityConnectionValuesMap(sc.getValues()),null);
dml = steps;
}
@Override
public boolean canRetry(Throwable t) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean requiresFreshTxn() {
// I don't think we want to retry this
return false;
}
@Override
public String description() {
return "CopyTable@" + System.identityHashCode(this) + " from " + otck.toString() + " to " + ntck.toString();
}
@Override
public CacheInvalidationRecord getInvalidationRecord() {
return record;
}
@Override
public void executeNested(ExecutionState execState, WorkerGroup wg, DBResultConsumer resultConsumer) throws Throwable {
if (dml != null) {
for (QueryStepOperation qso : dml) {
qso.executeSelf(execState, wg, resultConsumer);
}
}
}
@Override
public boolean requiresWorkers() {
return true;
}
}
private static class RenameTable extends DDLCallback {
protected PEAlterTableStatement modifier;
protected List<CatalogEntity> deletes = null;
protected List<CatalogEntity> updates = null;
protected SQLCommand sql = SQLCommand.EMPTY;
protected final SchemaContext context;
public RenameTable(SchemaContext sc, PEAlterTableStatement orig) {
modifier = orig;
context = sc;
}
@Override
public List<CatalogEntity> getUpdatedObjects() throws PEException {
return updates;
}
@Override
public List<CatalogEntity> getDeletedObjects() throws PEException {
return deletes;
}
@Override
public SQLCommand getCommand(CatalogDAO c) {
if (c == null)
return modifier.getSQLCommand(context);
else
return sql;
}
@Override
public void beforeTxn(SSConnection ssConn, CatalogDAO c, WorkerGroup wg) throws PEException {
deletes = null;
updates = null;
}
@Override
public void inTxn(SSConnection conn, WorkerGroup wg) throws PEException {
SchemaContext cntxt = SchemaContext.createContext(conn,context);
cntxt.forceMutableSource();
modifier.refresh(cntxt);
sql = modifier.getSQLCommand(cntxt);
updates = modifier.getCatalogObjects(cntxt);
deletes = Collections.emptyList();
}
@Override
public boolean canRetry(Throwable t) {
return false;
}
@Override
public String description() {
return System.identityHashCode(this) + "@RenameTable: " + modifier.getSQL(context);
}
@Override
public CacheInvalidationRecord getInvalidationRecord() {
return modifier.getInvalidationRecord(context);
}
@Override
public boolean requiresFreshTxn() {
return true;
}
@Override
public boolean requiresWorkers() {
return true;
}
}
}