package com.tesora.dve.distribution; /* * #%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.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.tesora.dve.common.catalog.CatalogDAO; import com.tesora.dve.common.catalog.DistributionModel; import com.tesora.dve.common.catalog.StorageGroup; import com.tesora.dve.common.catalog.StorageGroupGeneration; import com.tesora.dve.common.catalog.UserTable; import com.tesora.dve.db.mysql.MyFieldType; import com.tesora.dve.db.mysql.common.DBTypeBasedUtils; import com.tesora.dve.db.mysql.common.DataTypeValueFunc; import com.tesora.dve.exceptions.PEException; import com.tesora.dve.exceptions.PENotFoundException; import com.tesora.dve.groupmanager.GroupManager; import com.tesora.dve.membership.GroupTopicPublisher; import com.tesora.dve.groupmanager.PurgeDistributionRangeCache; import com.tesora.dve.locking.ClusterLock; import com.tesora.dve.queryplan.ExecutionState; import com.tesora.dve.resultset.ColumnMetadata; import com.tesora.dve.resultset.ColumnSet; import com.tesora.dve.server.connectionmanager.SSConnection; import com.tesora.dve.server.messaging.SQLCommand; import com.tesora.dve.server.messaging.WorkerExecuteRequest; import com.tesora.dve.server.messaging.WorkerRequest; import com.tesora.dve.singleton.Singletons; import com.tesora.dve.sql.schema.SchemaContext.DistKeyOpType; import com.tesora.dve.sql.util.Functional; import com.tesora.dve.sql.util.UnaryFunction; import com.tesora.dve.worker.MysqlTextResultCollector; import com.tesora.dve.worker.WorkerGroup; import com.tesora.dve.worker.WorkerGroup.MappingSolution; @Entity @DiscriminatorValue(RangeDistributionModel.MODEL_NAME) public class RangeDistributionModel extends DistributionModel { static Cache<Integer, DistributionRange> rangeCache = CacheBuilder.newBuilder().build(); private abstract class BinaryFunction<O, I> { public BinaryFunction() {} public abstract O evaluate(I o1, I o2) throws PEException; } private static final long serialVersionUID = 1L; public final static String MODEL_NAME = "Range"; public static final String GENERATION_LOCKNAME = "PE.Model.Range"; public final static RangeDistributionModel SINGLETON = new RangeDistributionModel(); public RangeDistributionModel() { super(MODEL_NAME); } @Override public MappingSolution mapKeyForInsert(CatalogDAO c, StorageGroup sg, IKeyValue key) throws PEException { return mapKeyToGeneration(c, key); } @Override public MappingSolution mapKeyForUpdate(CatalogDAO c, StorageGroup sg, IKeyValue key) throws PEException { return mapKeyToGeneration(c, key); } @Override public MappingSolution mapKeyForQuery(CatalogDAO c, StorageGroup sg, IKeyValue key, DistKeyOpType op) throws PEException { return mapKeyToGeneration(c, key); } private static DistributionRange findDistributionRange(final CatalogDAO c, final IKeyValue key) throws PEException { Integer distRangeID = key.getRangeId(); DistributionRange dr = null; if (distRangeID == null) throw new PEException("No range found for table " + key.getQualifiedTableName()); final int distid = distRangeID; try { dr = rangeCache.get(distid, new Callable<DistributionRange>() { @Override public synchronized DistributionRange call() throws Exception { DistributionRange distRange = c.findByKey(DistributionRange.class, distid); distRange.fullyLoad(); return distRange; } }); } catch (ExecutionException e) { throw new PEException("Cannot load range for id " + distid, e); } return dr; } MappingSolution mapKeyToGeneration(final CatalogDAO c, final IKeyValue key) throws PEException { return findDistributionRange(c,key).mapKeyToGeneration(key); } @Override public void prepareGenerationAddition(ExecutionState estate, WorkerGroup wg, UserTable ut, StorageGroupGeneration newGen) throws PEException { // Compute the range of the generation as the superset of the ranges // of all the tables that use the range (they should all be the same, // but in case the user has defined disjoint ranges we need to // pick the superset). SSConnection ssCon = estate.getConnection(); DistributionRange dr = ssCon.getCatalogDAO().findRangeForTable(ut); ClusterLock genLock = GroupManager.getCoordinationServices().getClusterLock(GENERATION_LOCKNAME); boolean didReadUnlock = false; try{ genLock.sharedUnlock(ssCon,"preparing generation, upgrading read lock"); didReadUnlock = true; } catch (IllegalMonitorStateException imse){ //we probably didn't have the lock. } genLock.exclusiveLock(ssCon,"preparing new generation"); try { StorageGroupGeneration rangeGeneration = ut.getPersistentGroup().getLastGen(); GenerationKeyRange genKeyRange = dr.getRangeForGeneration(rangeGeneration); Singletons.require(GroupTopicPublisher.class).publish(new PurgeDistributionRangeCache(dr.getId())); KeyValue dvStart = getRangeKey(ssCon, dr, wg, ut, "ASC", new BinaryFunction<KeyValue, KeyValue>() { @Override public KeyValue evaluate(KeyValue o1, KeyValue o2) throws PEException { return (o1.compare(o2) < 0) ? o1 : o2; } }); if (dvStart != null) { KeyValue dvEnd = getRangeKey(ssCon, dr, wg, ut, "DESC", new BinaryFunction<KeyValue, KeyValue>() { @Override public KeyValue evaluate(KeyValue o1, KeyValue o2) throws PEException { return (o1.compare(o2) > 0) ? o1 : o2; } }); if (genKeyRange == null) { genKeyRange = new GenerationKeyRange(dr, rangeGeneration, dvStart, dvEnd); dr.addRangeGeneration(genKeyRange); } else { genKeyRange.mergeRange(dvStart, dvEnd); } } } finally { genLock.exclusiveUnlock(ssCon,"releasing gen lock exclusive"); if (didReadUnlock) genLock.sharedLock(ssCon,"downgrading gen lock to share"); } } private KeyValue getRangeKey(SSConnection ssCon, DistributionRange dr, WorkerGroup wg, UserTable ut, final String order, BinaryFunction<KeyValue, KeyValue> chooser) throws PEException { KeyValue keyValue = ut.getDistValue(ssCon.getCatalogDAO()); KeyValue retValue = null; String orderColList = Functional.apply(keyValue.keySet(), new UnaryFunction<String, String>() { @Override public String evaluate(String object) { return object + " " + order; } }).toString(); String query = "SELECT " + keyValue.orderedKeyNames() + " FROM " + ut.getNameAsIdentifier() + " ORDER BY " + orderColList.substring(1, orderColList.length() - 1) + " LIMIT 1"; MysqlTextResultCollector results = new MysqlTextResultCollector(true); WorkerRequest req = new WorkerExecuteRequest(ssCon.getNonTransactionalContext(), new SQLCommand(ssCon, query)).onDatabase(ut.getDatabase()); wg.execute(MappingSolution.AllWorkers, req, results); if (!results.hasResults()) { throw new PENotFoundException("Could not get Key Ranges"); } Map<String, String> nativeTypeComparatorMap = DistributionRange.decodeSignatureIntoNativeTypeComparatorMap(dr.getSignature()); ColumnSet columnSet = results.getColumnSet(); for (List<String> row : results.getRowData()) { keyValue = ut.getDistValue(ssCon.getCatalogDAO()); for (int i = 0; i < keyValue.size(); ++i) { ColumnMetadata columnMetadata = columnSet.getColumn(i+1); DataTypeValueFunc typeReader = DBTypeBasedUtils.getMysqlTypeFunc(MyFieldType.fromByte( columnMetadata.getNativeTypeId()), columnMetadata.getSize(), columnMetadata.getNativeTypeFlags()); Object value = typeReader.convertStringToObject(row.get(i), columnMetadata); ColumnDatum cd = keyValue.get(columnMetadata.getAliasName()); cd.setValue(value); if (nativeTypeComparatorMap.size() > 0) { // avoid a lookup if we can String nativeType = columnMetadata.getTypeName().toLowerCase(); if (nativeTypeComparatorMap.containsKey(nativeType)) { cd.setComparatorClassName(nativeTypeComparatorMap.get(nativeType)); } } } retValue = (retValue == null) ? keyValue : chooser.evaluate(keyValue, retValue); } return retValue; } public static void clearCache() { rangeCache.invalidateAll(); } public static void clearCacheEntry(int distributionRangeId) { rangeCache.invalidate(distributionRangeId); } @Override public void onUpdate() { // do nothing } @Override public void onDrop() { // do nothing } }