package com.tesora.dve.common.catalog; /* * #%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.sql.Types; import java.util.*; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; import com.tesora.dve.common.PECollectionUtils; import com.tesora.dve.common.PEStringUtils; import com.tesora.dve.db.DBEmptyTextResultConsumer; import com.tesora.dve.distribution.*; import com.tesora.dve.exceptions.PECodingException; import com.tesora.dve.exceptions.PEException; import com.tesora.dve.membership.GroupTopicPublisher; import com.tesora.dve.groupmanager.PurgeWorkerGroupCaches; import com.tesora.dve.queryplan.ExecutionState; import com.tesora.dve.resultset.ColumnSet; import com.tesora.dve.resultset.ResultRow; import com.tesora.dve.server.connectionmanager.SSConnection; import com.tesora.dve.server.messaging.GetWorkerRequest; import com.tesora.dve.server.messaging.SQLCommand; import com.tesora.dve.server.messaging.WorkerExecuteRequest; import com.tesora.dve.singleton.Singletons; import com.tesora.dve.sql.schema.PEPersistentGroup.TStorageGroup; 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.variables.KnownVariables; import com.tesora.dve.worker.WorkerGroup; import com.tesora.dve.worker.WorkerGroup.MappingSolution; import com.tesora.dve.worker.WorkerManager; import com.tesora.dve.worker.WorkerGroup.WorkerGroupFactory; /** * A <b>StorageGroup</b> is a collection of instances of {@link PersistentSite} * used to represent the sites which hold a set of data relative to a * {@link DistributionModel}. * <p/> * The <b>StorageGroup</b> may be persisted in the catalog, * or it may be a non-persistent <b>StorageGroup</b>, for example when * selecting a subset of a <b>StorageGroup</b> to map data to as an * intermediate step in a {@link com.tesora.dve.queryplan.QueryPlan}. * */ @Entity @Table(name="persistent_group") public class PersistentGroup implements CatalogEntity, StorageGroup { private static final long serialVersionUID = 1L; public static final String TEMP_NAME = "TempGroup"; public static final String PERSISTENT_GROUP_CS_HEADER_VAL = "Persistent Group"; public static final String PERSISTENT_GROUP_SUPPRESS_CACHE_PURGE = "PersistentGroup.suppressCachePurge"; @Id @GeneratedValue @Column( name="persistent_group_id" ) int id; @Column(name="name", unique=true, nullable=false) String name; @OneToMany(mappedBy="storageGroup", cascade=CascadeType.ALL, fetch=FetchType.EAGER) @OrderBy("version ASC") List<StorageGroupGeneration> generations; public PersistentGroup() { } /** * Constructor to create a <b>StorageGroup</b> with the given <em>name</em>, * typically a persistent <b>StorageGroup</b>. * * @param name name of the <b>StorageGroup</b> * @throws PEException */ public PersistentGroup(String name) { this.name = name; generations = new ArrayList<StorageGroupGeneration>(); generations.add(new StorageGroupGeneration(this, /* ver */ 0)); } /** * Constructor to create a <b>StorageGroup</b> containing the provided * {@link PersistentSite}. This would be a non-persistent <b>StorageGroup</b>. * * @param site the {@link PersistentSite} in the group * @throws PELockedException */ public PersistentGroup(PersistentSite site) throws PELockedException { this(TEMP_NAME); generations.get(0).addStorageSite(site); } public PersistentGroup(Collection<PersistentSite> newSites) { this(TEMP_NAME); try { generations.get(0).add(newSites); } catch (PELockedException e) { throw new PECodingException("Temp groups should never be locked", e); } } /** * Locks the <b>StorageGroup</b>. This means that the list of * {@link PersistentSite} in the group may not be modifed, and is * used when data is added to a table using a {@link DistributionModel} * which will not be able to find records if the list of sites in the * <b>StorageGroup</b> is changed (for example, {@link RandomDistributionModel} * and {@link BroadcastDistributionModel} are tolerant of changes, * but DynamicDistributionModel and RangeDistributionModel * are not). */ public void lockGroup() { StorageGroupGeneration lastGen = getLastGen(); if (!lastGen.isLocked()) lastGen.lock(); } public StorageGroupGeneration getLastGen() { return generations.get(generations.size()-1); } public final List<PersistentSite> getStorageSites() { List<PersistentSite> allSites = new ArrayList<PersistentSite>(generations.get(0).getStorageSites()); if (generations.size() > 1) { Set<PersistentSite> siteSet = new HashSet<PersistentSite>(allSites); for (int i = 1; i < generations.size(); ++i) { for (PersistentSite site : generations.get(i).getStorageSites()) { if (!siteSet.contains(site)) { siteSet.add(site); allSites.add(site); } } } } return allSites; } public PersistentGroup anySite() throws PELockedException { final List<PersistentSite> allSites = getStorageSites(); return new PersistentGroup(PECollectionUtils.selectRandom(allSites)); } /** * @return * @throws PELockedException */ public PersistentSite anySiteInLatestGeneration() throws PELockedException { final List<PersistentSite> latestSites = getLastGen().getStorageSites(); return PECollectionUtils.selectRandom(latestSites); } /** * Returns the name of the <b>StorageGroup</b>. * * @return <b>StorageGroup</b> name */ @Override public String getName() { return this.name; } public Integer getLastGeneration() { if (generations.isEmpty()) return null; return generations.get(generations.size() - 1).id; } public final List<StorageGroupGeneration> getGenerations() { return generations; } @Override public String toString() { return PEStringUtils.toString(getName(), generations); } public static int computeHashCode(StorageGroup sg) { final int prime = 31; int result = 1; result = prime * result + ((sg.getName() == null) ? 0 : sg.getName().hashCode()); return result; } @Override public int hashCode() { return computeHashCode(this); } @Override public boolean equals(Object obj) { return computeEquals(this,obj); } public static boolean computeEquals(StorageGroup left, Object right) { if (left == right) return true; // NOPMD by doug on 15/01/13 3:56 PM if (right == null) return false; if (!(right instanceof PersistentGroup || right instanceof TStorageGroup)) return false; StorageGroup other = (StorageGroup) right; if (left.getName() == null) { if (other.getName() != null) return false; } else if (!left.getName().equals(other.getName())) { return false; } return true; } @Override public int getId() { return id; } public void addStorageSite(PersistentSite site) throws PEException { if (getLastGen().isLocked()) throw new PEException("Cannot add site to locked persistent group"); getLastGen().addStorageSite(site); } public void addGeneration(ExecutionState estate, WorkerGroup wg, StorageGroupGeneration newGen, ListOfPairs<UserTable, SQLCommand> tableDecls, boolean ignoreFKs, List<SQLCommand> userDecls) throws Throwable { SSConnection ssCon = estate.getConnection(); if (false == this.equals(wg.getGroup())) throw new PEException("WorkerGroup does not match StorageGroup"); if (generations.size() > 0) lockGroup(); ListSet<UserDatabase> dbs = new ListSet<UserDatabase>(); boolean fkVal = KnownVariables.FOREIGN_KEY_CHECKS.getSessionValue(ssCon); if (ignoreFKs) ssCon.setSessionVariable("foreign_key_checks", "0"); if (tableDecls != null) { for(Pair<UserTable,SQLCommand> p : tableDecls) dbs.add(p.getFirst().getDatabase()); for(UserDatabase udb : dbs) wg.assureDatabase(ssCon, udb); for(Pair<UserTable,SQLCommand> p : tableDecls) { p.getFirst().prepareGenerationAddition(estate, wg, newGen, p.getSecond()); } } else { List<UserTable> tables = ssCon.getCatalogDAO().findAllTablesInPersistentGroup(this); for(UserTable ut : tables) { dbs.add(ut.getDatabase()); } for(UserDatabase udb : dbs) wg.assureDatabase(ssCon, udb); for(UserTable ut : tables) ut.prepareGenerationAddition(estate,wg,newGen,null); } if (ignoreFKs) ssCon.setSessionVariable("foreign_key_checks", (fkVal ? "1" : "0")); // it's unclear whether newGen is just the new sites; if not then this stuff will fail if (userDecls != null) { for(SQLCommand usql : userDecls) { wg.submit(MappingSolution.AllWorkers, new WorkerExecuteRequest(ssCon.getNonTransactionalContext(),usql), DBEmptyTextResultConsumer.INSTANCE); } } generations.add(newGen); //NOTE: At this point: // all databases and tables have been created on the new sites. // any broadcast tables have been copied to new sites. // the previous gen's range min/max have been computed, and added to the catalog // a storage gen entry for the new default generation has been added. //TODO: remove anything tied to the old generations wg.markForPurge(); onUpdate(); WorkerGroupFactory.clearGroupFromCache(ssCon, this); ssCon.clearWorkerGroupCache(this); } public void removeGeneration(StorageGroupGeneration sgg) { generations.remove(sgg); } public void addAllSites(Collection<PersistentSite> sites) throws PELockedException { getLastGen().add(sites); } @Override public ColumnSet getShowColumnSet(CatalogQueryOptions cqo) { ColumnSet showColumnSet = new ColumnSet(); showColumnSet.addColumn(PERSISTENT_GROUP_CS_HEADER_VAL, 255, "varchar", Types.VARCHAR); if (!cqo.isPlural()) showColumnSet.addColumn("Latest Generation",3,"integer",Types.INTEGER); return showColumnSet; } @Override public boolean isTemporaryGroup() { return TEMP_NAME.equals(name); } @Override public ResultRow getShowResultRow(CatalogQueryOptions cqo) { ResultRow rr = new ResultRow(); rr.addResultColumn(this.name, false); if (!cqo.isPlural()) { if (generations.isEmpty()) rr.addResultColumn(null,true); else rr.addResultColumn(generations.get(generations.size() - 1).id, false); } return rr; } @Override public void removeFromParent() { // do nothing } @Override public List<CatalogEntity> getDependentEntities(CatalogDAO c) throws Throwable { ArrayList<CatalogEntity> out = new ArrayList<CatalogEntity>(); out.addAll(generations); for(StorageGroupGeneration sgg : generations) { out.addAll(sgg.getDependentEntities(c)); } // also any ranges out.addAll(c.findRangesOnGroup(getName())); return out; } @Override public void returnWorkerSites(WorkerManager workerManager, Collection<? extends StorageSite> sites) throws PEException { // Nothing to do here as sites are statically allocated to persistent groups } @Override public void provisionGetWorkerRequest(GetWorkerRequest getWorkerRequest) throws PEException { getWorkerRequest.fulfillGetWorkerRequest(getStorageSites()); } @Override public int sizeForProvisioning() throws PEException { return getStorageSites().size(); } @Override public void onUpdate() { onDrop(); } @Override public void onDrop() { if (!Boolean.getBoolean(PERSISTENT_GROUP_SUPPRESS_CACHE_PURGE)) { PurgeWorkerGroupCaches purgeMessage = new PurgeWorkerGroupCaches(this); Singletons.require(GroupTopicPublisher.class).publish(purgeMessage); } } }