/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.pig.backend.hadoop.executionengine.physicalLayer.relationalOperators; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pig.ExecType; import org.apache.pig.PigException; import org.apache.pig.backend.executionengine.ExecException; import org.apache.pig.backend.hadoop.datastorage.ConfigurationUtil; import org.apache.pig.backend.hadoop.executionengine.physicalLayer.POStatus; import org.apache.pig.backend.hadoop.executionengine.physicalLayer.PhysicalOperator; import org.apache.pig.backend.hadoop.executionengine.physicalLayer.Result; import org.apache.pig.backend.hadoop.executionengine.physicalLayer.expressionOperators.ConstantExpression; import org.apache.pig.backend.hadoop.executionengine.physicalLayer.plans.PhyPlanVisitor; import org.apache.pig.backend.hadoop.executionengine.physicalLayer.plans.PhysicalPlan; import org.apache.pig.backend.hadoop.executionengine.physicalLayer.relationalOperators.POMergeJoin.TuplesToSchemaTupleList; import org.apache.pig.data.DataBag; import org.apache.pig.data.DataType; import org.apache.pig.data.NonSpillableDataBag; import org.apache.pig.data.SchemaTupleBackend; import org.apache.pig.data.SchemaTupleClassGenerator.GenContext; import org.apache.pig.data.SchemaTupleFactory; import org.apache.pig.data.Tuple; import org.apache.pig.data.TupleFactory; import org.apache.pig.impl.PigContext; import org.apache.pig.impl.io.FileSpec; import org.apache.pig.impl.logicalLayer.schema.Schema; import org.apache.pig.impl.plan.NodeIdGenerator; import org.apache.pig.impl.plan.OperatorKey; import org.apache.pig.impl.plan.PlanException; import org.apache.pig.impl.plan.VisitorException; /** * The operator models the join keys using the Local Rearrange operators which * are configured with the plan specified by the user. It also sets up one * Hashtable per replicated input which maps the Key(k) stored as a Tuple to a * DataBag which holds all the values in the input having the same key(k) The * getNext() reads an input from its predecessor and separates them into key & * value. It configures a foreach operator with the databags obtained from each * Hashtable for the key and also with the value for the fragment input. It then * returns tuples returned by this foreach operator. */ // We intentionally skip type checking in backend for performance reasons @SuppressWarnings("unchecked") public class POFRJoin extends PhysicalOperator { private static final Log log = LogFactory.getLog(POFRJoin.class); /** * */ private static final long serialVersionUID = 1L; // The number in the input list which denotes the fragmented input private int fragment; // There can be n inputs each being a List<PhysicalPlan> // Ex. join A by ($0+$1,$0-$1), B by ($0*$1,$0/$1); private List<List<PhysicalPlan>> phyPlanLists; // The key type for each Local Rearrange operator private List<List<Byte>> keyTypes; // The Local Rearrange operators modeling the join key private POLocalRearrange[] LRs; // The set of files that represent the replicated inputs private FileSpec[] replFiles; // Used to configure the foreach operator private ConstantExpression[] constExps; // Used to produce the cross product of various bags private POForEach fe; // The array of Hashtables one per replicated input. replicates[fragment] = // null // fragment is the input which is fragmented and not replicated. private TupleToMapKey replicates[]; // varaible which denotes whether we are returning tuples from the foreach // operator private boolean processingPlan; // A dummy tuple private Tuple dumTup = TupleFactory.getInstance().newTuple(1); // An instance of tuple factory private transient TupleFactory mTupleFactory; private boolean setUp; // A Boolean variable which denotes if this is a LeftOuter Join or an Inner // Join private boolean isLeftOuterJoin; // This list contains nullTuples according to schema of various inputs private DataBag nullBag; private Schema[] inputSchemas; private Schema[] keySchemas; public POFRJoin(OperatorKey k, int rp, List<PhysicalOperator> inp, List<List<PhysicalPlan>> ppLists, List<List<Byte>> keyTypes, FileSpec[] replFiles, int fragment, boolean isLeftOuter, Tuple nullTuple) throws ExecException { this(k, rp, inp, ppLists, keyTypes, replFiles, fragment, isLeftOuter, nullTuple, null, null); } public POFRJoin(OperatorKey k, int rp, List<PhysicalOperator> inp, List<List<PhysicalPlan>> ppLists, List<List<Byte>> keyTypes, FileSpec[] replFiles, int fragment, boolean isLeftOuter, Tuple nullTuple, Schema[] inputSchemas, Schema[] keySchemas) throws ExecException { super(k, rp, inp); phyPlanLists = ppLists; this.fragment = fragment; this.keyTypes = keyTypes; this.replFiles = replFiles; replicates = new TupleToMapKey[ppLists.size()]; LRs = new POLocalRearrange[ppLists.size()]; constExps = new ConstantExpression[ppLists.size()]; createJoinPlans(k); processingPlan = false; mTupleFactory = TupleFactory.getInstance(); List<Tuple> tupList = new ArrayList<Tuple>(); tupList.add(nullTuple); nullBag = new NonSpillableDataBag(tupList); this.isLeftOuterJoin = isLeftOuter; if (inputSchemas != null) { this.inputSchemas = inputSchemas; } else { this.inputSchemas = new Schema[replFiles == null ? 0 : replFiles.length]; } if (keySchemas != null) { this.keySchemas = keySchemas; } else { this.keySchemas = new Schema[replFiles == null ? 0 : replFiles.length]; } } public List<List<PhysicalPlan>> getJoinPlans() { return phyPlanLists; } private OperatorKey genKey(OperatorKey old) { return new OperatorKey(old.scope, NodeIdGenerator.getGenerator() .getNextNodeId(old.scope)); } /** * Configures the Local Rearrange operators & the foreach operator * * @param old * @throws ExecException */ private void createJoinPlans(OperatorKey old) throws ExecException { List<PhysicalPlan> fePlans = new ArrayList<PhysicalPlan>(); List<Boolean> flatList = new ArrayList<Boolean>(); int i = -1; for (List<PhysicalPlan> ppLst : phyPlanLists) { ++i; POLocalRearrange lr = new POLocalRearrange(genKey(old)); lr.setIndex(i); lr.setResultType(DataType.TUPLE); lr.setKeyType(keyTypes.get(i).size() > 1 ? DataType.TUPLE : keyTypes.get(i).get(0)); try { lr.setPlans(ppLst); } catch (PlanException pe) { int errCode = 2071; String msg = "Problem with setting up local rearrange's plans."; throw new ExecException(msg, errCode, PigException.BUG, pe); } LRs[i] = lr; ConstantExpression ce = new ConstantExpression(genKey(old)); ce.setResultType((i == fragment) ? DataType.TUPLE : DataType.BAG); constExps[i] = ce; PhysicalPlan pp = new PhysicalPlan(); pp.add(ce); fePlans.add(pp); flatList.add(true); } // The ForEach operator here is used for generating a Cross-Product // It is given a set of constant expressions with // Tuple,(Bag|Tuple),(...) // It does a cross product on that and produces output. fe = new POForEach(genKey(old), -1, fePlans, flatList); } @Override public void visit(PhyPlanVisitor v) throws VisitorException { v.visitFRJoin(this); } @Override public String name() { return getAliasString() + "FRJoin[" + DataType.findTypeName(resultType) + "]" + " - " + mKey.toString(); } @Override public boolean supportsMultipleInputs() { return true; } @Override public boolean supportsMultipleOutputs() { return false; } @Override public Result getNextTuple() throws ExecException { Result res = null; Result inp = null; if (!setUp) { setUpHashMap(); setUp = true; } if (processingPlan) { // Return tuples from the for each operator // Assumes that it is configured appropriately with // the bags for the current key. while (true) { res = fe.getNextTuple(); if (res.returnStatus == POStatus.STATUS_OK) { return res; } if (res.returnStatus == POStatus.STATUS_EOP) { // We have completed all cross-products now its time to move // to next tuple of left side processingPlan = false; break; } if (res.returnStatus == POStatus.STATUS_ERR) { return res; } if (res.returnStatus == POStatus.STATUS_NULL) { continue; } } } while (true) { // Process the current input inp = processInput(); if (inp.returnStatus == POStatus.STATUS_EOP || inp.returnStatus == POStatus.STATUS_ERR) return inp; if (inp.returnStatus == POStatus.STATUS_NULL) { continue; } // Separate Key & Value using the fragment's LR operator POLocalRearrange lr = LRs[fragment]; lr.attachInput((Tuple) inp.result); Result lrOut = lr.getNextTuple(); if (lrOut.returnStatus != POStatus.STATUS_OK) { log.error("LocalRearrange isn't configured right or is not working"); return new Result(); } Tuple lrOutTuple = (Tuple) lrOut.result; Tuple key = TupleFactory.getInstance().newTuple(1); key.set(0, lrOutTuple.get(1)); Tuple value = getValueTuple(lr, lrOutTuple); lr.detachInput(); // Configure the for each operator with the relevant bags int i = -1; boolean noMatch = false; for (ConstantExpression ce : constExps) { ++i; if (i == fragment) { // We set the first CE as the tuple from fragmented Left ce.setValue(value); continue; } TupleToMapKey replicate = replicates[i]; if (replicate.get(key) == null) { if (isLeftOuterJoin) { ce.setValue(nullBag); } noMatch = true; break; } ce.setValue(new NonSpillableDataBag(replicate.get(key).getList())); } // If this is not LeftOuter Join and there was no match we // skip the processing of this left tuple and move ahead if (!isLeftOuterJoin && noMatch) continue; fe.attachInput(dumTup); processingPlan = true; // We are all set, we call getNext (this function) which will call // getNext on ForEach // And that will return one tuple of Cross-Product between set // constant Expressions // All subsequent calls ( by parent ) to this function will return // next tuple of crossproduct Result gn = getNextTuple(); return gn; } } private static class TupleToMapKey { private HashMap<Tuple, TuplesToSchemaTupleList> tuples; private SchemaTupleFactory tf; public TupleToMapKey(int ct, SchemaTupleFactory tf) { tuples = new HashMap<Tuple, TuplesToSchemaTupleList>(ct); this.tf = tf; } public TuplesToSchemaTupleList put(Tuple key, TuplesToSchemaTupleList val) { if (tf != null) { key = TuplesToSchemaTupleList.convert(key, tf); } return tuples.put(key, val); } public TuplesToSchemaTupleList get(Tuple key) { if (tf != null) { key = TuplesToSchemaTupleList.convert(key, tf); } return tuples.get(key); } } /** * Builds the HashMaps by reading each replicated input from the DFS using a * Load operator * * @throws ExecException */ private void setUpHashMap() throws ExecException { SchemaTupleFactory[] inputSchemaTupleFactories = new SchemaTupleFactory[inputSchemas.length]; SchemaTupleFactory[] keySchemaTupleFactories = new SchemaTupleFactory[inputSchemas.length]; for (int i = 0; i < inputSchemas.length; i++) { Schema schema = inputSchemas[i]; if (schema != null) { log.debug("Using SchemaTuple for FR Join Schema: " + schema); inputSchemaTupleFactories[i] = SchemaTupleBackend.newSchemaTupleFactory(schema, false, GenContext.FR_JOIN); } schema = keySchemas[i]; if (schema != null) { log.debug("Using SchemaTuple for FR Join key Schema: " + schema); keySchemaTupleFactories[i] = SchemaTupleBackend.newSchemaTupleFactory(schema, false, GenContext.FR_JOIN); } } int i = -1; long time1 = System.currentTimeMillis(); for (FileSpec replFile : replFiles) { ++i; SchemaTupleFactory inputSchemaTupleFactory = inputSchemaTupleFactories[i]; SchemaTupleFactory keySchemaTupleFactory = keySchemaTupleFactories[i]; if (i == fragment) { replicates[i] = null; continue; } POLoad ld = new POLoad(new OperatorKey("Repl File Loader", 1L), replFile); Properties props = ConfigurationUtil.getLocalFSProperties(); PigContext pc = new PigContext(ExecType.LOCAL, props); ld.setPc(pc); // We use LocalRearrange Operator to seperate Key and Values // eg. ( a, b, c ) would generate a, ( a, b, c ) // And we use 'a' as the key to the HashMap // The rest '( a, b, c )' is added to HashMap as value // We could have manually done this, but LocalRearrange does the // same thing, so utilizing its functionality POLocalRearrange lr = LRs[i]; lr.setInputs(Arrays.asList((PhysicalOperator) ld)); TupleToMapKey replicate = new TupleToMapKey(1000, keySchemaTupleFactory); log.debug("Completed setup. Trying to build replication hash table"); for (Result res = lr.getNextTuple(); res.returnStatus != POStatus.STATUS_EOP; res = lr.getNextTuple()) { if (getReporter() != null) getReporter().progress(); Tuple tuple = (Tuple) res.result; if (isKeyNull(tuple.get(1))) continue; Tuple key = mTupleFactory.newTuple(1); key.set(0, tuple.get(1)); Tuple value = getValueTuple(lr, tuple); if (replicate.get(key) == null) { replicate.put(key, new TuplesToSchemaTupleList(1, inputSchemaTupleFactory)); } replicate.get(key).add(value); } replicates[i] = replicate; } long time2 = System.currentTimeMillis(); log.debug("Hash Table built. Time taken: " + (time2 - time1)); } private boolean isKeyNull(Object key) throws ExecException { if (key == null) return true; if (key instanceof Tuple) { Tuple t = (Tuple)key; for (int i=0; i<t.size(); i++) { if (t.isNull(i)) return true; } } return false; } private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException, ExecException { is.defaultReadObject(); mTupleFactory = TupleFactory.getInstance(); // setUpHashTable(); } /* * Extracts the value tuple from the LR operator's output tuple */ private Tuple getValueTuple(POLocalRearrange lr, Tuple tuple) throws ExecException { Tuple val = (Tuple) tuple.get(2); Tuple retTup = null; boolean isProjectStar = lr.isProjectStar(); Map<Integer, Integer> keyLookup = lr.getProjectedColsMap(); int keyLookupSize = keyLookup.size(); Object key = tuple.get(1); boolean isKeyTuple = lr.isKeyTuple(); Tuple keyAsTuple = isKeyTuple ? (Tuple) tuple.get(1) : null; if (keyLookupSize > 0) { // we have some fields of the "value" in the // "key". int finalValueSize = keyLookupSize + val.size(); retTup = mTupleFactory.newTuple(finalValueSize); int valIndex = 0; // an index for accessing elements from // the value (val) that we have currently for (int i = 0; i < finalValueSize; i++) { Integer keyIndex = keyLookup.get(i); if (keyIndex == null) { // the field for this index is not in the // key - so just take it from the "value" // we were handed retTup.set(i, val.get(valIndex)); valIndex++; } else { // the field for this index is in the key if (isKeyTuple) { // the key is a tuple, extract the // field out of the tuple retTup.set(i, keyAsTuple.get(keyIndex)); } else { retTup.set(i, key); } } } } else if (isProjectStar) { // the whole "value" is present in the "key" retTup = mTupleFactory.newTuple(keyAsTuple.getAll()); } else { // there is no field of the "value" in the // "key" - so just make a copy of what we got // as the "value" retTup = mTupleFactory.newTuple(val.getAll()); } return retTup; } public int getFragment() { return fragment; } public void setFragment(int fragment) { this.fragment = fragment; } public FileSpec[] getReplFiles() { return replFiles; } public void setReplFiles(FileSpec[] replFiles) { this.replFiles = replFiles; } @Override public Tuple illustratorMarkup(Object in, Object out, int eqClassIndex) { // no op: all handled by the preceding POForEach return null; } }