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.HashMap;
import java.util.List;
import java.util.Map;
import com.tesora.dve.common.MultiMap;
import com.tesora.dve.common.catalog.TableState;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.schema.PEColumn;
import com.tesora.dve.sql.schema.PEForeignKey;
import com.tesora.dve.sql.schema.PEForeignKeyColumn;
import com.tesora.dve.sql.schema.PEKey;
import com.tesora.dve.sql.schema.PEKeyColumnBase;
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.mt.AdaptiveMTDDLPlannerUtils;
import com.tesora.dve.sql.schema.mt.FKRefMaintainer;
import com.tesora.dve.sql.schema.mt.PETenant;
import com.tesora.dve.sql.schema.mt.TableScope;
import com.tesora.dve.sql.schema.mt.AdaptiveMTDDLPlannerUtils.CreateTableOperation;
import com.tesora.dve.sql.schema.mt.AdaptiveMTDDLPlannerUtils.CreateTenantScope;
import com.tesora.dve.sql.schema.mt.AdaptiveMTDDLPlannerUtils.LateFKFixup;
import com.tesora.dve.sql.schema.mt.AdaptiveMTDDLPlannerUtils.CompositeNestedOperation;
import com.tesora.dve.sql.schema.mt.PETenant.TenantCacheKey;
import com.tesora.dve.sql.schema.validate.ForeignKeyValidateResult;
import com.tesora.dve.sql.schema.validate.ValidateResult;
import com.tesora.dve.sql.transform.behaviors.BehaviorConfiguration;
import com.tesora.dve.sql.transform.execution.ExecutionSequence;
import com.tesora.dve.sql.transform.execution.CatalogModificationExecutionStep.Action;
import com.tesora.dve.sql.util.ListOfPairs;
import com.tesora.dve.sql.util.Pair;
import com.tesora.dve.variables.KnownVariables;
// creating a tenant table
// for all cases we need to add a tvr
// for landlord tenant/private table we need to add the sql and user table
public class PECreateTenantTableStatement extends PECreateTableStatement {
private PETenant onTenant;
private UnqualifiedName logicalName;
private Long autoIncStart;
public PECreateTenantTableStatement(PECreateTableStatement basedOn, PETenant forTenant, Long autoIncOffset, UnqualifiedName logicalName) {
super(basedOn);
onTenant = forTenant;
this.logicalName = logicalName;
autoIncStart = autoIncOffset;
}
public PETenant getTenant() {
return onTenant;
}
public UnqualifiedName getLogicalName() {
return logicalName;
}
protected void checkColocatedFKs(SchemaContext sc) {
PETable tab = getTable();
if (sc.isPersistent()) {
List<ValidateResult> results = tab.validate(sc,false);
// make sure we fail on errors
for(ValidateResult vr : results) {
if (vr.isError()) {
String message = null;
if (vr instanceof ForeignKeyValidateResult) {
// need to map the tables
HashMap<PETable,UnqualifiedName> mapping = new HashMap<PETable,UnqualifiedName>();
mapping.put(tab,logicalName);
PEForeignKey pefk = (PEForeignKey) vr.getSubject();
if (pefk.getTable(sc) != tab) {
TableScope theScope = sc.getPolicyContext().getOfTenant(pefk.getTable(sc).getPersistent(sc));
mapping.put(pefk.getTable(sc),theScope.getName().getUnqualified());
}
if (pefk.getTargetTable(sc) != tab) {
TableScope theScope = sc.getPolicyContext().getOfTenant(pefk.getTargetTable(sc).getPersistent(sc));
mapping.put(pefk.getTargetTable(sc),theScope.getName().getUnqualified());
}
ForeignKeyValidateResult fkvr = (ForeignKeyValidateResult) vr;
fkvr.setMTMapping(mapping);
message = fkvr.getMessage(sc);
} else {
message = vr.getMessage(sc);
}
throw new SchemaException(Pass.NORMALIZE,message);
}
}
}
}
@Override
public void plan(SchemaContext sc, ExecutionSequence es, BehaviorConfiguration config)
throws PEException {
PETable tab = getTable();
// this is probably completely unecessary if the table is well formed and already exists
checkColocatedFKs(sc);
Map<UnqualifiedName,LateFKFixup> setNullFixups = new HashMap<UnqualifiedName,LateFKFixup>();
AdaptiveMTDDLPlannerUtils.handleSetNullActions(sc, es, tab, null, onTenant, setNullFixups);
boolean wellFormed = AdaptiveMTDDLPlannerUtils.isWellFormed(sc, tab, null);
CompositeNestedOperation uno = new CompositeNestedOperation();
CreateTableOperation ncto =
AdaptiveMTDDLPlannerUtils.addCreateTable(sc,es,onTenant,tab,logicalName,
(wellFormed? TableState.SHARED : TableState.FIXED), setNullFixups);
uno.withChange(new CreateTenantScope((TenantCacheKey)onTenant.getCacheKey(), logicalName, autoIncStart, ncto));
TableScope ts = new TableScope(sc, tab, onTenant, autoIncStart, logicalName);
CreateTableFKRefMaintainer disaster =
new CreateTableFKRefMaintainer(onTenant,tab,logicalName,ts,ncto);
disaster.maintain(sc);
disaster.schedule(sc, es, uno);
AdaptiveMTDDLPlannerUtils.addDDLCallback(sc, tab.getPEDatabase(sc), tab.getPersistentStorage(sc), tab, Action.CREATE, es, uno, null);
}
protected MultiMap<TableScope, PEForeignKey> computeRootSet(SchemaContext sc, PETable target) {
boolean required =
KnownVariables.FOREIGN_KEY_CHECKS.getSessionValue(sc.getConnection().getVariableSource()).booleanValue();
// we may have just resolved a foreign key - see if that is the case
List<TableScope> matching = sc.findScopesWithUnresolvedFKsTargeting(onTenant.getDatabase(sc).getName().getUnqualified(), logicalName, onTenant);
// first find the set that matches - that will be the root set of the schema graph
MultiMap<TableScope,PEForeignKey> rootSet = new MultiMap<TableScope,PEForeignKey>();
for(TableScope ts : matching) {
PETable backing = ts.getTable(sc);
for(PEKey pek : backing.getKeys(sc)) {
if (!pek.isForeign()) continue;
PEForeignKey pefk = (PEForeignKey) pek;
if (!pefk.isForward()) continue;
if (pefk.getTargetTableName(sc).equals(logicalName)) {
for(PEKeyColumnBase pekc : pefk.getKeyColumns()) {
PEForeignKeyColumn pefkc = (PEForeignKeyColumn)pekc;
PEColumn tc = target.lookup(sc, pefkc.getTargetColumnName());
if (tc == null && required) {
// should we make this conditional on the var? probably
throw new SchemaException(Pass.NORMALIZE,
"No such column: " + pefkc.getTargetColumnName()
+ " in table " + logicalName
+ " required for foreign key in table " + ts.getName());
}
}
rootSet.put(ts, pefk);
}
}
}
return rootSet;
}
public static class CreateTableFKRefMaintainer extends FKRefMaintainer {
protected PETable newTab;
protected UnqualifiedName logicalName;
protected TableScope theScope;
public CreateTableFKRefMaintainer(PETenant onTenant, PETable nt, UnqualifiedName nn, TableScope ns, CreateTableOperation newCreate) {
super(onTenant);
newTab = nt;
logicalName = nn;
theScope = ns;
forwarding.put(nt,newCreate);
}
@Override
protected void createBlocks(SchemaContext sc) {
modifications.put(newTab, new ModificationBlock(sc,theScope,newTab));
super.createBlocks(sc);
}
@Override
public ListOfPairs<TableScope, TaggedFK> computeRootSet(SchemaContext sc) {
boolean required =
KnownVariables.FOREIGN_KEY_CHECKS.getSessionValue(sc.getConnection().getVariableSource()).booleanValue();
// we may have just resolved a foreign key - see if that is the case
List<TableScope> matching = sc.findScopesWithUnresolvedFKsTargeting(tenant.getDatabase(sc).getName().getUnqualified(), logicalName, tenant);
ListOfPairs<TableScope, TaggedFK> out = new ListOfPairs<TableScope, TaggedFK>();
for(TableScope ts : matching) {
PETable backing = ts.getTable(sc);
for(PEKey pek : backing.getKeys(sc)) {
if (!pek.isForeign()) continue;
PEForeignKey pefk = (PEForeignKey) pek;
if (!pefk.isForward()) continue;
if (pefk.getTargetTableName(sc).equals(logicalName)) {
for(PEKeyColumnBase pekc : pefk.getKeyColumns()) {
PEForeignKeyColumn pefkc = (PEForeignKeyColumn)pekc;
PEColumn tc = newTab.lookup(sc, pefkc.getTargetColumnName());
if (tc == null && required) {
// TODO: should we make this conditional on the var
throw new SchemaException(Pass.NORMALIZE,
"No such column: " + pefkc.getTargetColumnName()
+ " in table " + logicalName
+ " required for foreign key in table " + ts.getName());
}
}
out.add(theScope, new TaggedFK(pefk,backing,ts));
}
}
}
return out;
}
@Override
public void modifyRoot(SchemaContext sc, Pair<TableScope, TaggedFK> r,
Map<PETable, ModificationBlock> blocks) {
PETable target = r.getFirst().getTable(sc);
PEForeignKey pefk = r.getSecond().getFK();
Pair<PEForeignKey,PEKey> nk = AdaptiveMTDDLPlannerUtils.maybeRequiresNewKey(sc, pefk, target);
if (nk != null) {
target.addKey(sc, nk.getSecond(), false);
target.setDeclaration(sc, target);
}
ModificationBlock mb = blocks.get(target);
ModificationBlock emb = blocks.get(r.getSecond().getEnclosing());
emb.resolveFK(sc, blocks, pefk, mb);
}
}
}