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 java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.tesora.dve.common.catalog.CatalogEntity;
import com.tesora.dve.common.catalog.DistributionModel;
import com.tesora.dve.common.catalog.MultitenantMode;
import com.tesora.dve.common.catalog.PersistentGroup;
import com.tesora.dve.common.catalog.TableState;
import com.tesora.dve.common.catalog.UserColumn;
import com.tesora.dve.common.catalog.UserDatabase;
import com.tesora.dve.common.catalog.UserTable;
import com.tesora.dve.db.DBNative;
import com.tesora.dve.db.mysql.MysqlEmitter;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.lockmanager.LockSpecification;
import com.tesora.dve.server.global.HostService;
import com.tesora.dve.singleton.Singletons;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.parser.InvokeParser;
import com.tesora.dve.sql.parser.ParserOptions;
import com.tesora.dve.sql.schema.cache.CacheSegment;
import com.tesora.dve.sql.schema.cache.SchemaCacheKey;
import com.tesora.dve.sql.schema.cache.SchemaEdge;
import com.tesora.dve.sql.schema.modifiers.CharsetTableModifier;
import com.tesora.dve.sql.schema.modifiers.CollationTableModifier;
import com.tesora.dve.sql.schema.modifiers.EngineTableModifier;
import com.tesora.dve.sql.schema.mt.TenantColumn;
import com.tesora.dve.sql.statement.Statement;
import com.tesora.dve.sql.statement.ddl.PECreateStatement;
import com.tesora.dve.sql.util.Cast;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.ListOfPairs;
import com.tesora.dve.sql.util.ListSet;
import com.tesora.dve.sql.util.Pair;
import com.tesora.dve.sql.util.UnaryFunction;
// everything that looks like a table derives from this
// userland tables
// userland views
// temp tables
public abstract class PEAbstractTable<T> extends Persistable<T, UserTable> implements Table<PEColumn> {
// columns, in declaration order
protected List<PEColumn> columns;
// lookup helper
protected SchemaLookup<PEColumn> lookup;
// distribution vector
protected DistributionVector dv;
// persistent group
protected SchemaEdge<PEStorageGroup> storage;
// enclosing database
protected SchemaEdge<PEDatabase> db;
protected TableState state;
private String createTableStatement;
@SuppressWarnings("unchecked")
protected PEAbstractTable(SchemaContext pc, Name name,
List<TableComponent<?>> fieldsAndKeys, DistributionVector dv,
PEPersistentGroup defStorage, PEDatabase db,
TableState theState) {
super(getTableKey(db,name));
setName(name);
this.storage = StructuralUtils.buildEdge(pc,defStorage,false);
this.columns = new ArrayList<PEColumn>();
initializeColumns(pc,fieldsAndKeys);
setDatabase(pc,db,false);
if (this.storage.get(pc) == null && this.db != null && this.db.has())
this.storage = StructuralUtils.buildEdge(pc,this.db.get(pc).getDefaultStorage(pc),false);
setDistributionVector(pc,dv,true);
createTableStatement = null;
state = theState;
lookup = new SchemaLookup<PEColumn>(columns, false, false);
setPersistent(pc,null,null);
}
// make a copy of other, consuming it
protected PEAbstractTable(SchemaContext pc, PEAbstractTable other) {
super(other.getCacheKey());
setName(other.getName(pc,pc.getValues()));
this.columns = new ArrayList<PEColumn>();
this.storage = StructuralUtils.buildEdge(pc,other.getPersistentStorage(pc),false);
ArrayList<TableComponent<?>> fieldsAndKeys = new ArrayList<TableComponent<?>>();
fieldsAndKeys.addAll(other.getFields());
fieldsAndKeys.addAll(other.getKeys());
initializeColumns(pc,fieldsAndKeys);
setDatabase(pc,other.getPEDatabase(pc),false);
setDistributionVector(pc,other.getDistributionVector(pc),true);
createTableStatement = other.createTableStatement;
state = other.state;
lookup = new SchemaLookup<PEColumn>(columns,false,false);
setPersistent(pc,null,null);
}
public abstract PETable asTable();
public abstract PEViewTable asView();
protected abstract boolean isLoaded();
protected abstract void setLoaded();
public abstract String getTableType();
public abstract boolean hasCardinalityInfo(SchemaContext sc);
// for temp tables & virtual tables
@SuppressWarnings("unchecked")
protected PEAbstractTable(SchemaContext pc, Name name, List<PEColumn> cols, DistributionVector distVect, PEStorageGroup group, PEDatabase pdb) {
super(null);
setName(name);
this.columns = new ArrayList<PEColumn>();
for(PEColumn c : cols)
addColumn(pc, c,true);
setDistributionVector(pc,distVect);
storage = StructuralUtils.buildEdge(pc,group, false);
setDatabase(pc,pdb,false);
lookup = new SchemaLookup<PEColumn>(columns, false, false);
setPersistent(pc,null,null);
}
public static PEAbstractTable<?> load(UserTable table, SchemaContext lc) {
PEAbstractTable<?> t = (PEAbstractTable<?>)lc.getLoaded(table,getTableKey(table));
if (t == null) {
if (table.getView() != null)
t = new PEViewTable(table,lc);
else
t = new PETable(table, lc);
}
return t;
}
protected PEAbstractTable(UserTable table, SchemaContext lc) {
super(getTableKey(table));
}
@SuppressWarnings("unchecked")
protected void loadPersistent(UserTable table, SchemaContext lc) {
UnqualifiedName tn = new UnqualifiedName(table.getName(),true);
setName(tn);
setPersistent(lc,table,table.getId());
if (table.getPersistentGroup() != null)
this.storage = StructuralUtils.buildEdge(lc,PEPersistentGroup.load(table.getPersistentGroup(), lc),true);
setDatabase(lc,PEDatabase.load(table.getDatabase(), lc),true);
try {
this.createTableStatement = table.getCreateTableStmt();
} catch (PEException pe) {
this.createTableStatement = null;
}
state = table.getState();
checkLoaded(lc);
}
protected void initializeColumns(SchemaContext pc, List<TableComponent<?>> fieldsAndKeys) {
for(Iterator<TableComponent<?>> iter = fieldsAndKeys.iterator(); iter.hasNext();) {
TableComponent<?> tc = iter.next();
if (tc instanceof PEColumn) {
PEColumn c = (PEColumn)tc;
addColumn(pc, c,true);
iter.remove();
}
}
}
protected List<TableComponent<?>> getKeys() {
return Collections.EMPTY_LIST;
}
protected List<TableComponent<?>> getFields() {
return Functional.apply(columns, new Cast<TableComponent<?>,PEColumn>());
}
public TableState getState() {
return state;
}
public void setState(TableState ts) {
state = ts;
}
// one of our internal temp tables
public boolean isTempTable() {
return false;
}
public boolean isUserlandTemporaryTable(){
return false;
}
public boolean isVirtualTable() {
return false;
}
public boolean isView() {
return false;
}
public void setFrozen() {
}
public boolean isTable() {
return false;
}
public EngineTableModifier getEngine() {
return null;
}
public CollationTableModifier getCollation() {
return null;
}
public CharsetTableModifier getCharset() {
return null;
}
protected PEColumn addColumn(SchemaContext pc, PEColumn c, boolean init) {
if (!init)
checkLoaded(pc);
c.setPosition(this.columns.size());
this.columns.add(c);
if (this.lookup != null) this.lookup.refresh(this.columns);
c.setTable(this);
return c;
}
public void refreshColumnLookupTable() {
if (this.lookup != null) {
this.lookup.refresh(this.columns);
}
}
@Override
public PEColumn addColumn(SchemaContext pc, PEColumn c) {
return addColumn(pc, c,false);
}
public void addColumns(SchemaContext pc, List<PEColumn> columns) {
for (final PEColumn c : columns) {
addColumn(pc, c, false);
}
}
protected List<PEColumn> getColumns(SchemaContext pc, boolean init) {
if (!init)
checkLoaded(pc);
return Collections.unmodifiableList(this.columns);
}
public List<PEKey> getKeys(SchemaContext sc) {
return Collections.emptyList();
}
@Override
public PEColumn lookup(SchemaContext pc, Name name) {
return lookup.lookup(name);
}
// used in tests, and now in templates
public PEColumn lookup(SchemaContext pc, String name) {
checkLoaded(pc);
return lookup(pc, new UnqualifiedName(name));
}
@Override
public List<PEColumn> getColumns(SchemaContext pc) {
return getColumns(pc,false);
}
public void removeColumn(SchemaContext pc, PEColumn c) {
checkLoaded(pc);
this.columns.remove(c);
}
public void removeColumns(SchemaContext pc, List<PEColumn> columns) {
checkLoaded(pc);
this.columns.removeAll(columns);
}
@Override
public Name getName() {
return this.name;
}
@Override
public Name getName(SchemaContext sc,ConnectionValues cv) {
return getName();
}
@Override
public boolean isInfoSchema() {
return false;
}
protected void loadColumns(UserTable table, SchemaContext pc) {
this.columns = new ArrayList<PEColumn>();
for(UserColumn col : table.getUserColumns()) {
PEColumn c = PEColumn.load(col, pc, null);
addColumn(pc, c,true);
}
// set the lookup so that keys & dist vect may be resolved
lookup = new SchemaLookup<PEColumn>(columns, false, false);
}
protected void checkLoaded(SchemaContext pc) {
if (isLoaded() || pc == null)
return;
UserTable table = getPersistent(pc,true);
if (table == null)
throw new IllegalStateException("missing persistent table");
synchronized(this) {
if (isLoaded())
return;
loadColumns(table,pc);
dv = DistributionVector.load(pc, this, table);
loadRest(table,pc);
setLoaded();
}
}
protected void loadRest(UserTable table, SchemaContext pc) {
}
public DistributionVector getDistributionVector(SchemaContext sc) {
checkLoaded(sc);
return this.dv;
}
public void setDistributionVector(SchemaContext sc, DistributionVector dv) {
setDistributionVector(sc,dv,false);
}
protected void setDistributionVector(SchemaContext sc, DistributionVector indv, boolean init) {
if (indv instanceof UnresolvedRangeDistributionVector) {
UnresolvedRangeDistributionVector unres = (UnresolvedRangeDistributionVector) indv;
indv = unres.resolve(sc, getPersistentStorage(sc));
}
// if we have an existing distribution vector, go unset the dv offsets if applicable
if (this.dv != null && this.dv.usesColumns(sc)) {
for(PEColumn pec : this.dv.getColumns(sc)) {
pec.setDistributionValuePosition(0);
}
}
this.dv = indv;
if (this.dv != null) {
this.dv.setTable(sc, this);
if (this.dv.usesColumns(sc)) {
List<PEColumn> cols = this.dv.getColumns(sc,init);
for(int i = 0; i < cols.size(); i++)
cols.get(i).setDistributionValuePosition(i+1);
}
}
}
public boolean usesDV(SchemaContext sc) {
checkLoaded(sc);
if (dv == null)
return false;
return dv.usesColumns(sc);
}
public boolean isContainerBaseTable(SchemaContext sc) {
return false;
}
public String getQualifiedPersistentName(SchemaContext sc) {
return db.get(sc).getName().getUnquotedName().get() + "." + getName().getUnquotedName().get();
}
public PEPersistentGroup getPersistentStorage(SchemaContext pc) {
if (this.storage.get(pc).isTempGroup()) return null;
return (PEPersistentGroup)this.storage.get(pc);
}
@SuppressWarnings("unchecked")
public void setStorage(SchemaContext pc, PEPersistentGroup peStorageGroup, boolean persistent) {
this.storage = StructuralUtils.buildEdge(pc,peStorageGroup,persistent);
}
public PEStorageGroup getStorageGroup(SchemaContext sc) { return this.storage.get(sc); }
public SchemaEdge<PEStorageGroup> getStorageGroupEdge() { return this.storage; }
@SuppressWarnings("unchecked")
public void setDatabase(SchemaContext sc, PEDatabase pdb, boolean persistent) {
this.db = StructuralUtils.buildEdge(sc,pdb,persistent);
}
public TenantColumn getTenantColumn(SchemaContext sc) {
return getTenantColumn(sc,false);
}
public TenantColumn getTenantColumn(SchemaContext sc, boolean init) {
for(PEColumn c : getColumns(sc, init))
if (c.isTenantColumn())
return (TenantColumn) c;
return null;
}
public ListSet<PEColumn> getDiscriminantColumns(SchemaContext sc) {
ListSet<PEColumn> out = new ListSet<PEColumn>();
for(PEColumn pec : getColumns(sc)) {
if (pec.isPartOfContainerDistributionVector())
out.add(pec);
}
return out;
}
@Override
public Database<?> getDatabase(SchemaContext sc) {
return db.get(sc);
}
public boolean hasDatabase(SchemaContext sc) {
if (db == null) {
return false;
}
return (db.get(sc) != null);
}
public PEDatabase getPEDatabase(SchemaContext sc) {
return (PEDatabase)getDatabase(sc);
}
// for temporary table support
public Name getDatabaseName(SchemaContext sc) {
return getDatabase(sc).getName();
}
// also for temp table support
public MultitenantMode getEnclosingDatabaseMTMode(SchemaContext sc) {
Database<?> db = getDatabase(sc);
if (db instanceof PEDatabase) {
PEDatabase pedb = (PEDatabase) db;
return pedb.getMTMode();
}
return MultitenantMode.OFF;
}
public void setDeclaration(SchemaContext sc, PEAbstractTable<?> basedOn) {
checkLoaded(sc);
if (!getName().equals(basedOn.getName()))
throw new SchemaException(Pass.PLANNER, "Invalid create table stmt: trying to set " + basedOn.getName() + " into table " + getName());
createTableStatement = new MysqlEmitter().emitCreateTableStatement(sc, sc.getValues(),basedOn);
}
public String getDeclaration() {
return createTableStatement;
}
@SuppressWarnings("unchecked")
@Override
public Persistable<T, UserTable> reload(SchemaContext usingContext) {
UserTable ut = usingContext.getCatalog().findUserTable(getName(), getPEDatabase(usingContext).getPersistentID(), getDatabase(usingContext).getName().getUnquotedName().get());
return (Persistable<T, UserTable>) PEAbstractTable.load(ut, usingContext);
}
@Override
protected UserTable createEmptyNew(SchemaContext pc) throws PEException {
String persistName = Singletons.require(DBNative.class).getEmitter().getPersistentName(pc, pc.getValues(), this);
UserDatabase pdb = this.db.get(pc).persistTree(pc);
DistributionModel dm = dv.persistTree(pc);
PersistentGroup sg = null;
if (storage != null && storage.get(pc) != null)
sg = storage.get(pc).persistTree(pc);
else
sg = pdb.getDefaultStorageGroup();
EngineTableModifier etm = getEngine();
UserTable ut = new UserTable(persistName, dm, pdb, state, (etm == null ? null : etm.getPersistent()), getTableType());
ut.setPersistentGroup(sg);
pdb.addUserTable(ut);
pc.getSaveContext().add(this,ut);
return ut;
}
@Override
protected void populateNew(SchemaContext pc,UserTable p) throws PEException {
for(PEColumn col : getColumns(pc)) {
p.addUserColumn(col.persistTree(pc));
}
if (db != null && db.get(pc) != null)
p.setDatabase(this.db.get(pc).persistTree(pc));
p.setCreateTableStmt(createTableStatement);
if (dv != null)
dv.persistForTable(this, p, pc);
}
@Override
protected Class<? extends CatalogEntity> getPersistentClass() {
return UserTable.class;
}
@Override
public UserTable persistTree(SchemaContext pc, boolean forRefresh) throws PEException {
if (!forRefresh)
checkLoaded(pc);
return super.persistTree(pc,forRefresh);
}
@Override
protected void updateExisting(SchemaContext pc, UserTable ut) throws PEException {
checkLoaded(pc);
ut.setCreateTableStmt(createTableStatement);
ut.setName(getName().get());
ut.setState(state);
updateExistingColumns(pc,ut);
}
private static UserColumn getUserColumnForStorage(final SchemaContext sc, final PEColumn source) throws PEException {
final UserColumn nuc = source.persistTree(sc);
nuc.setOrderInTable(source.getPosition() + 1);
return nuc;
}
protected void updateExistingColumns(SchemaContext pc, UserTable ut) throws PEException {
final LinkedHashMap<String, PEColumn> transCols = new LinkedHashMap<String, PEColumn>();
// build a lookup table of names; key is transient name, value is persistent name.
final ListOfPairs<String, String> transToPersMapping = new ListOfPairs<String, String>();
for (final PEColumn c : columns) {
final String transName = c.getName().getCapitalized().get();
transCols.put(transName, c);
if (c.getPersistentID() != null) {
final String persName = c.getPersistent(pc).getName().toUpperCase().trim();
if (!transName.equals(persName))
transToPersMapping.add(transName, persName);
}
}
pc.beginSaveContext();
try {
// build a lookup table for the persistent columns
final LinkedHashMap<String, UserColumn> persCols = new LinkedHashMap<String, UserColumn>();
for (final UserColumn uc : ut.getUserColumns()) {
final String name = uc.getName().toUpperCase().trim();
persCols.put(name, uc);
}
final List<UserColumn> userTableColumns = new ArrayList<UserColumn>(persCols.size());
// do any renaming first
for (final Pair<String, String> p : transToPersMapping) {
final PEColumn pec = transCols.remove(p.getFirst());
userTableColumns.add(getUserColumnForStorage(pc, pec));
}
for (final Map.Entry<String, UserColumn> pv : persCols.entrySet()) {
final PEColumn any = transCols.remove(pv.getKey());
final UserColumn uc = pv.getValue();
if (any != null) {
final PEColumn apc = PEColumn.load(uc, pc, null);
final int ucPosition = uc.getOrderInTable(); // 1 based
apc.setPosition(ucPosition - 1);
final String anydiffs = any.differs(pc, apc, true);
if (anydiffs != null) {
// persist updated columns
userTableColumns.add(getUserColumnForStorage(pc, any));
} else {
userTableColumns.add(uc); // reuse not updated
}
}
// anything in persCols that wasn't found in transCols - deletion
ut.removeUserColumn(uc);
}
// anything left in transCols that wasn't present in persCols - addition
for (final PEColumn c : transCols.values()) {
userTableColumns.add(getUserColumnForStorage(pc, c));
}
// order the columns by their position within the parent table
Collections.sort(userTableColumns, new Comparator<UserColumn>() {
@Override
public int compare(UserColumn uc1, UserColumn uc2) {
return uc1.getOrderInTable().compareTo(uc2.getOrderInTable());
}
});
ut.addUserColumnList(userTableColumns);
} finally {
pc.endSaveContext();
}
}
public static SchemaCacheKey<PEAbstractTable<?>> getTableKey(UserTable ut) {
return new TableCacheKey(ut.getDatabase().getId(),ut.getDatabase().getName(),new UnqualifiedName(ut.getName()));
}
public static TableCacheKey getTableKey(PEDatabase within, Name tableName) {
if (within == null) return null;
int dbid = (within.getPersistentID() == null ? 0 : within.getPersistentID().intValue());
return new TableCacheKey(dbid, within.getName().getUnquotedName().get(), tableName);
}
public static class TableCacheKey extends SchemaCacheKey<PEAbstractTable<?>> {
private static final long serialVersionUID = 1L;
private int dbid;
private String dbName;
private Name tabname;
public TableCacheKey(int dbid, String dbn, Name tableName) {
super();
this.dbid = dbid;
this.tabname = tableName.copy();
this.tabname.prepareForSerialization();
dbName = dbn;
}
@Override
public int hashCode() {
return addIntHash(addHash(initHash(PETable.class,tabname.getUnquotedName().getUnqualified().get().hashCode()),dbName.hashCode()),dbid);
}
@Override
public String toString() {
return "PETable:" + dbid + "(" + dbName + ")/" + tabname.getUnquotedName().getUnqualified().get();
}
public String getDatabaseName() {
return dbName;
}
public Name getTableName() {
return tabname;
}
@Override
public boolean equals(Object o) {
if (o instanceof TableCacheKey) {
TableCacheKey petk = (TableCacheKey) o;
return this.dbid == petk.dbid && this.tabname.get().equals(petk.tabname.get());
}
return false;
}
@Override
public PEAbstractTable<?> load(SchemaContext sc) {
UserTable ut = sc.getCatalog().findUserTable(tabname, dbid, dbName);
if (ut == null)
return null;
return PEAbstractTable.load(ut, sc);
}
@Override
public CacheSegment getCacheSegment() {
return CacheSegment.TABLE;
}
@Override
public Collection<SchemaCacheKey<?>> getCascades(Object obj) {
PEAbstractTable<?> pet = (PEAbstractTable<?>) obj;
if (pet.isTable()) {
PETable t = (PETable)pet;
if (t.getReferencingTables() == null) return Collections.emptyList();
return Functional.apply(t.getReferencingTables(), new UnaryFunction<SchemaCacheKey<?>,SchemaCacheKey<PEAbstractTable<?>>>() {
@Override
public SchemaCacheKey<?> evaluate(SchemaCacheKey<PEAbstractTable<?>> object) {
return object;
}
});
}
return super.getCascades(obj);
}
@Override
public LockSpecification getLockSpecification(String reason) {
return new TableUseLock(reason, dbName,tabname.getUnquotedName().get());
}
}
@Override
protected UserTable lookup(SchemaContext pc) throws PEException {
if (persistentID == null) return null;
PEDatabase peds = this.db.get(pc);
return pc.getCatalog().findUserTable(this.getName(), peds.getPersistentID(), peds.getName().getUnquotedName().get());
}
@Override
protected int getID(UserTable p) {
return p.getId();
}
public PEAbstractTable<?> recreate(SchemaContext sc, String decl, LockInfo li) {
Database<?> cdb = sc.getCurrentDatabase(false);
ParserOptions prevOptions = sc.getOptions();
if (prevOptions == null)
prevOptions = ParserOptions.NONE;
ParserOptions subOptions = prevOptions
.setAllowDuplicates()
.setAllowTenantColumn()
.setTSchema()
.setOmitMetadataInjection()
.disableMTLookupChecks()
.setOmitTenantColumnInjection();
if (li != null)
subOptions = subOptions.setLockOverride(li);
PETable tschemaVersion = null;
ValueManager vm = sc.getValueManager();
ConnectionValues cv = sc.getValues();
sc.setValueManager(new ValueManager());
try {
sc.setCurrentDatabase(getDatabase(sc));
List<Statement> prereq = InvokeParser.parse(InvokeParser.buildInputState(decl,sc), subOptions, sc).getStatements();
for(Statement s : prereq) {
if (s instanceof PECreateStatement) {
PECreateStatement<?,?> pecs = (PECreateStatement<?,?>) s;
if (pecs.getRoot() instanceof PETable) {
tschemaVersion = (PETable) pecs.getRoot();
break;
}
}
}
} finally {
sc.setOptions(prevOptions);
sc.setCurrentDatabase(cdb);
sc.setValueManager(vm);
sc.setValues(cv);
}
if (tschemaVersion == null) {
throw new SchemaException(Pass.PLANNER, "Unable to generate create table statement");
}
if (!tschemaVersion.getName().equals(getName()))
throw new SchemaException(Pass.PLANNER, "Wrong recreated tschema table. Have " + getName() + " but recreated " + tschemaVersion.getName());
// make sure we keep the original dist vect - we might have applied a new one if the template has changed
tschemaVersion.dv = dv.adapt(sc, tschemaVersion);
return tschemaVersion;
}
}