package com.tesora.dve.sql.schema;
/*
* #%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 com.tesora.dve.common.PEConstants;
import com.tesora.dve.common.catalog.FKMode;
import com.tesora.dve.common.catalog.MultitenantMode;
import com.tesora.dve.common.catalog.TemplateMode;
import com.tesora.dve.common.catalog.UserDatabase;
import com.tesora.dve.common.catalog.UserTable;
import com.tesora.dve.errmap.AvailableErrors;
import com.tesora.dve.errmap.ErrorInfo;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.expression.TableKey;
import com.tesora.dve.sql.node.expression.LiteralExpression;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.node.expression.TenantIDLiteral;
import com.tesora.dve.sql.schema.cache.SchemaEdge;
import com.tesora.dve.sql.schema.mt.AdaptiveMultitenantSchemaPolicyContext;
import com.tesora.dve.sql.schema.mt.IPETenant;
import com.tesora.dve.sql.schema.mt.PETenant;
import com.tesora.dve.sql.schema.mt.TableScope;
import com.tesora.dve.sql.schema.mt.TenantColumn;
import com.tesora.dve.sql.statement.EmptyStatement;
import com.tesora.dve.sql.statement.Statement;
import com.tesora.dve.sql.statement.StatementType;
import com.tesora.dve.sql.statement.ddl.PEAlterTableStatement;
import com.tesora.dve.sql.statement.ddl.PEAlterTenantStatement;
import com.tesora.dve.sql.statement.ddl.PECreateDatabaseStatement;
import com.tesora.dve.sql.statement.ddl.PECreateStatement;
import com.tesora.dve.sql.statement.ddl.PECreateTableStatement;
import com.tesora.dve.sql.statement.ddl.PECreateTenantStatement;
import com.tesora.dve.sql.statement.ddl.PEDropDatabaseStatement;
import com.tesora.dve.sql.statement.ddl.PEDropTableStatement;
import com.tesora.dve.sql.statement.ddl.PEDropTenantStatement;
import com.tesora.dve.sql.statement.ddl.alter.AbstractAlterColumnAction;
import com.tesora.dve.sql.statement.ddl.alter.AlterTableAction;
import com.tesora.dve.sql.statement.ddl.alter.AlterTargetKind;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.dml.InsertIntoValuesStatement;
import com.tesora.dve.sql.statement.dml.InsertIntoValuesStatement.ValueHandler;
import com.tesora.dve.sql.statement.session.UseDatabaseStatement;
import com.tesora.dve.sql.statement.session.UseTenantStatement;
import com.tesora.dve.sql.template.TemplateManager;
import com.tesora.dve.sql.transform.execution.ExecutionType;
import com.tesora.dve.sql.util.Pair;
import com.tesora.dve.variables.KnownVariables;
public class SchemaPolicyContext {
public static SchemaPolicyContext buildContext(SchemaContext sc) {
IPETenant tenant = sc.getCurrentTenant().get(sc);
if (tenant instanceof PEContainerTenant)
return new ContainerPolicyContext(sc,(PEContainerTenant)tenant);
else if (tenant instanceof PETenant)
return AdaptiveMultitenantSchemaPolicyContext.build(sc,((PETenant)tenant).getDatabase(sc));
Database<?> db = sc.getCurrentDatabase(false,false);
if (db == null || db.isInfoSchema())
return new SchemaPolicyContext(sc);
PEDatabase peds = (PEDatabase) db;
if (peds.getMTMode().isMT())
return AdaptiveMultitenantSchemaPolicyContext.build(sc, peds);
return new SchemaPolicyContext(sc);
}
protected SchemaContext sc;
protected SchemaEdge<PEUser> user;
protected SchemaPolicyContext(SchemaContext cntxt) {
sc = cntxt;
user = sc.getCurrentUser();
}
protected SchemaContext getSchemaContext() {
return sc;
}
public PEUser getUser() {
return user.get(sc);
}
public void checkRootPermission(String what) {
if (!isRoot())
throw new SchemaException(Pass.SECOND, "You do not have permission to " + what);
}
public void checkEnabled() {
checkEnabled(getCurrentTenant());
}
protected void checkEnabled(IPETenant tenant) {
if (isRoot()) return; // root user can always use a tenant
if (tenant == null) return;
if (tenant instanceof PETenant) {
PETenant pet = (PETenant) tenant;
if (pet.isSuspended())
throw new SchemaException(Pass.SECOND, "Your account has been disabled");
}
}
// use database statement needs to be mt aware
// the lookup order is:
// check for info schema - if info schema use that
//
// find the user
// if user is not root
// find a visible db name/tenant in the user
// else
// find the tenant/db name on the catalog
// if dbname is an mtdb, set the tenant to the landlord and the db to the db
// if dbname is the landlord tenant, set the tenant to null and the db to the db
public Statement buildUseDatabaseStatement(Name dbName) {
if (PEConstants.INFORMATION_SCHEMA_DBNAME.equals(dbName.getCapitalized().get()) ||
PEConstants.MYSQL_SCHEMA_DBNAME.equals(dbName.get())) {
return new UseDatabaseStatement(sc.findDatabase(dbName));
}
PEUser currentUser = user.get(sc);
if (!currentUser.isRoot()) {
Persistable<?,?> any = currentUser.resolve(getSchemaContext(),dbName);
if (any instanceof PEDatabase) {
return new UseDatabaseStatement((PEDatabase)any);
} else if (any instanceof PETenant) {
PETenant ten = (PETenant) any;
checkEnabled(ten);
return new UseTenantStatement(ten,ten.getDatabase(sc));
} else if (currentUser.hasGlobalPriviledge(getSchemaContext())) {
Database<?> db = sc.findDatabase(dbName);
if (db != null) {
if (db.isInfoSchema()) return new UseDatabaseStatement(db);
PEDatabase peds = (PEDatabase)db;
if (!peds.getMTMode().isMT())
return new UseDatabaseStatement(peds);
// purposefully falling through because a regular user shouldn't be able to poke around
// in the mtdb
} else {
PETenant tenant = sc.findTenant(dbName);
if (tenant != null)
return new UseTenantStatement(tenant,tenant.getDatabase(sc));
}
}
} else {
// since root can access everything, we have to look it up differently
Database<?> db = sc.findDatabase(dbName);
if (db != null) {
if (db.isInfoSchema()) return new UseDatabaseStatement(db);
PEDatabase peds = (PEDatabase) db;
if (peds.getMTMode().isMT()) {
PETenant pet = sc.findTenant(new UnqualifiedName(PEConstants.LANDLORD_TENANT));
return new UseTenantStatement(pet,peds);
}
return new UseDatabaseStatement(peds);
}
if (PEConstants.LANDLORD_TENANT.equals(dbName.get())) {
// setting to the pe landlord - don't set the tenant
PEDatabase peds = sc.findSingleMTDatabase();
return new UseDatabaseStatement(peds);
}
PETenant tenant = sc.findTenant(dbName);
if (tenant != null)
return new UseTenantStatement(tenant,tenant.getDatabase(sc));
}
throw new SchemaException(new ErrorInfo(AvailableErrors.UNKNOWN_DATABASE,dbName.getUnquotedName().get()));
}
public Statement buildCreateTenantStatement(PEDatabase peds, Name tenantName, String description) {
return buildCreateTenantStatement(peds, tenantName, description, "tenant", "Tenant", StatementType.UNIMPORTANT);
}
protected PETenant lookupTenant(Name n, boolean except) {
PETenant any = sc.findTenant(n);
if (any == null && except)
throw new SchemaException(Pass.SECOND, "No such tenant: " + n.getSQL());
return any;
}
protected Statement buildCreateTenantStatement(PEDatabase mtdb, Name tenantName, String description, String tag, String ltag, StatementType logicalType) {
checkRootPermission("create a " + tag);
PETenant existing = lookupTenant(tenantName, false);
if (existing != null)
throw new SchemaException(Pass.SECOND, ltag + " " + tenantName.getSQL() + " already exists");
existing = new PETenant(sc,mtdb,tenantName.getUnquotedName().getSQL(), description);
return new PECreateTenantStatement(existing,false, logicalType);
}
public Statement buildDropTenantStatement(Name tenantName, boolean viaDropDB) {
checkRootPermission("drop a tenant");
if (tenantName.get().toLowerCase().equals(PEConstants.LANDLORD_TENANT))
throw new SchemaException(Pass.SECOND,"Cannot drop root tenant");
PETenant existing = lookupTenant(tenantName, true);
return new PEDropTenantStatement(null, existing, (viaDropDB ? StatementType.DROP_DB : null));
}
public Statement buildResumeTenantStatement(Name tenantName) {
checkRootPermission("resume a tenant");
PETenant existing = lookupTenant(tenantName, true);
return new PEAlterTenantStatement(existing, false);
}
public Statement buildSuspendTenantStatement(Name tenantName) {
checkRootPermission("suspend a tenant");
PETenant existing = lookupTenant(tenantName, true);
if (existing.getName().get().equals(PEConstants.LANDLORD_TENANT))
throw new SchemaException(Pass.SECOND, "Cannot suspend root tenant");
return new PEAlterTenantStatement(existing, true);
}
/**
* @param nt
*/
public void modifyTablePart(PETable nt) {
}
public Statement modifyCreateTable(PECreateTableStatement stmt) {
return stmt;
}
public Statement modifyDropTable(PEDropTableStatement stmt) {
return stmt;
}
/**
* @param mustExist
*/
public Database<?> getCurrentDatabase(Database<?> peds, boolean mustExist) {
return peds;
}
/**
* dml rewrites occur for two cases:
* if an insert, then the tenant id MUST be set, and mt rewrites are required.
* if a delete/update/select, then the tenant id MAY be set, and mt rewrites MAY be required.
*
* @param et
*/
public boolean requiresMTRewrites(ExecutionType et) {
return false;
}
public LiteralExpression getTenantIDLiteral(boolean mustExist) {
Long value = getTenantID(mustExist);
if (value == null) return null;
sc.getValueManager().setTenantID(sc,value,null);
return new TenantIDLiteral(sc.getValues());
}
public Long getTenantID(boolean mustExist) {
if (mustExist)
throw new SchemaException(Pass.REWRITER, "No tenant ids available in nontenant mode");
return null;
}
public IPETenant getCurrentTenant() {
return null;
}
public boolean showTenantColumn() {
return false;
}
/**
* @param in
*/
public void applyDegenerateMultitenantFilter(DMLStatement in) {
// do nothing if it's not a mt context
}
public boolean allowTenantColumnDeclaration() {
return false;
}
/**
* @param t
*/
public void onDropUserTable(PETable t) {
}
public TableInstance buildInCurrentTenant(PESchema schema, UnqualifiedName n, LockInfo lockType) {
return schema.buildInstance(getSchemaContext(),n, lockType, false);
}
// generally answers the question is it mt mode
public boolean isMTMode() {
return false;
}
public MultitenantMode getMTMode() {
return MultitenantMode.OFF;
}
public boolean isRoot() {
return user.get(sc).isRoot();
}
public boolean isSchemaTenant() {
return false;
}
public boolean isDataTenant() {
return false;
}
public boolean isAlwaysDistKey() {
return false;
}
/**
* used in show create table
*
* @param ut
*/
public TableScope getOfTenant(UserTable ut) {
return null;
}
public Name getLocalName(UserTable ut) {
return new UnqualifiedName(ut.getName());
}
public long getNextAutoIncrBlock(TableInstance tab, long blockSize) {
return sc.getCatalog().getNextIncrementValueChunk(sc,tab.getAbstractTable().asTable(), blockSize);
}
public long readAutoIncrBlock(TableKey tab) {
return sc.getCatalog().readNextIncrementValue(sc,tab);
}
public void removeValue(TableKey tab, long value) {
sc.getCatalog().removeNextIncrementValue(sc,tab, value);
}
public void removeValue(PETable tab, long value) {
sc.getCatalog().removeNextIncrementValue(sc, tab, value);
}
/**
* @param ts
* @param value
*/
public void removeValue(TableScope ts, long value) {
throw new SchemaException(Pass.PLANNER, "Invalid removeValue call: table scope in non mt context");
}
// when not in mt mode, we generally support all the usual alters
// well, except if the target is a container base table - then we don't let you modify the discriminant columns
public Statement modifyAlterTableStatement(PEAlterTableStatement in) {
PETable targ = in.getTarget();
if (targ.isContainerBaseTable(getSchemaContext())) {
for(AlterTableAction aa : in.getActions()) {
if (aa.getTargetKind() == AlterTargetKind.COLUMN) {
AbstractAlterColumnAction aaca = (AbstractAlterColumnAction) aa;
for(PEColumn targcol : aaca.getColumns()) {
if (targcol.isPartOfContainerDistributionVector())
throw new SchemaException(Pass.PLANNER, "Illegal alter on container base table " + targ.getName().getSQL() + " discriminant column " + targcol.getName().getSQL());
}
}
}
}
return in;
}
private Statement buildCreatePEDatabase(Name dbName, Name defStorageGroup, Pair<Name, TemplateMode> templateDecl,
Boolean ifNotExists, String tag, MultitenantMode mtm, FKMode fkm,
String charSet, String collation) {
assert (templateDecl != null);
MultitenantMode mtmode = (mtm == null ? MultitenantMode.OFF : mtm);
PEPersistentGroup pesg = null;
if (defStorageGroup != null) {
// An explicit persistent group has been specified - use it
pesg = sc.findStorageGroup(defStorageGroup);
if (pesg == null)
throw new SchemaException(Pass.SECOND, "Persistent group " + defStorageGroup.getSQL() + " does not exist.");
} else if (KnownVariables.BALANCE_PERSISTENT_GROUPS.getValue(sc.getConnection().getVariableSource()).booleanValue()) {
// We need to dynamic pick a persistent group out of those configured
pesg = sc.findBalancedPersistentGroup(
KnownVariables.BALANCE_PERSISTENT_GROUPS_PREFIX.getValue(sc.getConnection().getVariableSource()));
if(pesg == null)
throw new SchemaException(Pass.SECOND, "Failed to find suitable persistent group for balanced database.");
} else {
// Use the default persistent group
pesg = sc.getPersistentGroup();
if (pesg == null) {
throw new SchemaException(Pass.SECOND, "Must specify persistent group. No default persistent group set on project.");
}
}
final Pair<Name, TemplateMode> checkedTemplateDecl = TemplateManager.findTemplateForDatabase(sc, dbName, templateDecl.getFirst(),
templateDecl.getSecond());
PEDatabase pdb = new PEDatabase(sc, dbName.getUnquotedName(), pesg, checkedTemplateDecl, mtmode, fkm, charSet, collation);
PECreateStatement<PEDatabase, UserDatabase> cdb = new PECreateDatabaseStatement(pdb, false, ifNotExists, tag, false);
return cdb;
}
public Statement buildCreateDatabase(Name dbName, Name defStorageGroup,
Pair<Name, TemplateMode> templateDecl, Boolean ifNotExists,
String tag, MultitenantMode inmtm, FKMode fkm,
String charSet, String collation) {
checkRootPermission("create a database");
MultitenantMode mtm = (inmtm == null ? MultitenantMode.OFF : inmtm);
// there's a few cases here:
// [1] dbName is not a database, and not a tenant
// [a] there's a single mt database in mode adaptive - create a tenant
// [b] there's no single mt database - create a new database
// [2] dbName is a database
// [a] dbName is currently differently declared - error
// [b] if not exists - empty create
// [c] error - dup database
// [3] dbName is a tenant
// [a] if not exists - ok
// [b] error - dup tenant
PEDatabase edb = sc.findPEDatabase(dbName);
PETenant eten = sc.findTenant(dbName);
if (edb == null && eten == null) {
PEDatabase smt = sc.findSingleMTDatabase();
if (smt == null) {
return buildCreatePEDatabase(dbName,defStorageGroup,templateDecl,ifNotExists,tag,mtm,fkm,charSet,collation);
}
return buildCreateTenantStatement(smt,dbName, null, "database", "Database",StatementType.CREATE_DB);
} else if (edb != null) {
if (!edb.getMTMode().equals(mtm))
throw new SchemaException(Pass.SECOND,
"Attempt to redeclare database " + dbName + " as a " + mtm.describe()
+ " database but " + dbName + " is a " + edb.getMTMode().describe() + " database");
if (Boolean.TRUE.equals(ifNotExists))
return new PECreateDatabaseStatement(edb,false,ifNotExists,tag,true);
throw new SchemaException(Pass.SECOND, "Database " + dbName.getSQL() + " already exists");
} else if (eten != null) {
// redeclaration of tenant on same db is ok if if not exists
PEDatabase smt = sc.findSingleMTDatabase();
if (!smt.getName().equals(eten.getDatabase(sc).getName())) {
throw new SchemaException(Pass.SECOND,"Attempt to redeclare a database with different backing storage");
}
if (Boolean.TRUE.equals(ifNotExists))
return new PECreateTenantStatement(eten,ifNotExists,true,StatementType.CREATE_DB);
throw new SchemaException(Pass.SECOND, "Database " + dbName.getSQL() + " already exists");
} else {
throw new SchemaException(Pass.SECOND, "Database " + dbName.getSQL() + " already exists");
}
}
public Statement buildDropDatabaseStatement(Name dbName, Boolean ifExists, boolean dropmt, String tag) {
checkRootPermission("drop a database");
// We have a number of cases here
// [1] dbName is an existing database
// [a] it's an mt database, and the dropmt flag is false - error
// [b] it's not an mt database, and the dropmt flag is true - error
// [c] drop the database
// [2] dbName is an existing tenant
// [a] the tenant db is in standard mode - error
// [b] the tenant db is in relaxed/adaptive - drop the tenant
// [3] dbName cannot be found
// [a] if not exists - empty statement
// [b] error
PEDatabase eped = sc.findPEDatabase(dbName);
PETenant eten = sc.findTenant(dbName);
if (eped != null) {
if (eped.getMTMode().isMT() && !dropmt)
throw new SchemaException(Pass.SECOND, "Illegal drop database statement. Use DROP MULTITENANT DATABASE to drop a multitenant database");
else if (!eped.getMTMode().isMT() && dropmt)
throw new SchemaException(Pass.SECOND, "Database " + dbName.getSQL() + " is not a multitenant database");
return new PEDropDatabaseStatement(eped, tag);
} else if (eten != null) {
return buildDropTenantStatement(dbName,true);
} else {
if (Boolean.TRUE.equals(ifExists))
return new EmptyStatement("drop nonexistent database/tenant",StatementType.DROP_DB);
throw new SchemaException(Pass.SECOND, "Database " + dbName.getSQL() + " does not exist");
}
}
protected void addTenantColumn(PETable nt, UnqualifiedName tenantColumnName) {
PEColumn already = nt.lookup(getSchemaContext(),tenantColumnName);
if (already != null) {
if (allowTenantColumnDeclaration())
return;
else
throw new SchemaException(Pass.SECOND, "Tenant column conflict: found existing tenant column");
} else {
nt.addColumn(getSchemaContext(),new TenantColumn(getSchemaContext()));
}
}
public boolean allowTenantColumnDeclarationChecking() {
// allow two specific cases
// - testing
// - alter support
// note that we checked permissions already
if (getSchemaContext().getConnection().allowTenantColumnDecls())
return true;
if (getSchemaContext().getOptions().isAllowTenantColumn())
return true;
return false;
}
public boolean isCacheableInsert(InsertIntoValuesStatement stmt) {
PEAbstractTable<?> pet = stmt.getTableInstance().getAbstractTable();
if (pet.isContainerBaseTable(getSchemaContext()))
throw new SchemaException(new ErrorInfo(AvailableErrors.INVALID_INSERT_CONTAINER_BASE_TABLE,
pet.getName().getUnquotedName().get(),
pet.getDistributionVector(getSchemaContext()).getContainer(getSchemaContext()).getName().getUnquotedName().get()));
else if (pet.getDistributionVector(getSchemaContext()).isContainer())
// insert into a cmt with no tenant specified is an error
throw new SchemaException(new ErrorInfo(AvailableErrors.INVALID_INSERT_CONTAINER_TABLE,
pet.getName().getUnquotedName().get(),
pet.getDistributionVector(getSchemaContext()).getContainer(getSchemaContext()).getName().getUnquotedName().get()));
return true;
}
public ValueHandler handleTenantColumnUponInsert(InsertIntoValuesStatement stmt, PEColumn column) {
throw new SchemaException(Pass.NORMALIZE, "Tenant column found in non tenant mode");
}
public boolean isContainerContext() {
return false;
}
}