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.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import com.tesora.dve.db.DBNative;
import org.apache.commons.codec.binary.Hex;
import com.tesora.dve.common.MultiMap;
import com.tesora.dve.common.PECharsetUtils;
import com.tesora.dve.common.PEConstants;
import com.tesora.dve.common.catalog.CatalogDAO;
import com.tesora.dve.common.catalog.ConstraintType;
import com.tesora.dve.common.catalog.DistributionModel;
import com.tesora.dve.common.catalog.Key;
import com.tesora.dve.common.catalog.PersistentColumn;
import com.tesora.dve.common.catalog.PersistentContainer;
import com.tesora.dve.common.catalog.PersistentDatabase;
import com.tesora.dve.common.catalog.PersistentTable;
import com.tesora.dve.common.catalog.StorageGroup;
import com.tesora.dve.common.catalog.TableState;
import com.tesora.dve.common.catalog.UserTable;
import com.tesora.dve.common.catalog.UserTrigger;
import com.tesora.dve.db.mysql.MysqlEmitter;
import com.tesora.dve.distribution.KeyValue;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.server.global.HostService;
import com.tesora.dve.singleton.Singletons;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.schema.cache.SchemaCacheKey;
import com.tesora.dve.sql.schema.modifiers.AutoincTableModifier;
import com.tesora.dve.sql.schema.modifiers.CharsetTableModifier;
import com.tesora.dve.sql.schema.modifiers.CollationTableModifier;
import com.tesora.dve.sql.schema.modifiers.CommentTableModifier;
import com.tesora.dve.sql.schema.modifiers.CreateOptionModifier;
import com.tesora.dve.sql.schema.modifiers.EngineTableModifier;
import com.tesora.dve.sql.schema.modifiers.RowFormatTableModifier;
import com.tesora.dve.sql.schema.modifiers.TableModifier;
import com.tesora.dve.sql.schema.modifiers.TableModifierTag;
import com.tesora.dve.sql.schema.modifiers.TableModifiers;
import com.tesora.dve.sql.schema.validate.ValidateResult;
import com.tesora.dve.sql.util.Cast;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.ListSet;
import com.tesora.dve.sql.util.UnaryPredicate;
import com.tesora.dve.variables.KnownVariables;
public class PETable extends PEAbstractTable<PETable> implements HasComment {
private List<PEKey> keys;
// duplicated for convenience
private PEKey pk;
private Boolean hasCardInfo = null;
// tables which have fks which refer to this table. used in fk action support.
private ListSet<SchemaCacheKey<PEAbstractTable<?>>> referring;
// when this is persistently loaded the trigger info is the planning version
private EnumMap<TriggerEvent,PETableTriggerEventInfo> triggers;
// table options - this encompasses both those persisted separately and those not.
// for non-new tables (i.e. loaded) this contains the options separately persisted.
TableModifiers modifiers;
private String tableDefinition;
// cache this for the truncate case
private Boolean hasAutoInc = null;
// keep track of our autoinc tracker id to avoid a catalog lookup
private Integer autoIncTrackerID;
private boolean loaded;
// cache object; used to avoid some catalog access issues in the engine
// also used for cta support
protected CachedPETable cached;
public PETable(SchemaContext pc, Name name,
List<TableComponent<?>> fieldsAndKeys, DistributionVector dv, List<TableModifier> modifier,
PEPersistentGroup defStorage, PEDatabase db,
TableState theState) {
super(pc,name,fieldsAndKeys,dv,defStorage,db,theState);
loaded = true;
this.pk = null;
this.referring = new ListSet<SchemaCacheKey<PEAbstractTable<?>>>();
this.keys = new ArrayList<PEKey>();
this.modifiers = new TableModifiers(modifier);
this.triggers = new EnumMap<TriggerEvent,PETableTriggerEventInfo>(TriggerEvent.class);
// do keys & columns first so that database can propagate charset/collation
initializeColumnsAndKeys(pc,fieldsAndKeys,db);
setDatabase(pc,db,false);
autoIncTrackerID = null;
forceStorage(pc);
setPersistent(pc,null,null);
cached = null;
}
@Override
public boolean isTable() {
return true;
}
@Override
public PETable asTable() {
return this;
}
@Override
public PEViewTable asView() {
throw new IllegalStateException("Cannot cast a table to a view");
}
@Override
protected boolean isLoaded() {
return loaded;
}
@Override
protected void setLoaded() {
loaded = true;
}
@SuppressWarnings("unchecked")
private void initializeColumnsAndKeys(SchemaContext pc, List<TableComponent<?>> fieldsAndKeys, PEDatabase db) {
// add in canonical order: columns, nonfks, fks
for(PEColumn p : getColumns(pc,true)) {
if (p.isAutoIncrement())
hasAutoInc = Boolean.TRUE;
}
for(Iterator<TableComponent<?>> iter = fieldsAndKeys.iterator(); iter.hasNext();) {
PEKey ktc = (PEKey) iter.next();
if (ktc.getConstraint() == ConstraintType.FOREIGN)
continue;
addKey(pc, ktc,false,db);
if (ktc.isPrimary()) {
if (this.pk != null)
throw new SchemaException(Pass.SECOND,"Only one primary key per table, please");
this.pk = ktc;
}
ktc.setTable(StructuralUtils.buildEdge(pc,this,false));
iter.remove();
}
for(Iterator<TableComponent<?>> iter = fieldsAndKeys.iterator(); iter.hasNext();) {
PEForeignKey pefk = (PEForeignKey) iter.next();
addKey(pc,pefk,false,db);
}
}
// placeholder table ctor
public PETable(SchemaContext pc, Name name, List<PEColumn> cols, DistributionVector distVect, PEStorageGroup group, PEDatabase pdb) {
super(pc,name,cols,distVect,group,pdb);
loaded = true;
this.pk = null;
this.keys = new ArrayList<PEKey>();
autoIncTrackerID = null;
modifiers = new TableModifiers();
setDatabase(pc,pdb,false);
forceStorage(pc);
setPersistent(pc,null,null);
cached = null;
}
protected PETable(UserTable table, SchemaContext lc) {
super(table,lc);
loaded = false;
lc.startLoading(this, table);
modifiers = new TableModifiers();
modifiers.setModifier(new EngineTableModifier(EngineTableModifier.EngineTag.findEngine(table.getEngine())));
if (table.getCollation() != null)
modifiers.setModifier(new CollationTableModifier(new UnqualifiedName(table.getCollation())));
if (table.getComment() != null)
modifiers.setModifier(new CommentTableModifier(new Comment(table.getComment())));
if (table.getRowFormat() != null)
modifiers.setModifier(new RowFormatTableModifier(new UnqualifiedName(table.getRowFormat())));
loadPersistent(table,lc);
setDatabase(lc,PEDatabase.load(table.getDatabase(), lc),true);
this.tableDefinition = (table.getShape() == null ? null : table.getShape().getTableDefinition());
if (table.hasAutoIncr())
autoIncTrackerID = table.getAutoIncr().getId();
checkLoaded(lc);
cached = new CachedPETable(lc, this);
lc.finishedLoading(this, table);
}
@SuppressWarnings("unchecked")
protected void loadRest(UserTable table, SchemaContext pc) {
for(PEColumn c : getColumns(pc,true)) {
if (c.isAutoIncrement())
hasAutoInc = Boolean.TRUE;
}
this.keys = new ArrayList<PEKey>();
for(Key k : table.getKeys()) {
PEKey pek = PEKey.load(k, pc, this);
if (pek.isPrimary())
pk = pek;
keys.add(pek);
pek.setTable(StructuralUtils.buildEdge(pc,this, true));
}
referring = new ListSet<SchemaCacheKey<PEAbstractTable<?>>>();
for(Key k : table.getReferringKeys()) {
referring.add(PEAbstractTable.getTableKey(k.getTable()));
}
forceStorage(pc);
// load the triggers here
this.triggers = new EnumMap<TriggerEvent,PETableTriggerEventInfo>(TriggerEvent.class);
if (!table.getTriggers().isEmpty()) {
for(UserTrigger ut : table.getTriggers()) {
PETrigger trig = PETrigger.load(ut, pc, this);
addTriggerInternal(trig,true);
}
}
}
private void addTriggerInternal(PETrigger trig,boolean persistent) {
PETableTriggerEventInfo any = triggers.get(trig.getEvent());
if (any == null) {
any = (persistent ? new PETableTriggerPlanningEventInfo() : new PETableTriggerEventInfo());
triggers.put(trig.getEvent(),any);
}
any.set(trig);
}
public void setDeclaration(SchemaContext sc, PETable basedOn) {
super.setDeclaration(sc,basedOn);
tableDefinition = new MysqlEmitter().emitTableDefinition(sc,sc.getValues(),basedOn);
}
public String getDefinition() {
return tableDefinition;
}
public String getTypeHash() {
return buildTypeHash(tableDefinition);
}
public static String buildTypeHash(String in) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] bout = md.digest(PECharsetUtils.getBytes(in, PECharsetUtils.UTF_8));
String out = new String(Hex.encodeHex(bout, true));
return out;
} catch (NoSuchAlgorithmException nsae) {
throw new SchemaException(Pass.PLANNER, "Unable to obtain sha-1 hash of type definition");
}
}
// used in mt mode
public void resetName(SchemaContext sc, UnqualifiedName unq) {
setName(unq);
int counter = 0;
for(PEKey pek : getKeys(sc)) {
if (!pek.isForeign()) continue;
PEForeignKey pefk = (PEForeignKey) pek;
pefk.setPhysicalSymbol(new UnqualifiedName(getName().getUnqualified().getUnquotedName().get() + "_ibfk_" + (++counter)));
}
}
@SuppressWarnings("unchecked")
public void setDatabase(SchemaContext sc, PEDatabase pdb, boolean persistent) {
this.db = StructuralUtils.buildEdge(sc,pdb,persistent);
if (pdb != null && modifiers != null) {
CharsetTableModifier charset = (CharsetTableModifier) modifiers.getModifier(TableModifierTag.DEFAULT_CHARSET);
CollationTableModifier collation = (CollationTableModifier) modifiers.getModifier(TableModifierTag.DEFAULT_COLLATION);
if (charset == null) {
charset = new CharsetTableModifier(new UnqualifiedName(pdb.getCharSet()));
modifiers.setModifier(charset);
}
if (collation == null) {
collation = new CollationTableModifier(new UnqualifiedName(pdb.getCollation()));
modifiers.setModifier(collation);
}
for(PEColumn pec : getColumns(sc)) {
pec.takeCharsetSettings(charset, collation, false);
}
}
}
public boolean isContainerBaseTable(SchemaContext sc) {
checkLoaded(sc);
if (dv == null) return false;
if (!dv.isContainer()) return false;
return dv.getContainer(sc).getBaseTable(sc) == this;
}
@Override
public void setComment(Comment c) {
if (modifiers == null)
modifiers = new TableModifiers();
modifiers.setModifier(new CommentTableModifier(c));
}
@Override
public Comment getComment() {
CommentTableModifier ctm = (CommentTableModifier) modifiers.getModifier(TableModifierTag.COMMENT);
if (ctm == null) return null;
return ctm.getComment();
}
public TableModifiers getModifiers() {
return modifiers;
}
public Long getAutoIncOffset(SchemaContext sc) {
AutoincTableModifier atm = (AutoincTableModifier) modifiers.getModifier(TableModifierTag.AUTOINCREMENT);
if (atm == null) return null;
return atm.getStartAt();
}
public EngineTableModifier getEngine() {
return (EngineTableModifier) modifiers.getModifier(TableModifierTag.ENGINE);
}
public CollationTableModifier getCollation() {
return (CollationTableModifier) modifiers.getModifier(TableModifierTag.DEFAULT_COLLATION);
}
public CharsetTableModifier getCharset() {
return (CharsetTableModifier) modifiers.getModifier(TableModifierTag.DEFAULT_CHARSET);
}
public boolean shouldEmitCharset() {
return true;
}
public boolean shouldEmitCollation(SchemaContext sc) {
CollationTableModifier ctm = getCollation();
if (ctm == null) return false;
PEDatabase ofdb = getPEDatabase(sc);
if (ofdb == null) return false;
return !ctm.getCollation().getUnquotedName().get().equals(ofdb.getCollation());
}
public List<PEKey> getKeys(SchemaContext sc) {
checkLoaded(sc);
return keys;
}
protected List<TableComponent<?>> getKeys() {
return Functional.apply(keys,new Cast<TableComponent<?>,PEKey>());
}
public PEKey lookupKey(SchemaContext sc, Name keyName) {
if (!loaded) return null;
PEKey asConstraint = null;
for(PEKey pek : getKeys(sc)) {
if (pek.getName().equals(keyName))
return pek;
else if (pek.getSymbol() != null && pek.getSymbol().equals(keyName))
asConstraint = pek;
}
return asConstraint;
}
public PEKey addKey(SchemaContext sc, PEKey pek, boolean doSyntheticChecks) {
return addKey(sc, pek, doSyntheticChecks, getPEDatabase(sc));
}
@SuppressWarnings("unchecked")
public PEKey addKey(SchemaContext sc, PEKey pek, boolean doSyntheticChecks, PEDatabase theDB) {
checkLoaded(sc);
PEKey newlyDropped = null;
boolean mtmode = (theDB == null ? false : theDB.getMTMode().isMT());
if (pek.getConstraint() == ConstraintType.FOREIGN) {
if (!mtmode) {
PEForeignKey pefk = (PEForeignKey) pek;
PEKey exists = pefk.findPrefixKey(sc, this);
if (exists == null) {
exists = pefk.buildPrefixKey(sc,this);
addKey(sc,exists,false,theDB);
}
}
} else if (!pek.isSynthetic() && doSyntheticChecks && !mtmode) {
for(PEKey cpek : keys) {
if (!cpek.isSynthetic()) continue;
if (PEKey.samePrefix(cpek, pek)) {
newlyDropped = cpek;
}
}
}
generateNames(sc, pek, mtmode);
// canonical order for keys is:
// any primary key
// all unique keys
// other keys
// foreign keys
if (keys.isEmpty()) {
keys.add(pek);
} else if (pek.getConstraint() == ConstraintType.PRIMARY) {
keys.add(0, pek);
} else if (pek.getConstraint() == ConstraintType.FOREIGN) {
keys.add(pek);
} else {
PEKey thePK = null;
List<PEKey> uniques = new ArrayList<PEKey>();
List<PEKey> regulars = new ArrayList<PEKey>();
List<PEKey> foreigns = new ArrayList<PEKey>();
for(PEKey ipek : keys) {
if (ipek.getConstraint() == ConstraintType.PRIMARY)
thePK = ipek;
else if (ipek.getConstraint() == ConstraintType.UNIQUE)
uniques.add(ipek);
else if (ipek.getConstraint() == ConstraintType.FOREIGN)
foreigns.add(ipek);
else
regulars.add(ipek);
}
if (pek.getConstraint() == ConstraintType.UNIQUE)
uniques.add(pek);
else
regulars.add(pek);
keys.clear();
if (thePK != null)
keys.add(thePK);
keys.addAll(uniques);
keys.addAll(regulars);
keys.addAll(foreigns);
}
pek.setTable(StructuralUtils.buildEdge(sc,this, false));
return newlyDropped;
}
public void generateNames(SchemaContext sc, PEKey pek, boolean mtMode) {
generateKeyName(sc, pek);
if (pek.getConstraint() == null) {
} else if (pek.getSymbol() != null) {
} else if (pek.getConstraint() == ConstraintType.PRIMARY) {
pek.setSymbol(new UnqualifiedName("PRIMARY"));
} else if (pek.getConstraint() == ConstraintType.UNIQUE) {
pek.setSymbol(pek.getName());
} else if (pek.getConstraint() == ConstraintType.FOREIGN) {
pek.setSymbol(buildFKSymbol(sc));
}
if (pek.getConstraint() == ConstraintType.FOREIGN && mtMode) {
PEForeignKey pefk = (PEForeignKey) pek;
pefk.setPhysicalSymbol(buildFKSymbol(sc));
}
}
private void generateKeyName(SchemaContext sc, PEKey pek) {
if (pek.getName() == null) {
if (pek.isPrimary()) {
pek.setName(new UnqualifiedName("PRIMARY"));
pek.setSymbol(new UnqualifiedName("PRIMARY"));
} else if (!pek.isForeign()) {
Name candidate = pek.getKeyColumns().get(0).getColumn().getName();
Name nn = null;
int counter = 0;
while(nn == null) {
nn = (counter == 0 ? candidate : new UnqualifiedName(candidate.getUnqualified().getUnquotedName().get() + "_" + counter,true));
for(PEKey opek : keys) {
if (opek.getName().getUnquotedName().equals(nn.getUnquotedName())) {
nn = null;
counter++;
break;
}
}
}
pek.setName(nn);
} else {
if (pek.getSymbol() != null) {
pek.setName(pek.getSymbol());
} else {
Name symbol = buildFKSymbol(sc);
pek.setName(symbol);
pek.setSymbol(symbol);
}
if (isDuplicateFKSymbol(sc, pek.getSymbol().get()))
throw new SchemaException(Pass.SECOND, "Duplicate foreign key name: " + pek.getSymbol().get());
}
} else {
if (pek.getConstraint() == ConstraintType.FOREIGN && pek.getSymbol() != null) {
if (isDuplicateFKSymbol(sc, pek.getSymbol().get()))
throw new SchemaException(Pass.SECOND, "Duplicate foreign key name: " + pek.getSymbol().get());
for(PEKey p : getKeys(sc)) {
if (p.getConstraint() != ConstraintType.FOREIGN) {
continue;
}
if (p.getSymbol().equals(pek.getSymbol()))
throw new SchemaException(Pass.SECOND, "Duplicate foreign key name: " + pek.getSymbol().get());
}
} else {
for(PEKey p : getKeys(sc)) {
if (p.getConstraint() == ConstraintType.FOREIGN) {
continue;
}
if (p.getName().equals(pek.getName()))
throw new SchemaException(Pass.SECOND, "Duplicate key name: " + pek.getName());
}
}
}
}
private UnqualifiedName buildFKSymbol(SchemaContext sc) {
// foreign keys follow tablename_ibfk_n; the _n is the number of fks
int n = getForeignKeys(sc).size() + 1;
return new UnqualifiedName(getName().getUnqualified().getUnquotedName().get() + "_ibfk_" + n);
}
private boolean isDuplicateFKSymbol(SchemaContext sc, String symbol) {
boolean ret = false;
try {
Long tenid = sc.getPolicyContext().getTenantID(false);
Integer tenantID = (tenid == null ? null : tenid.intValue());
PEKey fKey = sc.findForeignKey(hasDatabase(sc) ? getDatabase(sc) : null, tenantID, null, symbol);
if (fKey != null) {
PETable t = fKey.getTable(sc);
if (t != null && !t.equals(this))
ret = true;
}
} catch(Exception e) {
ret = false;
}
return ret;
}
public void removeKey(SchemaContext sc, PEKey pek) {
checkLoaded(sc);
// also, at this point, clear the key flags on the columns
// but only if the column is now truly not a key
MultiMap<PEColumn,PEKey> colByKey = new MultiMap<PEColumn,PEKey>();
for(PEKey k : keys) {
if (k.isForeign()) continue; // doesn't matter
for(PEKeyColumnBase pekc : k.getKeyColumns()) {
colByKey.put(pekc.getColumn(), k);
}
}
for(PEColumn pec : colByKey.keySet()) {
Collection<PEKey> vals = colByKey.get(pec);
vals.remove(pek);
// now look at the remaining keys, and figure out the key parts
boolean isPrimary = false;
boolean isUnique = false;
boolean isKey = !vals.isEmpty();
for(PEKey ipek : vals) {
if (ipek.getConstraint() == ConstraintType.PRIMARY)
isPrimary = true;
if (ipek.getConstraint() == ConstraintType.UNIQUE)
isUnique = true;
}
if (!isPrimary)
pec.clearPrimaryKeyPart();
if (!isUnique)
pec.clearUniqueKeyPart();
if (!isKey)
pec.clearKeyPart();
}
keys.remove(pek);
}
public int getOffsetOf(SchemaContext sc, PEKey pek) {
for(int i = 0; i < keys.size(); i++) {
PEKey me = keys.get(i);
if (me.getConstraint() == pek.getConstraint() && me.getName().equals(pek.getName()))
return i+1;
}
throw new SchemaException(Pass.PLANNER, "Cannot find key " + pek + " within table " + getName());
}
public List<PEForeignKey> getForeignKeys(SchemaContext sc) {
List<PEKey> all = getKeys(sc);
List<PEForeignKey> out = new ArrayList<PEForeignKey>();
for(PEKey pek : all)
if (pek.isForeign())
out.add((PEForeignKey) pek);
return out;
}
public void removeForeignKeys(SchemaContext sc) {
for(PEForeignKey fk : getForeignKeys(sc)) {
removeKey(sc, fk);
}
}
public List<PEKey> getUniqueKeys(SchemaContext sc) {
List<PEKey> all = getKeys(sc);
return Functional.select(all, new UnaryPredicate<PEKey>() {
@Override
public boolean test(PEKey object) {
if (object.isForeign()) return false;
if (!object.isUnique()) return false;
return true;
}
});
}
public PEKey getPrimaryKey(SchemaContext sc) {
checkLoaded(sc);
return pk;
}
public PEKey getUniqueKey(SchemaContext sc) {
checkLoaded(sc);
if (pk != null) return pk;
for(PEKey pek : getKeys(sc)) {
if (pek.isUnique())
return pek;
}
return null;
}
public boolean hasUniqueKey(SchemaContext sc) {
return (getUniqueKey(sc) != null);
}
public boolean isPrimaryKeyPart(SchemaContext sc, PEColumn c) {
checkLoaded(sc);
if (pk == null) return false;
return pk.containsColumn(c);
}
public ListSet<SchemaCacheKey<PEAbstractTable<?>>> getReferencingTables() {
return referring;
}
public void addTrigger(SchemaContext sc, PETrigger trig) {
checkLoaded(sc);
addTriggerInternal(trig, false);
}
public void removeTrigger(SchemaContext sc, PETrigger trig) {
checkLoaded(sc);
PETableTriggerEventInfo any = triggers.get(trig.getEvent());
if (any == null) return;
any.remove(trig);
}
@Override
public boolean collectDifferences(SchemaContext sc, List<String> messages, Persistable<PETable, UserTable> oth,
boolean first, @SuppressWarnings("rawtypes") Set<Persistable> visited) {
PETable other = oth.get();
if (visited.contains(this) && visited.contains(other)) {
return false;
}
visited.add(this);
visited.add(other);
if (maybeBuildDiffMessage(sc,messages, "name", getName(), other.getName(), first, visited))
return true;
if ((dv == null ? 1 : 0) != (other.getDistributionVector(sc) == null ? 1 : 0)) {
if (dv == null)
messages.add("Extra distribution vector present");
else
messages.add("Distribution vector missing.");
if (first)
return true;
} else if (dv != null && other != null &&
dv.collectDifferences(sc, messages, other.getDistributionVector(sc), first, visited))
return true;
if (compareColumns(sc,messages,other,first,visited))
return true;
if (compareKeys(sc,messages,other,first,visited))
return true;
return false;
}
private boolean compareColumns(SchemaContext sc, List<String> messages, PETable other,
boolean first, @SuppressWarnings("rawtypes") Set<Persistable> visited) {
if (maybeBuildDiffMessage(sc,messages, "number of columns", getColumns(sc).size(), other.getColumns(sc).size(), first, visited))
return true;
Iterator<PEColumn> leftIter = getColumns(sc).iterator();
Iterator<PEColumn> rightIter = other.getColumns(sc).iterator();
while(leftIter.hasNext() && rightIter.hasNext()) {
PEColumn lc = leftIter.next();
PEColumn rc = rightIter.next();
if (lc.collectDifferences(sc,messages, rc, first, visited))
return true;
}
return false;
}
private boolean compareKeys(SchemaContext sc, List<String> messages, PETable other,
boolean first, @SuppressWarnings("rawtypes") Set<Persistable> visited) {
if (maybeBuildDiffMessage(sc,messages, "number of keys", getKeys(sc).size(), other.getKeys(sc).size(), first, visited))
return true;
Iterator<PEKey> leftIter = getKeys(sc).iterator();
Iterator<PEKey> rightIter = getKeys(sc).iterator();
while(leftIter.hasNext() && rightIter.hasNext()) {
PEKey lk = leftIter.next();
PEKey rk = rightIter.next();
if (lk.collectDifferences(sc, messages, rk, first, visited))
return true;
}
return false;
}
@Override
protected String getDiffTag() {
return "Table";
}
@Override
public String differs(SchemaContext sc, Persistable<PETable, UserTable> other, boolean first) {
checkLoaded(sc);
return super.differs(sc,other, first);
}
public String definitionDiffers(PETable other) {
String mine = getDefinition();
String yours = other.getDefinition();
if (mine != null && yours != null) {
if (!mine.equals(yours)) {
return "table definitions differ";
}
return null;
} else if (mine == null && yours == null) {
return null;
} else {
return "missing table definition";
}
}
@Override
public String toString() {
final Name n = getName();
return (n != null) ? n.get() : String.valueOf(n);
}
@Override
protected void populateNew(SchemaContext pc,UserTable p) throws PEException {
super.populateNew(pc,p);
for(PEKey pek : getKeys(pc)) {
p.addKey(pek.persistTree(pc));
}
setModifiers(pc,p);
}
@Override
protected Persistable<PETable, UserTable> load(SchemaContext pc, UserTable p)
throws PEException {
return new PETable(p,pc);
}
private void setModifiers(SchemaContext pc, UserTable p) throws PEException {
CollationTableModifier collation = (CollationTableModifier) modifiers.getModifier(TableModifierTag.DEFAULT_COLLATION);
EngineTableModifier etm = (EngineTableModifier) modifiers.getModifier(TableModifierTag.ENGINE);
RowFormatTableModifier rtfm = (RowFormatTableModifier) modifiers.getModifier(TableModifierTag.ROW_FORMAT);
CommentTableModifier ctm = (CommentTableModifier) modifiers.getModifier(TableModifierTag.COMMENT);
if (collation != null)
p.setCollation(collation.getCollation().getUnquotedName().get());
if (rtfm != null)
p.setRowFormat(rtfm.getRowFormat().getSQL());
if (etm != null)
p.setEngine(etm.getPersistent());
if (ctm != null)
p.setComment(ctm.getComment().getComment());
p.setCreateOptions(CreateOptionModifier.build(CreateOptionModifier.combine(CreateOptionModifier.decode(p.getCreateOptions()), modifiers)));
}
@Override
protected void updateExisting(SchemaContext pc, UserTable ut) throws PEException {
super.updateExisting(pc,ut);
updateExistingKeys(pc,ut);
updateExistingTriggers(pc,ut);
setModifiers(pc,ut);
}
private static void updateColumnPositions(final SchemaContext sc, final PEKey source, final PEKey in) {
final List<PEColumn> updated = in.getColumns(sc);
for (final PEColumn sourceCol : source.getColumns(sc)) {
final int index = updated.indexOf(sourceCol);
if (index > -1) {
final PEColumn updatedCol = updated.get(index);
updatedCol.setPosition(sourceCol.getPosition());
}
}
}
protected void updateExistingKeys(SchemaContext pc, UserTable ut) throws PEException {
HashMap<String, Key> persKeys = new HashMap<String, Key>();
HashMap<String, Key> persCons = new HashMap<String, Key>();
HashMap<String, PEKey> transKeys = new HashMap<String, PEKey>();
HashMap<String, PEForeignKey> transCons = new HashMap<String, PEForeignKey>();
for(PEKey c : getKeys(pc)) {
if (c.isForeign())
transCons.put(c.getName().getCapitalized().get(), (PEForeignKey)c);
else
transKeys.put(c.getName().getCapitalized().get(), c);
}
for(Key uc : ut.getKeys()) {
String name = uc.getName().toUpperCase().trim();
if (uc.isForeignKey()) {
PEForeignKey was = transCons.remove(name);
boolean same = (was != null);
if (same) {
PEForeignKey apc = PEForeignKey.load(uc, pc, null);
updateColumnPositions(pc, was, apc);
String anydiffs = was.differs(pc,apc, true);
if (anydiffs != null) {
same = false;
transCons.put(name, was);
}
}
if (!same)
persCons.put(name,uc);
} else {
PEKey was = transKeys.remove(name);
boolean same = (was != null);
if (same) {
PEKey apc = PEKey.load(uc, pc, null);
updateColumnPositions(pc, was, apc);
String anydiffs = was.differs(pc,apc, true);
if (anydiffs != null) {
same = false;
transKeys.put(name, was);
}
}
if (!same)
persKeys.put(name,uc);
}
}
// now transCols has columns not in persCols, and persCols has columns not in transCols
// the former are additions, the latter are removals
for(Key uc : persCons.values()) {
ut.removeKey(uc);
}
for(Key uc : persKeys.values()) {
ut.removeKey(uc);
}
pc.beginSaveContext();
try {
for(PEKey c : transKeys.values()) {
ut.addKey(c.persistTree(pc));
}
for(PEForeignKey c : transCons.values()) {
ut.addKey(c.persistTree(pc));
}
} finally {
pc.endSaveContext();
}
}
protected void updateExistingTriggers(SchemaContext sc, UserTable ut) throws PEException {
HashMap<String, UserTrigger> persistent = new HashMap<String,UserTrigger>();
HashMap<String, PETrigger> trans = new HashMap<String,PETrigger>();
for(PETableTriggerEventInfo trig : triggers.values()) {
for(PETrigger pet : trig.get()) {
trans.put(pet.getName().getUnqualified().getUnquotedName().get(),pet);
}
}
for(UserTrigger trig : ut.getTriggers())
persistent.put(trig.getName(),trig);
// anything that exists in persistent but not in trans has been deleted
// anything that exists in trans but not persistent has been added
Set<String> persTrigNames = persistent.keySet();
Set<String> transTrigNames = trans.keySet();
if (persTrigNames.equals(transTrigNames)) return; // nothing to do
if (persTrigNames.size() < transTrigNames.size()) {
// added
transTrigNames.removeAll(persTrigNames);
for(String s : transTrigNames) {
ut.getTriggers().add(trans.get(s).persistTree(sc));
}
} else {
// dropped
persTrigNames.removeAll(transTrigNames);
for(String s : persTrigNames) {
ut.getTriggers().remove(trans.get(s).persistTree(sc));
}
}
}
public boolean hasAutoInc() {
return (Boolean.TRUE.equals(hasAutoInc));
}
private void forceStorage(SchemaContext pc) {
EngineTableModifier etm = (EngineTableModifier) modifiers.getModifier(TableModifierTag.ENGINE);
if (etm == null) {
etm = new EngineTableModifier(KnownVariables.STORAGE_ENGINE.getSessionValue(pc.getConnection().getVariableSource()));
modifiers.setModifier(etm);
}
}
public void alterModifier(TableModifier tm) {
if (tm.getKind() == null)
throw new SchemaException(Pass.PLANNER, "Unknown table modifier kind, unable to alter");
modifiers.setModifier(tm);
}
// we don't store a complete orig decl in any one place, but instead recreate it
// this function does that - take the decl and apply any changes (autoincs, nonpersisted fks, synthetic keys)
// to the decl then return a new tschema copy
@Override
public PETable recreate(SchemaContext sc, String decl, LockInfo li) {
PETable tschemaVersion = super.recreate(sc,decl, li).asTable();
boolean mtmode = tschemaVersion.getPEDatabase(sc).getMTMode().isMT();
// correctly
boolean mod = false;
for(PEColumn c : getColumns(sc)) {
if (c.isAutoIncrement()) {
PEColumn ntc = (PEColumn) c.getIn(sc,tschemaVersion);
ntc.makeAutoincrement();
mod = true;
} else if (c.getType().isBinaryText()) {
// we don't store this in the declaration, but it is in the catalog
PEColumn ntc = c.getIn(sc, tschemaVersion);
ntc.makeBinaryText();
}
if (!c.hasDefault()) {
// make sure the rebuilt column has no default as well
PEColumn ntc = (PEColumn) c.getIn(sc, tschemaVersion);
if (ntc == null) continue;
ntc.setDefaultValue(null);
}
}
if (mtmode) {
HashMap<UnqualifiedName,PEForeignKey> tfks = new HashMap<UnqualifiedName,PEForeignKey>();
for(PEKey pek : tschemaVersion.getKeys(sc)) {
if (!pek.isForeign()) continue;
PEForeignKey pefk = (PEForeignKey) pek;
tfks.put(pefk.getPhysicalSymbol(), pefk);
}
for(PEKey pek : getKeys(sc)) {
if (!pek.isForeign()) continue;
PEForeignKey pefk = (PEForeignKey) pek;
PEForeignKey tfk = tfks.get(pefk.getPhysicalSymbol());
if (tfk != null) {
tfk.setSymbol(pefk.getSymbol());
tfk.setPhysicalSymbol(pefk.getPhysicalSymbol());
mod = true;
}
}
}
if (mod)
tschemaVersion.setDeclaration(sc,tschemaVersion);
// there is nothing in the create tbl stmt that can indicate that a key is synthetic, so if
// we have any synthetic keys in this table, so mark them in the recreated table
// also add in any fks that we don't actually persist - note that we have to make the order match, so we will
// mess around with the list of keys directly in the tschema version
List<PEKey> myKeys = getKeys(sc);
for(int i = 0; i < myKeys.size(); i++) {
PEKey pek = myKeys.get(i);
if (pek.isSynthetic()) {
PEKey npek = pek.getIn(sc, tschemaVersion);
npek.setSynthetic();
} else if (pek.isForeign()) {
PEForeignKey pefk = (PEForeignKey) pek;
if (!pefk.isPersisted()) {
// need to copy it in
PEForeignKey npefk = (PEForeignKey) pefk.copy(sc, tschemaVersion);
npefk.setPersisted(false);
tschemaVersion.keys.add(i, npefk);
}
}
}
return tschemaVersion;
}
public Integer getAutoIncrTrackerID() {
return autoIncTrackerID;
}
@Override
public void checkValid(SchemaContext sc, List<ValidateResult> acc) {
for(PEKey pek : getKeys(sc)) {
if (pek.isForeign()) {
PEForeignKey pefk = (PEForeignKey) pek;
pefk.checkValid(sc, acc);
}
}
}
public static boolean valid(UserTable ut) {
if (ut.getReferringKeys() == null)
return false;
if (ut.getKeys() == null)
return false;
if (!ut.getReferringKeys().isEmpty())
ut.getReferringKeys().iterator().next();
if (!ut.getKeys().isEmpty())
ut.getKeys().iterator().next();
return true;
}
@Override
public String getTableType() {
return PEConstants.DEFAULT_TABLE_TYPE;
}
public PersistentTable getPersistentTable(SchemaContext sc) {
return cached;
}
protected static class CachedPETable implements PersistentTable {
private final PETable table;
private final PersistentDatabase db;
private final StorageGroup pg;
private final PersistentContainer container;
private final Integer rangeID;
public CachedPETable(SchemaContext sc, PETable tab) {
this.table = tab;
this.db = tab.getPEDatabase(sc);
this.container = tab.getDistributionVector(sc).getContainer(sc);
if (tab.getPersistentStorage(sc) == null)
this.pg = null;
else
this.pg = tab.getPersistentStorage(sc).getScheduledGroup(sc,sc.getValues());
rangeID = tab.getDistributionVector(sc).getRangeID(sc);
}
@Override
public String displayName() {
return getPersistentName();
}
@Override
public String getNameAsIdentifier() {
return Singletons.require(DBNative.class).getNameForQuery(this);
}
@Override
public String getPersistentName() {
return table.getName().getUnqualified().getUnquotedName().get();
}
@Override
public String getQualifiedName() {
if (db == null)
return getPersistentName();
return db.getUserVisibleName() + "." + getPersistentName();
}
@Override
public int getNumberOfColumns() {
return table.getColumns(null).size();
}
@Override
public KeyValue getDistValue(CatalogDAO c) {
KeyValue dv = new KeyValue(this,rangeID);
TreeMap<Integer,PersistentColumn> sorted =new TreeMap<Integer,PersistentColumn>();
for(PEColumn pec : table.getColumns(null))
if (pec.getHashPosition() > 0)
sorted.put(pec.getHashPosition(),pec);
for(PersistentColumn pc : sorted.values())
dv.addColumnTemplate(pc);
return dv;
}
@Override
public StorageGroup getPersistentGroup() {
return pg;
}
@Override
public DistributionModel getDistributionModel() {
return table.dv.getModel().getSingleton();
}
@Override
public int getId() {
if (table.getPersistentID() == null)
return 0;
return table.getPersistentID();
}
@Override
public PersistentContainer getContainer() {
return container;
}
@Override
public PersistentDatabase getDatabase() {
return db;
}
@Override
public PersistentColumn getUserColumn(String name) {
return table.lookup(null, name);
}
@Override
public Integer getRangeID(CatalogDAO c) throws PEException {
return rangeID;
}
}
public long getTableSizeEstimate(SchemaContext sc) {
for(PEKey pek : getUniqueKeys(sc)) {
long card = pek.getCardinality();
if (card > -1) return card;
}
return -1;
}
@Override
public boolean hasCardinalityInfo(SchemaContext sc) {
if (hasCardInfo == null)
hasCardInfo = computeCardInfo(sc);
return hasCardInfo.booleanValue();
}
private boolean computeCardInfo(SchemaContext sc) {
for(PEKey pek : getKeys(sc)) {
if (pek.isForeign()) continue;
if (pek.getCardinality() == -1) return false;
}
return true;
}
public boolean isExplicitlyDeclared() {
return false;
}
public boolean mustBeCreated() {
return false;
}
public boolean hasTrigger(SchemaContext sc, TriggerEvent et) {
return getTriggers(sc,et) != null;
}
public boolean hasTriggers() {
return ((triggers != null) && !triggers.isEmpty());
}
public PETableTriggerEventInfo getTriggers(SchemaContext sc, TriggerEvent et) {
if (!hasTriggers()) return null;
return triggers.get(et);
}
public Set<PETrigger> getAllTriggers(SchemaContext sc) {
if (hasTriggers()) {
final Set<PETrigger> allTriggers = new LinkedHashSet<PETrigger>();
for (final TriggerEvent e : TriggerEvent.values()) {
final PETableTriggerEventInfo trigger = triggers.get(e);
if (trigger != null) {
allTriggers.addAll(trigger.get());
}
}
}
return Collections.EMPTY_SET;
}
}