package com.tesora.dve.sql.statement.ddl; /* * #%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.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang.StringUtils; import com.tesora.dve.common.MultiMap; import com.tesora.dve.common.catalog.CatalogDAO; import com.tesora.dve.common.catalog.CatalogEntity; import com.tesora.dve.common.catalog.UserTable; import com.tesora.dve.exceptions.PEException; import com.tesora.dve.queryplan.QueryStepDDLGeneralOperation.DDLCallback; import com.tesora.dve.server.connectionmanager.SSConnection; import com.tesora.dve.server.messaging.SQLCommand; import com.tesora.dve.sql.ParserException.Pass; import com.tesora.dve.sql.SchemaException; import com.tesora.dve.sql.expression.TableKey; import com.tesora.dve.sql.schema.ComplexPETable; import com.tesora.dve.sql.schema.Name; import com.tesora.dve.sql.schema.PEAbstractTable; import com.tesora.dve.sql.schema.PEContainer; import com.tesora.dve.sql.schema.PEDatabase; import com.tesora.dve.sql.schema.PEForeignKey; import com.tesora.dve.sql.schema.PETable; import com.tesora.dve.sql.schema.PETrigger; import com.tesora.dve.sql.schema.QualifiedName; import com.tesora.dve.sql.schema.SchemaContext; import com.tesora.dve.sql.schema.cache.CacheInvalidationRecord; import com.tesora.dve.sql.schema.cache.InvalidationScope; import com.tesora.dve.sql.schema.cache.SchemaCacheKey; import com.tesora.dve.sql.statement.StatementType; import com.tesora.dve.sql.transform.behaviors.BehaviorConfiguration; import com.tesora.dve.sql.transform.execution.ComplexDDLExecutionStep; import com.tesora.dve.sql.transform.execution.EmptyExecutionStep; import com.tesora.dve.sql.transform.execution.ExecutionSequence; import com.tesora.dve.sql.transform.execution.ExecutionStep; import com.tesora.dve.sql.util.ListOfPairs; import com.tesora.dve.worker.WorkerGroup; public class PEDropTableStatement extends PEDropStatement<PETable, UserTable> { protected Set<CatalogEntity> deletes = null; protected Set<CatalogEntity> updates = null; protected Set<PEAbstractTable<?>> referenced = null; private List<TableKey> tableKeys; private List<Name> unknownTables; private CacheInvalidationRecord cacheInvalidationRecord = null; private boolean temporary; public PEDropTableStatement(SchemaContext sc, List<TableKey> tks, List<Name> unknownTbls, Boolean ifExists, boolean temporary) { super(PETable.class, ifExists, false, (tks.isEmpty() ? unknownTbls.get(0) : tks.get(0).getAbstractTable().getName(sc,sc.getValues())), "TABLE"); this.tableKeys = new ArrayList<TableKey>(tks); this.unknownTables = unknownTbls; this.temporary = temporary; } public PEDropTableStatement(PEDropTableStatement base) { super(PETable.class, base.isIfExists(), false, base.getTarget(), "TABLE"); this.tableKeys = new ArrayList<TableKey>(base.tableKeys); this.unknownTables = new ArrayList<Name>(base.unknownTables); this.temporary = base.temporary; } @Override public PETable getTarget() { if (tableKeys.isEmpty()) return null; return tableKeys.get(0).getAbstractTable().asTable(); } public List<TableKey> getDroppedTableKeys() { return tableKeys; } public boolean isTemporary() { return temporary; } @Override public PEDatabase getDatabase(SchemaContext pc) { if (getRoot() != null) return ((PETable)getRoot()).getPEDatabase(pc); return super.getDatabase(pc); } @Override public List<CatalogEntity> getCatalogObjects(SchemaContext pc) throws PEException { compute(pc,false); return new ArrayList<CatalogEntity>(updates); } @Override public List<CatalogEntity> getDeleteObjects(SchemaContext pc) throws PEException { compute(pc,false); return new ArrayList<CatalogEntity>(deletes); } @Override protected void preplan(SchemaContext pc, ExecutionSequence es,boolean explain) throws PEException { if (temporary) return; super.preplan(pc, es, explain); } protected static void compute(SchemaContext pc, Set<CatalogEntity> deletes, Set<CatalogEntity> updates, List<TableKey> keys, Set<PEAbstractTable<?>> referencedTables, boolean ignoreFKChecks) throws PEException { if (keys.isEmpty()) return; if (!deletes.isEmpty()) return; for(TableKey tk : keys) { PETable tab = tk.getAbstractTable().asTable(); appendChildTriggersToCollection(pc, tab, deletes); List<PEForeignKey> effectedForeignKeys = new ArrayList<PEForeignKey>(); if (!tk.isUserlandTemporaryTable()) checkForeignKeys(pc, tab, effectedForeignKeys, referencedTables, ignoreFKChecks); pc.beginSaveContext(); try { if (tk.isUserlandTemporaryTable()) { // for userland temp tables, we only need to toss out the temporary table record deletes.addAll(pc.getCatalog().findUserlandTemporaryTable(pc.getConnection().getConnectionId(), tab.getDatabaseName(pc).getUnquotedName().get(), tab.getName().getUnquotedName().get())); } else { deletes.add(tab.persistTree(pc)); if (tab.isContainerBaseTable(pc)) { PEContainer cont = tab.getDistributionVector(pc).getContainer(pc); List<UserTable> anyTabs = pc.getCatalog().findContainerMembers(cont.getName().get()); if (anyTabs.size() == 1) { // last table - this is ok cont.setBaseTable(pc, null); updates.add(cont.persistTree(pc)); // we're also going to delete all of the container tenants try { deletes.addAll(pc.getCatalog().getDAO().findContainerTenants(cont.getPersistent(pc))); } catch (Exception e) { throw new PEException("Unable to find container tenants of container " + cont.getName(),e); } } else { // more than one table left - not ok throw new SchemaException(Pass.PLANNER, "Unable to drop table " + tab.getName().getSQL() + " because it is the base table to container " + cont.getName().getSQL() + " which is not empty"); } } for(PEForeignKey pefk : effectedForeignKeys) { // this should be persisting the whole table, not the key updates.add(pefk.persistTree(pc)); } } } finally { pc.endSaveContext(); } } } private static void appendChildTriggersToCollection(final SchemaContext pc, final PETable parent, final Set<CatalogEntity> collection) { for (final PETrigger trigger : parent.getAllTriggers(pc)) { collection.add(trigger.getPersistent(pc)); } } protected void compute(SchemaContext pc, boolean ignoreFKChecks) throws PEException { if (tableKeys.isEmpty()) return; if (deletes != null) return; deletes = new HashSet<CatalogEntity>(); updates = new HashSet<CatalogEntity>(); referenced = new HashSet<PEAbstractTable<?>>(); compute(pc,deletes,updates,tableKeys,referenced, ignoreFKChecks); } protected static void checkForeignKeys(SchemaContext pc, PETable targetTable, List<PEForeignKey> updatedKeys, Set<PEAbstractTable<?>> referencedTables, boolean ignoreFKChecks) { emitReferencedTables(pc, targetTable, referencedTables); MultiMap<PETable, PEForeignKey> referencing = pc.findFKSReferencing(targetTable); if (!referencing.isEmpty()) { // otherwise we have to fix up the pertinent fks to use strings instead of fk refs for(PEForeignKey pefk : referencing.values()) { pefk.revertToForward(pc); updatedKeys.add(pefk); } } } private static void emitReferencedTables(final SchemaContext pc, final PETable targetTable, final Set<PEAbstractTable<?>> container) { for (final PEForeignKey fk : targetTable.getForeignKeys(pc)) { final PEAbstractTable<?> child = fk.getTargetTable(pc); // The child may have already been dropped in a multi-table statement. if (child != null) { container.add(child); } } } @Override public StatementType getStatementType() { return StatementType.DROP_TABLE; } @Override public boolean filterStatement(SchemaContext pc) { if (tableKeys.isEmpty()) return false; for(TableKey tk : tableKeys) { PEAbstractTable<?> tbl = tk.getAbstractTable(); if (tbl != null) { if (pc.getConnection().isFilteredTable( new QualifiedName( tbl.getDatabase(pc).getName().getUnquotedName().getUnqualified(), tbl.getName().getUnquotedName().getUnqualified()))) { return true; } } } return false; } @Override public void plan(SchemaContext pc,ExecutionSequence es, BehaviorConfiguration config) throws PEException { if (!tableKeys.isEmpty()) es.append(buildStep(pc)); else es.append(new EmptyExecutionStep(0,"already dropped: " + getSQL(pc))); } @Override protected ExecutionStep buildStep(SchemaContext pc) throws PEException { final List<CatalogEntity> updates = getCatalogObjects(pc); final List<CatalogEntity> deletes = getDeleteObjects(pc); return new ComplexDDLExecutionStep(getDatabase(pc), getStorageGroup(pc), getRoot(), getAction(), new DropTableCallback(pc,getSQLCommand(pc),getInvalidationRecord(pc), updates,deletes)); } @Override public CacheInvalidationRecord getInvalidationRecord(SchemaContext sc) { if (tableKeys.isEmpty()) return null; if (cacheInvalidationRecord != null) return cacheInvalidationRecord; ListOfPairs<SchemaCacheKey<?>,InvalidationScope> actions = new ListOfPairs<SchemaCacheKey<?>,InvalidationScope>(); for(TableKey tk : tableKeys) { if (!tk.isUserlandTemporaryTable()) { actions.add(tk.getAbstractTable().getCacheKey(), InvalidationScope.CASCADE); } } for (final PEAbstractTable<?> child : referenced) { actions.add(child.getCacheKey(), InvalidationScope.LOCAL); } cacheInvalidationRecord = new CacheInvalidationRecord(actions); return cacheInvalidationRecord; } private class DropTableCallback extends DDLCallback { private final SQLCommand command; private final CacheInvalidationRecord record; private final List<CatalogEntity> updates; private final List<CatalogEntity> deletes; private final SchemaContext context; public DropTableCallback(SchemaContext ctxt, SQLCommand theCommand, CacheInvalidationRecord theRecord, List<CatalogEntity> updates, List<CatalogEntity> deletes) { this.command = theCommand; this.record = theRecord; this.updates = updates; this.deletes = deletes; this.context = ctxt; } @Override public SQLCommand getCommand(CatalogDAO c) { return command; } @Override public String description() { return command.getRawSQL(); } @Override public CacheInvalidationRecord getInvalidationRecord() { return record; } public void postCommitAction(CatalogDAO c) throws PEException { if (unknownTables != null && unknownTables.size()>0) { if (!isIfExists()) { throw new PEException("Unknown table '" + StringUtils.join(unknownTables, ",") + "'"); } else { // set warning count to number of unknown tables if we only could... } } } public List<CatalogEntity> getUpdatedObjects() throws PEException { return updates; } public List<CatalogEntity> getDeletedObjects() throws PEException { return deletes; } public void onCommit(SSConnection conn, CatalogDAO c, WorkerGroup wg) { for(TableKey tk : tableKeys) { if (tk.isUserlandTemporaryTable()) { wg.clearPinned(); context.getTemporaryTableSchema().removeTable(context, (ComplexPETable) tk.getAbstractTable()); if (context.getTemporaryTableSchema().isEmpty()) conn.releaseInflightTemporaryTablesLock(); } } } } }