package com.tesora.dve.sql.transform.strategy.join; /* * #%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.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import com.tesora.dve.exceptions.PEException; import com.tesora.dve.sql.jg.DGJoin; import com.tesora.dve.sql.jg.DPart; import com.tesora.dve.sql.schema.SchemaContext; import com.tesora.dve.sql.statement.dml.SelectStatement; import com.tesora.dve.sql.transform.strategy.ExecutionCost; import com.tesora.dve.sql.transform.strategy.PlannerContext; import com.tesora.dve.sql.transform.strategy.join.JoinRewriteTransformFactory.JoinRewriteAdaptedTransform; import com.tesora.dve.sql.util.ListSet; public abstract class JoinEntry { protected SelectStatement basis; protected JoinRewriteAdaptedTransform parentTransform; protected final JoinRewriteTransformFactory featurePlanner; protected ExecutionCost score; protected ListSet<JoinEntry> references; protected final SchemaContext sc; protected final PlannerContext pc; protected final long id; public JoinEntry(PlannerContext pc, SchemaContext sc, SelectStatement basis, JoinRewriteAdaptedTransform parent, JoinRewriteTransformFactory factory) { this.basis = basis; this.parentTransform = parent; references = new ListSet<JoinEntry>(); this.sc = sc; this.pc = pc; this.id = sc.getNextObjectID(); this.featurePlanner = factory; } public SchemaContext getSchemaContext() { return sc; } public PlannerContext getPlannerContext() { return pc; } public SelectStatement getBasis() { return basis; } public JoinRewriteAdaptedTransform getParentTransform() { return parentTransform; } public JoinRewriteTransformFactory getFeaturePlanner() { return featurePlanner; } public ExecutionCost getScore() throws PEException{ if (score == null) score = computeScore(); return score; } protected abstract ExecutionCost computeScore() throws PEException; protected void clearScore() { score = null; // also clear on all my refs for(JoinEntry je : references) je.clearScore(); } public abstract ListSet<DPart> getPartitions(); // might be more than one for clustered joins public abstract DGJoin getJoin(); public ListSet<DGJoin> getClusteredJoins() { return null; } public boolean isOuterJoin() { if (getJoin() == null) return false; return getJoin().getJoinType().isOuterJoin(); } // schedule as an independent join public abstract JoinedPartitionEntry schedule() throws PEException; public abstract JoinedPartitionEntry schedule(JoinedPartitionEntry head) throws PEException; public abstract JoinedPartitionEntry schedule(List<JoinedPartitionEntry> ipes) throws PEException; public boolean schedule(ListSet<JoinedPartitionEntry> heads) throws PEException { return findMatchingJoins(heads); } protected abstract boolean preferNewHead(ListSet<JoinedPartitionEntry> head) throws PEException; public boolean findMatchingJoins(ListSet<JoinedPartitionEntry> heads) throws PEException { if (!heads.isEmpty() && buildIntIntJoin(heads)) return true; // we can prefer a new head iff none of our partitions are in an existing head // we however cannot prefer a new head if one of our partitions is part of the existing partitions boolean partiallyContained = false; ListSet<DPart> myParts = getPartitions(); for(JoinedPartitionEntry ipe : heads) { for(DPart dp : myParts) { if (ipe.getPartitions().contains(dp)) { partiallyContained = true; break; } } } boolean trynew = preferNewHead(heads); if (trynew && !partiallyContained) { if (tryNewHead(heads)) return true; } if (!heads.isEmpty() && buildIndIntJoin(heads)) return true; if (!trynew && !partiallyContained) { if (tryNewHead(heads)) return true; } return false; } protected boolean tryNewHead(ListSet<JoinedPartitionEntry> heads) throws PEException { if (parentTransform.canschedule(this)) { if (parentTransform.emitting()) parentTransform.emit("tryNewHead(heads) on " + toString()); JoinedPartitionEntry ipe = schedule(); heads.add(ipe); if (parentTransform.emitting()) { parentTransform.emit("Created initial ipe: "); parentTransform.emit(ipe.toString()); parentTransform.emit("using " + this); } return true; } return false; } public boolean buildIntIntJoin(ListSet<JoinedPartitionEntry> heads) throws PEException { // essentially, we pass here if our join can work on the existing heads final Map<DPart, JoinedPartitionEntry> ipeForPartition = new HashMap<DPart, JoinedPartitionEntry>(); for(JoinedPartitionEntry ipe : heads) { for(DPart p : ipe.getPartitions()) ipeForPartition.put(p, ipe); } List<JoinedPartitionEntry> ipes = new ListSet<JoinedPartitionEntry>(); for(DPart p : getPartitions()) { JoinedPartitionEntry ipe = ipeForPartition.get(p); if (ipe == null) return false; ipes.add(ipe); } if (!parentTransform.canschedule(this)) return false; if (parentTransform.emitting()) parentTransform.emit("buildIntIntJoin(heads) on " + toString()); JoinedPartitionEntry newIpe = schedule(ipes); for(JoinedPartitionEntry ipe : ipes) heads.remove(ipe); heads.add(newIpe); if (parentTransform.emitting()) { parentTransform.emit("created ipe from ipes " + newIpe); parentTransform.emit("using " + this); } return true; } public boolean buildIndIntJoin(ListSet<JoinedPartitionEntry> heads) throws PEException { HashSet<DPart> usedPartitions = new HashSet<DPart>(); for(JoinedPartitionEntry ipe : heads) usedPartitions.addAll(ipe.getPartitions()); ListSet<DPart> currentUsed = new ListSet<DPart>(); ListSet<DPart> currentUnused = new ListSet<DPart>(); for(DPart p : getPartitions()) { if (usedPartitions.contains(p)) currentUsed.add(p); else currentUnused.add(p); } if (!currentUsed.isEmpty() && !currentUnused.isEmpty()) { // join a new table in to an existing intermediate partition. figure out which one. if (currentUsed.size() > 1) throw new PEException("Unable to schedule int int join with multiple used partitions"); DPart usedPartition = currentUsed.get(0); for(JoinedPartitionEntry ipe : heads) { if (ipe.getPartitions().contains(usedPartition) && parentTransform.canschedule(this)) { if (parentTransform.emitting()) parentTransform.emit("buildIndIntJoin(heads) on " + toString()); JoinedPartitionEntry newHead = null; if (parentTransform.getPlannerContext() != null) newHead = schedule(ipe); else newHead = schedule(ipe); heads.remove(ipe); heads.add(newHead); if (parentTransform.emitting()) { parentTransform.emit("created ipe from ipe and pe " + newHead); parentTransform.emit("using " + this); } return true; } } } return false; } public void addReference(JoinEntry other) { references.add(other); } @Override public String toString() { return this.getClass().getSimpleName() + "@" + id; } }