com.tesora.dve.sql.schema.PETable.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.sql.schema.PETable.java

Source

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;
    }

}