package com.tesora.dve.sql.schema.mt;
/*
* #%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.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tesora.dve.db.DBNative;
import com.tesora.dve.singleton.Singletons;
import org.apache.log4j.Logger;
import com.tesora.dve.common.MultiMap;
import com.tesora.dve.common.PEConstants;
import com.tesora.dve.common.catalog.MultitenantMode;
import com.tesora.dve.common.catalog.TableVisibility;
import com.tesora.dve.common.catalog.UserTable;
import com.tesora.dve.errmap.AvailableErrors;
import com.tesora.dve.errmap.ErrorInfo;
import com.tesora.dve.lockmanager.LockType;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.expression.ColumnKey;
import com.tesora.dve.sql.expression.ExpressionUtils;
import com.tesora.dve.sql.expression.MTTableKey;
import com.tesora.dve.sql.expression.TableKey;
import com.tesora.dve.sql.node.Edge;
import com.tesora.dve.sql.node.LanguageNode;
import com.tesora.dve.sql.node.Traversal;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.FunctionCall;
import com.tesora.dve.sql.node.expression.LiteralExpression;
import com.tesora.dve.sql.node.expression.MTTableInstance;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.node.structural.FromTableReference;
import com.tesora.dve.sql.node.structural.JoinedTable;
import com.tesora.dve.sql.node.test.EngineConstant;
import com.tesora.dve.sql.schema.Database;
import com.tesora.dve.sql.schema.ForeignKeyAction;
import com.tesora.dve.sql.schema.FunctionName;
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.PEColumn;
import com.tesora.dve.sql.schema.PEDatabase;
import com.tesora.dve.sql.schema.PEForeignKey;
import com.tesora.dve.sql.schema.PEKey;
import com.tesora.dve.sql.schema.PESchema;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.SchemaPolicyContext;
import com.tesora.dve.sql.schema.StructuralUtils;
import com.tesora.dve.sql.schema.UnqualifiedName;
import com.tesora.dve.sql.schema.cache.SchemaEdge;
import com.tesora.dve.sql.statement.EmptyStatement;
import com.tesora.dve.sql.statement.Statement;
import com.tesora.dve.sql.statement.ddl.PEAlterTableStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterTenantTableStatement;
import com.tesora.dve.sql.statement.ddl.PECreateTableAsSelectStatement;
import com.tesora.dve.sql.statement.ddl.PECreateTableStatement;
import com.tesora.dve.sql.statement.ddl.PECreateTenantTableStatement;
import com.tesora.dve.sql.statement.ddl.PEDropTableStatement;
import com.tesora.dve.sql.statement.ddl.PEDropTenantTableStatement;
import com.tesora.dve.sql.statement.ddl.alter.AddIndexAction;
import com.tesora.dve.sql.statement.ddl.alter.AlterTableAction;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.dml.DeleteStatement;
import com.tesora.dve.sql.statement.dml.InsertIntoValuesStatement;
import com.tesora.dve.sql.statement.dml.ProjectingStatement;
import com.tesora.dve.sql.statement.dml.InsertIntoValuesStatement.ValueHandler;
import com.tesora.dve.sql.transform.execution.ExecutionType;
import com.tesora.dve.sql.util.ListSet;
public class AdaptiveMultitenantSchemaPolicyContext extends SchemaPolicyContext {
public static AdaptiveMultitenantSchemaPolicyContext build(SchemaContext sc, PEDatabase db) {
MultitenantMode mm = db.getMTMode();
if (mm == null)
throw new SchemaException(Pass.FIRST, "Missing multitenant mode");
else if (MultitenantMode.ADAPTIVE == mm)
return new AdaptiveMultitenantSchemaPolicyContext(sc);
else
throw new SchemaException(Pass.FIRST, "Unknown multitenant mode: " + mm.describe());
}
Logger logger = Logger.getLogger(AdaptiveMultitenantSchemaPolicyContext.class);
public static final String TENANT_COLUMN = "___mtid";
public static final UnqualifiedName tenantColumnName = new UnqualifiedName(TENANT_COLUMN);
public static final String LANDLORD_TENANT = PEConstants.LANDLORD_TENANT;
protected SchemaEdge<PEDatabase> mtdb;
protected SchemaEdge<IPETenant> tenant;
// when this is true, there is no tenant, but we are still in multitenant mode
// when it is null, we haven't computed it yet
protected Boolean landlord;
public AdaptiveMultitenantSchemaPolicyContext(SchemaContext cntxt) {
super(cntxt);
mtdb = null;
landlord = null;
}
@Override
public boolean isSchemaTenant() {
return getCurrentTenant(false) != null;
}
@Override
public TableScope getOfTenant(UserTable ut) {
PETenant currentTenant = getCurrentTenant(false);
if (currentTenant == null) return null;
TableVisibility tv = sc.getCatalog().findVisibilityRecord(ut.getId(),currentTenant.getPersistentID());
if (tv == null) return null;
return TableScope.load(tv, sc);
}
@Override
public Name getLocalName(UserTable ut) {
PETenant currentTenant = getCurrentTenant(false);
if (currentTenant == null) return super.getLocalName(ut);
TableVisibility tv = sc.getCatalog().findVisibilityRecord(ut.getId(),currentTenant.getPersistentID());
if (tv == null || tv.getLocalName() == null) return super.getLocalName(ut);
return new UnqualifiedName(tv.getLocalName());
}
protected void checkAllowedLandlord(String what) {
if (landlord == null) getCurrentTenant(false);
if (!landlord) return;
throw new SchemaException(Pass.SECOND, "Invalid landlord operation (use <db> first): " + what);
}
@Override
public Database<?> getCurrentDatabase(Database<?> peds, boolean mustExist) {
if (isRoot())
return peds;
getCurrentTenant(true);
return peds;
}
@Override
public boolean requiresMTRewrites(ExecutionType et) {
if (ExecutionType.INSERT == et) {
return true;
} else {
return (getCurrentTenant(false) != null);
}
}
@Override
public Long getTenantID(boolean mustExist) {
PETenant pet = getCurrentTenant(mustExist);
if (mustExist && pet == null)
throw new SchemaException(new ErrorInfo(AvailableErrors.NO_DATABASE_SELECTED));
if (pet == null) return null;
return pet.getTenantID();
}
@Override
public void removeValue(TableScope ts, long value) {
sc.getCatalog().removeNextIncrementValue(sc, ts, value);
}
@SuppressWarnings("unchecked")
protected PEDatabase getMultitenantDB() {
if (mtdb == null)
mtdb = StructuralUtils.buildEdge(sc, sc.findSingleMTDatabase(), true);
return mtdb.get(sc);
}
protected PETenant getCurrentTenant(boolean mustExist) {
if (Boolean.TRUE.equals(landlord)) return null;
IPETenant candidate = null;
if (tenant != null) candidate = tenant.get(sc);
if (candidate == null) {
tenant = sc.getCurrentTenant();
if (tenant != null)
candidate = tenant.get(sc);
}
// figure out if this is landlord mode. it is if the db is set but the tenant is not.
if (candidate == null && sc.hasCurrentDatabase()) {
landlord = true;
return null;
}
if (candidate == null && mustExist) {
if (logger.isDebugEnabled())
logger.debug("no current tenant");
throw new SchemaException(Pass.SECOND, "No database selected");
}
if (candidate != null) landlord = false;
return (PETenant)candidate;
}
@Override
public PETenant getCurrentTenant() {
return getCurrentTenant(false);
}
@Override
public void modifyTablePart(PETable nt) {
addTenantColumn(nt,tenantColumnName);
modifyUniqueKeys(nt);
}
private void modifyUniqueKeys(PETable nt) {
if (getSchemaContext().getOptions().isOmitTenantColumnInjection()) return;
TenantColumn tc = nt.getTenantColumn(getSchemaContext());
for(PEKey k : nt.getKeys(getSchemaContext()))
modifyKey(k, tc);
}
private void modifyKey(PEKey k, TenantColumn tc) {
if (!k.containsColumn(tc)) {
if (k.isForeign()) {
PEForeignKey pefk = (PEForeignKey) k;
if (pefk.getDeleteAction() != ForeignKeyAction.SET_NULL && pefk.getUpdateAction() != ForeignKeyAction.SET_NULL)
pefk.addColumn(0, tc, tenantColumnName);
} else {
k.addColumn(0, tc);
}
}
}
protected void ensureTenantColumn(PEAlterTableStatement in) {
PETable targ = in.getTarget();
TenantColumn tc = targ.getTenantColumn(getSchemaContext());
for(AlterTableAction aa : in.getActions()) {
if (aa instanceof AddIndexAction) {
AddIndexAction aia = (AddIndexAction) aa;
PEKey ntc = aia.getNewIndex();
modifyKey(ntc,tc);
}
}
}
public Statement maybePassthroughAlterStatement(PEAlterTableStatement in) {
AlterTableAction passthrough = null;
AlterTableAction actionable = null;
for(AlterTableAction aa : in.getActions()) {
if (aa.isPassthrough())
passthrough = aa;
else
actionable = aa;
}
String passthroughSQL = null;
if (passthrough != null) {
StringBuilder buf = new StringBuilder();
Singletons.require(DBNative.class).getEmitter().emitAlterAction(getSchemaContext(),
getSchemaContext().getValues(),passthrough, buf);
passthroughSQL = buf.toString();
}
if (passthrough != null && actionable != null) {
StringBuilder buf = new StringBuilder();
buf.append("Currently unable to execute ");
Singletons.require(DBNative.class).getEmitter().emitAlterAction(getSchemaContext(),
getSchemaContext().getValues(),
actionable, buf);
buf.append(" and ").append(passthroughSQL).append(" in the same alter statement");
throw new SchemaException(Pass.PLANNER, buf.toString());
}
if (passthrough != null) {
return new EmptyStatement(passthroughSQL);
}
return null;
}
@Override
public boolean showTenantColumn() {
return getTenantID(false) == null;
}
public void applyDegenerateMultitenantFilter(DMLStatement in, LiteralExpression tenantID) {
// first, see if we have any nested queries
ListSet<ProjectingStatement> nested = EngineConstant.NESTED.getValue(in,getSchemaContext());
if (nested != null) {
// rewrite all children that have tables.
for(ProjectingStatement ss : nested) {
ListSet<TableKey> embedTables = EngineConstant.TABLES.getValue(ss,getSchemaContext());
if (embedTables == null || embedTables.isEmpty()) continue;
applyMultitenantFilter(ss,tenantID);
}
}
ListSet<TableKey> parentTables = EngineConstant.TABLES.getValue(in,getSchemaContext());
if (parentTables == null || parentTables.isEmpty()) return;
applyMultitenantFilter(in,tenantID);
}
// the degenerate multitenant filter does:
// figure out all base tables in the from clause.
// figure out all nonbase tables in the from clause.
// add a tenant column equijoin to all explicit joins. this covers the nonbase tables.
// in the where clause, add a filter on all base tables.
// due to non equijoin join conditions we have to be complete.
@Override
public void applyDegenerateMultitenantFilter(DMLStatement in) {
if (!requiresMTRewrites(in.getExecutionType()) || landlord || (in instanceof InsertIntoValuesStatement))
return;
applyDegenerateMultitenantFilter(in,getTenantIDLiteral(true));
}
private void applyMultitenantFilter(DMLStatement in, LiteralExpression tenantID) {
// does not apply if there is no where clause
Edge<?, LanguageNode> wce = EngineConstant.WHERECLAUSE.getEdge(in);
if (wce == null)
return;
ListSet<TableInstance> baseTables = new ListSet<TableInstance>();
Edge<?,? extends LanguageNode> fromClause = EngineConstant.FROMCLAUSE.getEdge(in);
for(LanguageNode ln : fromClause.getMulti()) {
if (ln instanceof TableInstance)
baseTables.add((TableInstance)ln);
else if (ln instanceof FromTableReference) {
FromTableReference ftr = (FromTableReference) ln;
if (ftr.getBaseTable() != null)
baseTables.add(ftr.getBaseTable());
}
}
Edge<?,?> fcedge = EngineConstant.FROMCLAUSE.getEdge(in);
ModifyJoinExTraversal joinEx = new ModifyJoinExTraversal(getSchemaContext(),tenantID);
joinEx.traverse(fcedge);
// now the from clause is modified. for every base table add a where clause filter on the tenant id.
ExpressionNode cwc = (ExpressionNode) wce.get();
ArrayList<ExpressionNode> filters = new ArrayList<ExpressionNode>();
ListSet<TableKey> notYetSpecified = new ListSet<TableKey>();
ListSet<TableKey> uniqueTables = new ListSet<TableKey>();
for(TableInstance ti : baseTables)
uniqueTables.add(ti.getTableKey());
uniqueTables.addAll(joinEx.getRequiresWhereClause());
if (cwc != null) {
cwc.setGrouped();
filters.add(cwc);
// see what's already specified.
TenantColumnFullySpecifiedTraversal tcfs = new TenantColumnFullySpecifiedTraversal(getSchemaContext(),uniqueTables);
tcfs.traverse(cwc);
notYetSpecified = tcfs.getUnfiltered();
} else {
notYetSpecified = uniqueTables;
}
for(TableKey tk : notYetSpecified) {
PEAbstractTable<?> tab = tk.getAbstractTable();
if (tab.isTempTable())
continue;
PEColumn tc = tab.getTenantColumn(getSchemaContext());
FunctionCall fc = new FunctionCall(FunctionName.makeEquals(),
new ColumnInstance(tc,tk.toInstance()),
(ExpressionNode)tenantID.copy(null));
filters.add(fc);
}
if (filters.size() == 0)
return;
if (filters.size() == 1)
wce.set(filters.get(0));
else {
FunctionCall nwc = new FunctionCall(FunctionName.makeAnd(),filters);
wce.set(nwc);
}
}
private static class ModifyJoinExTraversal extends Traversal {
private final ListSet<TableKey> requiresWC;
private final SchemaContext context;
private final ExpressionNode tenantID;
public ModifyJoinExTraversal(SchemaContext sc, ExpressionNode tenid) {
super(Order.POSTORDER, ExecStyle.ONCE);
requiresWC = new ListSet<TableKey>();
context = sc;
tenantID = tenid;
}
public ListSet<TableKey> getRequiresWhereClause() {
return requiresWC;
}
@Override
public LanguageNode action(LanguageNode in) {
if (in instanceof JoinedTable) {
JoinedTable jt = (JoinedTable) in;
// so if the original is of the form lhs = constant (i.e. an explicit join to a particular value)
// we won't have an lhs and an rhs - but we still need to bound the side that is not a constant
ListSet<ExpressionNode> parts = ExpressionUtils.decomposeAndClause(jt.getJoinOn());
ListSet<ExpressionNode> extras = new ListSet<ExpressionNode>();
HashSet<TableKey> already = new HashSet<TableKey>();
// each part is either of the form column = column or column = constant, or constant = column
// for the last two, rewrite to column = constant and tenantcolumn = tenantid
// for the first rewrite to column = column and tenantcolumn = tenantcolumn
// if in the first case one of the sides doesn't have a tenant column, specify it in the other
// case anyways
for(ExpressionNode c : parts) {
if (EngineConstant.FUNCTION.has(c, EngineConstant.EQUALS)) {
FunctionCall fc = (FunctionCall) c;
ExpressionNode lhs = fc.getParametersEdge().get(0);
ExpressionNode rhs = fc.getParametersEdge().get(1);
ColumnInstance lhc = null;
ColumnInstance rhc = null;
TableKey ltk = null;
TableKey rtk = null;
if (lhs instanceof ColumnInstance) {
lhc = (ColumnInstance) lhs;
ltk = lhc.getTableInstance().getTableKey();
}
if (rhs instanceof ColumnInstance) {
rhc = (ColumnInstance) rhs;
rtk = rhc.getTableInstance().getTableKey();
}
if (lhc != null && rhc != null) {
PEColumn ltc = lhc.getTableInstance().getAbstractTable().getTenantColumn(context);
PEColumn rtc = rhc.getTableInstance().getAbstractTable().getTenantColumn(context);
if (ltc != null && rtc != null) {
extras.add(new FunctionCall(FunctionName.makeEquals(),
new ColumnInstance(ltc,lhc.getTableInstance()),
new ColumnInstance(rtc,rhc.getTableInstance())));
already.add(ltk);
already.add(rtk);
} else if (ltc != null && !already.contains(ltk)) {
extras.add(new FunctionCall(FunctionName.makeEquals(),
new ColumnInstance(ltc,lhc.getTableInstance()),
(ExpressionNode)tenantID.copy(null)));
already.add(ltk);
} else if (rtc != null && !already.contains(rtk)) {
extras.add(new FunctionCall(FunctionName.makeEquals(),
new ColumnInstance(rtc,rhc.getTableInstance()),
(ExpressionNode)tenantID.copy(null)));
already.add(rtk);
}
} else if (lhc != null) {
PEColumn ltc = lhc.getTableInstance().getAbstractTable().getTenantColumn(context);
if (ltc != null && !already.contains(ltk)) {
extras.add(new FunctionCall(FunctionName.makeEquals(),
new ColumnInstance(ltc,lhc.getTableInstance()),
(ExpressionNode)tenantID.copy(null)));
already.add(ltk);
}
} else if (rhc != null) {
PEColumn rtc = rhc.getTableInstance().getAbstractTable().getTenantColumn(context);
if (rtc != null && !already.contains(rtk)) {
extras.add(new FunctionCall(FunctionName.makeEquals(),
new ColumnInstance(rtc,rhc.getTableInstance()),
(ExpressionNode)tenantID.copy(null)));
already.add(rtk);
}
}
}
}
if (extras.isEmpty())
return in;
extras.addAll(parts);
ExpressionNode newClause = ExpressionUtils.safeBuildAnd(extras);
jt.getJoinOnEdge().set(newClause);
}
return in;
}
}
private static class TenantColumnFullySpecifiedTraversal extends Traversal {
private final Map<ColumnKey, TableKey> givenColumns;
private final Set<ColumnKey> found;
private final Set<ColumnKey> used;
private MultiMap<LanguageNode, ColumnKey> filtered;
private boolean done = false;
public TenantColumnFullySpecifiedTraversal(SchemaContext pc, ListSet<TableKey> tabs) {
super(Order.POSTORDER, ExecStyle.ONCE);
givenColumns = new LinkedHashMap<ColumnKey, TableKey>();
for(TableKey tk : tabs)
givenColumns.put(new ColumnKey(tk, tk.getAbstractTable().getTenantColumn(pc)), tk);
found = new HashSet<ColumnKey>();
found.addAll(givenColumns.keySet());
filtered = new MultiMap<LanguageNode, ColumnKey>();
used = new HashSet<ColumnKey>();
}
public ListSet<TableKey> getUnfiltered() {
// the stuff remaining is the key set from givenColumns - found.
// recall that cks leave found if they are not amongst deps at some level, so what's left is only stuff
// that is fully filtered - but we don't care about that (but make sure the thing was used at all)
for(ColumnKey ck : found) {
if (used.contains(ck))
givenColumns.remove(ck);
}
Collection<TableKey> tabs = givenColumns.values();
ListSet<TableKey> out = new ListSet<TableKey>();
out.addAll(tabs);
return out;
}
@Override
public LanguageNode action(LanguageNode in) {
// if all the watched columns are lost, we're done
if (done || found.isEmpty()) return in;
if (EngineConstant.COLUMN.has(in)) {
ColumnInstance ci = (ColumnInstance) in;
if (givenColumns.containsKey(ci.getColumnKey())) {
filtered.put(in, ci.getColumnKey());
used.add(ci.getColumnKey());
}
} else if (EngineConstant.FUNCTION.has(in)) {
FunctionCall fc = (FunctionCall) in;
// if this is a not function, we're done - for safety we have to slap everything on.
// if this is an or function, we have to slap only those columns which are not represented in
// every branch.
// for any other function, propagate upwards.
if (fc.getFunctionName().isEffectiveNot()) {
done = true;
return in;
} else if (fc.getFunctionName().isOr()) {
HashSet<ColumnKey> intersectedDeps = new HashSet<ColumnKey>();
boolean first = true;
for(LanguageNode ln : fc.getParameters()) {
Collection<ColumnKey> deps = filtered.get(ln);
if (deps == null || deps.isEmpty()) continue;
if (EngineConstant.CONSTANT.has(ln)) continue;
for(Iterator<ColumnKey> iter = found.iterator(); iter.hasNext();) {
if (!deps.contains(iter.next()))
iter.remove();
}
if (first) {
intersectedDeps.addAll(deps);
} else {
intersectedDeps.retainAll(deps);
}
}
filtered.putAll(in, intersectedDeps);
} else {
// any others, set all child deps on this node
HashSet<ColumnKey> deps = new HashSet<ColumnKey>();
if (fc.getParameters().isEmpty())
filtered.putAll(in, givenColumns.keySet());
for(LanguageNode ln : fc.getParameters()) {
Collection<ColumnKey> subdeps = filtered.get(ln);
if (subdeps == null || subdeps.isEmpty()) continue;
deps.addAll(subdeps);
}
filtered.putAll(in, deps);
}
}
return in;
}
}
@Override
public boolean allowTenantColumnDeclaration() {
return allowTenantColumnDeclarationChecking();
}
@Override
public TableInstance buildInCurrentTenant(PESchema schema, UnqualifiedName n, LockInfo info) {
if (landlord)
return super.buildInCurrentTenant(schema, n, info);
return getCurrentTenant(true).build(getSchemaContext(),n, info);
}
@Override
public boolean isMTMode() {
return true;
}
@Override
public long getNextAutoIncrBlock(TableInstance tab, long blockSize) {
if (getCurrentTenant(false) == null)
return super.getNextAutoIncrBlock(tab, blockSize);
if (tab instanceof MTTableInstance) {
MTTableInstance mti = (MTTableInstance) tab;
return sc.getCatalog().getNextIncrementValueChunk(getSchemaContext(), mti.getTableScope(), blockSize);
}
return super.getNextAutoIncrBlock(tab, blockSize);
}
@Override
public long readAutoIncrBlock(TableKey tab) {
if (getCurrentTenant(false) == null)
return super.readAutoIncrBlock(tab);
return sc.getCatalog().readNextIncrementValue(getSchemaContext(),tab);
}
@Override
public void removeValue(TableKey tab, long value) {
if (getCurrentTenant(false) == null) {
super.removeValue(tab, value);
} else {
sc.getCatalog().removeNextIncrementValue(getSchemaContext(),tab, value);
}
}
@Override
public ValueHandler handleTenantColumnUponInsert(InsertIntoValuesStatement stmt, PEColumn column) {
return new TenantIDValueHandler(stmt, column);
}
private static class TenantIDValueHandler extends ValueHandler {
public TenantIDValueHandler(InsertIntoValuesStatement stmt, PEColumn col) {
super(stmt,col);
}
@Override
public ExpressionNode handleMissing(SchemaContext sc) {
return sc.getPolicyContext().getTenantIDLiteral(true);
}
}
public static DeleteStatement buildTenantDeleteFromTableStatement(SchemaContext sc, PETable tab, TableScope ts) {
TableInstance ti = new MTTableInstance(tab,ts,tab.getName(),null,true);
FromTableReference ftr = new FromTableReference(ti);
ExpressionNode wc = new FunctionCall(FunctionName.makeEquals(),
new ColumnInstance(ts.getTable(sc).getTenantColumn(sc),ti),LiteralExpression.makeAutoIncrLiteral(ts.getTenant(sc).getTenantID()));
ArrayList<FromTableReference> froms = new ArrayList<FromTableReference>();
froms.add(ftr);
DeleteStatement ds = new DeleteStatement(froms, wc, false, null);
ds.getDerivedInfo().addLocalTable(ti.getTableKey());
return ds;
}
public boolean canCreatePrivateTables() {
return true;
}
public boolean canCreateSharedTables() {
return true;
}
@Override
public Statement modifyCreateTable(PECreateTableStatement stmt) {
checkAllowedLandlord("create a table");
if (stmt instanceof PECreateTableAsSelectStatement)
throw new SchemaException(Pass.PLANNER, "Currently unsupported in mt mode - create table as select");
if (stmt.getTable().isUserlandTemporaryTable())
throw new SchemaException(Pass.PLANNER, "Currently unsupported in mt mode - create temporary table");
PETable newTable = (PETable) stmt.getRoot();
PETenant currentTenant = getCurrentTenant(true);
PETable existingTable = currentTenant.lookup(getSchemaContext(),newTable.getName(), new LockInfo(LockType.EXCLUSIVE, "create table"));
if (existingTable != null) {
String anyDiffs = existingTable.definitionDiffers(newTable);
if (anyDiffs != null)
throw new SchemaException(Pass.SECOND, "Invalid redeclaration of table: " + anyDiffs);
if ((stmt.isIfNotExists() == null || !stmt.isIfNotExists().booleanValue()) && !getSchemaContext().getOptions().isAllowDuplicates())
// already created, without the if not exists bit - this is an error
throw new SchemaException(Pass.SECOND, "Table " + newTable.getName() + " already exists");
stmt.setOld();
return stmt;
}
return new PECreateTenantTableStatement(stmt,currentTenant,newTable.getAutoIncOffset(getSchemaContext()),newTable.getName().getUnqualified());
}
@Override
public Statement modifyDropTable(PEDropTableStatement peds) {
if (peds.getTarget() == null) return peds;
List<TableKey> tks = peds.getDroppedTableKeys();
if (tks.size() > 1)
// this is a limitation of the current implementation - we can circle back to this in a bit.
throw new SchemaException(Pass.PLANNER, "Too many tables in drop table stmt");
TableKey tk = tks.get(0);
if (tk.isUserlandTemporaryTable())
throw new SchemaException(Pass.PLANNER, "No support for drop temporary table in mt mode");
TableScope ts = null;
if (tk instanceof MTTableKey) {
MTTableKey mttk = (MTTableKey) tk;
ts = mttk.getScope();
}
PETenant tenant = getCurrentTenant(true);
return new PEDropTenantTableStatement(peds,ts,tenant);
}
@Override
public Statement modifyAlterTableStatement(PEAlterTableStatement in) {
TableKey tk = in.getTableKey();
if (tk.isUserlandTemporaryTable())
throw new SchemaException(Pass.PLANNER, "No support for alter temporary table in mt mode");
TableScope tenantScope = null;
if (tk instanceof MTTableKey) {
MTTableKey mtk = (MTTableKey) tk;
tenantScope = mtk.getScope();
}
if (tenantScope == null)
throw new SchemaException(Pass.PLANNER, "Unable to find tenant table scope for altered table");
Statement passthrough = maybePassthroughAlterStatement(in);
if (passthrough != null) return passthrough;
ensureTenantColumn(in);
return new PEAlterTenantTableStatement(getSchemaContext(),in, tenantScope, tenantScope.getTenant(getSchemaContext()));
}
@Override
public MultitenantMode getMTMode() {
return MultitenantMode.ADAPTIVE;
}
public static boolean canRetry(Throwable t) {
String[] cns = {
"java.sql.SQLException",
"org.hibernate.HibernateException",
"org.hibernate.exception.LockAcquisitionException",
"org.hibernate.exception.ConstraintViolationException",
"javax.persistence.EntityNotFoundException",
"org.hibernate.StaleStateException"
};
Throwable p = t;
while(p != null) {
Class<?> c = p.getClass();
String cn = c.getName();
for(int i = 0; i < cns.length; i++)
if (cns[i].equals(cn)) {
if (i == 0) {
String message = p.getMessage();
if (!message.startsWith("Lock wait timeout exceeded"))
continue;
} else if (i == 1) {
String message = p.getMessage();
if (!message.startsWith("More than one row with the given identifier was found"))
continue;
}
return true;
}
p = p.getCause();
}
return false;
}
}