/** * 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.hadoop.hive.ql.exec.vector.mapjoin; import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.hadoop.hive.ql.CompilationOpContext; import org.apache.hadoop.hive.ql.exec.JoinUtil; import org.apache.hadoop.hive.ql.exec.vector.ColumnVector; import org.apache.hadoop.hive.ql.exec.vector.VectorizationContext; import org.apache.hadoop.hive.ql.exec.vector.VectorizedRowBatch; import org.apache.hadoop.hive.ql.exec.vector.expressions.VectorExpression; import org.apache.hadoop.hive.ql.exec.vector.mapjoin.hashtable.VectorMapJoinHashMap; import org.apache.hadoop.hive.ql.exec.vector.mapjoin.hashtable.VectorMapJoinHashMapResult; import org.apache.hadoop.hive.ql.exec.vector.mapjoin.hashtable.VectorMapJoinHashTableResult; import org.apache.hadoop.hive.ql.metadata.HiveException; import org.apache.hadoop.hive.ql.plan.OperatorDesc; import org.apache.hadoop.hive.serde2.WriteBuffers.ByteSegmentRef; /** * This class has methods for generating vectorized join results for outer joins. * * The big difference between inner joins and outer joins is the treatment of null and non-matching * keys. * * Inner joins ignore null keys. Outer joins include big table rows with null keys in the result. * * (Left non-full) outer joins include big table rows that do not match the small table. Small * table columns for non-matches will be NULL. * * Another important difference is filtering. For outer joins to include the necessary rows, * filtering must be done after the hash table lookup. That is because filtering does not * eliminate rows, but changes them from match to non-matching rows. They will still appear in * the join result. * * One vector outer join optimization is referencing bytes outer keys. When a bytes key appears * in the small table results area, instead of copying the bytes key we reference the big table key. * Bytes column vectors allow a by reference entry to bytes. It is safe to do a by reference * since it is within the same row. * * Outer join uses a hash map since small table columns can be included in the join result. */ public abstract class VectorMapJoinOuterGenerateResultOperator extends VectorMapJoinGenerateResultOperator { private static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(VectorMapJoinOuterGenerateResultOperator.class.getName()); //--------------------------------------------------------------------------- // Outer join specific members. // // An array of hash map results so we can do lookups on the whole batch before output result // generation. protected transient VectorMapJoinHashMapResult hashMapResults[]; // Pre-allocated member for remembering the big table's selected array at the beginning of // the process method before applying any filter. For outer join we need to remember which // rows did not match since they will appear the in outer join result with NULLs for the // small table. protected transient int[] inputSelected; // Pre-allocated member for storing the (physical) batch index of matching row (single- or // multi-small-table-valued) indexes during a process call. protected transient int[] allMatchs; /* * Pre-allocated members for storing information equal key series for small-table matches. * * ~HashMapResultIndices * Index into the hashMapResults array for the match. * ~AllMatchIndices * (Logical) indices into allMatchs to the first row of a match of a * possible series of duplicate keys. * ~IsSingleValue * Whether there is 1 or multiple small table values. * ~DuplicateCounts * The duplicate count for each matched key. * */ protected transient int[] equalKeySeriesHashMapResultIndices; protected transient int[] equalKeySeriesAllMatchIndices; protected transient boolean[] equalKeySeriesIsSingleValue; protected transient int[] equalKeySeriesDuplicateCounts; // Pre-allocated member for storing the (physical) batch index of rows that need to be spilled. protected transient int[] spills; // Pre-allocated member for storing index into the hashSetResults for each spilled row. protected transient int[] spillHashMapResultIndices; // Pre-allocated member for storing any non-spills, non-matches, or merged row indexes during a // process method call. protected transient int[] nonSpills; protected transient int[] noMatchs; protected transient int[] merged; /** Kryo ctor. */ protected VectorMapJoinOuterGenerateResultOperator() { super(); } public VectorMapJoinOuterGenerateResultOperator(CompilationOpContext ctx) { super(ctx); } public VectorMapJoinOuterGenerateResultOperator(CompilationOpContext ctx, VectorizationContext vContext, OperatorDesc conf) throws HiveException { super(ctx, vContext, conf); } /* * Setup our outer join specific members. */ protected void commonSetup(VectorizedRowBatch batch) throws HiveException { super.commonSetup(batch); // Outer join specific. VectorMapJoinHashMap baseHashMap = (VectorMapJoinHashMap) vectorMapJoinHashTable; hashMapResults = new VectorMapJoinHashMapResult[batch.DEFAULT_SIZE]; for (int i = 0; i < hashMapResults.length; i++) { hashMapResults[i] = baseHashMap.createHashMapResult(); } inputSelected = new int[batch.DEFAULT_SIZE]; allMatchs = new int[batch.DEFAULT_SIZE]; equalKeySeriesHashMapResultIndices = new int[batch.DEFAULT_SIZE]; equalKeySeriesAllMatchIndices = new int[batch.DEFAULT_SIZE]; equalKeySeriesIsSingleValue = new boolean[batch.DEFAULT_SIZE]; equalKeySeriesDuplicateCounts = new int[batch.DEFAULT_SIZE]; spills = new int[batch.DEFAULT_SIZE]; spillHashMapResultIndices = new int[batch.DEFAULT_SIZE]; nonSpills = new int[batch.DEFAULT_SIZE]; noMatchs = new int[batch.DEFAULT_SIZE]; merged = new int[batch.DEFAULT_SIZE]; } //----------------------------------------------------------------------------------------------- /* * Outer join (hash map). */ /** * Do the per-batch setup for an outer join. */ protected void outerPerBatchSetup(VectorizedRowBatch batch) { // For join operators that can generate small table results, reset their // (target) scratch columns. for (int column : smallTableOutputVectorColumns) { ColumnVector smallTableColumn = batch.cols[column]; smallTableColumn.reset(); } for (int column : bigTableOuterKeyOutputVectorColumns) { ColumnVector bigTableOuterKeyColumn = batch.cols[column]; bigTableOuterKeyColumn.reset(); } } /** * Apply the value expression to rows in the (original) input selected array. * * @param batch * The vectorized row batch. * @param inputSelectedInUse * Whether the (original) input batch is selectedInUse. * @param inputLogicalSize * The (original) input batch size. */ private void doValueExprOnInputSelected(VectorizedRowBatch batch, boolean inputSelectedInUse, int inputLogicalSize) { int saveBatchSize = batch.size; int[] saveSelected = batch.selected; boolean saveSelectedInUse = batch.selectedInUse; batch.size = inputLogicalSize; batch.selected = inputSelected; batch.selectedInUse = inputSelectedInUse; if (bigTableValueExpressions != null) { for(VectorExpression ve: bigTableValueExpressions) { ve.evaluate(batch); } } batch.size = saveBatchSize; batch.selected = saveSelected; batch.selectedInUse = saveSelectedInUse; } /** * Apply the value expression to rows specified by a selected array. * * @param batch * The vectorized row batch. * @param selected * The (physical) batch indices to apply the expression to. * @param size * The size of selected. */ private void doValueExpr(VectorizedRowBatch batch, int[] selected, int size) { int saveBatchSize = batch.size; int[] saveSelected = batch.selected; boolean saveSelectedInUse = batch.selectedInUse; batch.size = size; batch.selected = selected; batch.selectedInUse = true; if (bigTableValueExpressions != null) { for(VectorExpression ve: bigTableValueExpressions) { ve.evaluate(batch); } } batch.size = saveBatchSize; batch.selected = saveSelected; batch.selectedInUse = saveSelectedInUse; } /** * Remove (subtract) members from the input selected array and produce the results into * a difference array. * * @param inputSelectedInUse * Whether the (original) input batch is selectedInUse. * @param inputLogicalSize * The (original) input batch size. * @param remove * The indices to remove. They must all be present in input selected array. * @param removeSize * The size of remove. * @param difference * The resulting difference -- the input selected array indices not in the * remove array. * @return * The resulting size of the difference array. * @throws HiveException */ private int subtractFromInputSelected(boolean inputSelectedInUse, int inputLogicalSize, int[] remove, int removeSize, int[] difference) throws HiveException { // if (!verifyMonotonicallyIncreasing(remove, removeSize)) { // throw new HiveException("remove is not in sort order and unique"); // } int differenceCount = 0; // Determine which rows are left. int removeIndex = 0; if (inputSelectedInUse) { for (int i = 0; i < inputLogicalSize; i++) { int candidateIndex = inputSelected[i]; if (removeIndex < removeSize && candidateIndex == remove[removeIndex]) { removeIndex++; } else { difference[differenceCount++] = candidateIndex; } } } else { for (int candidateIndex = 0; candidateIndex < inputLogicalSize; candidateIndex++) { if (removeIndex < removeSize && candidateIndex == remove[removeIndex]) { removeIndex++; } else { difference[differenceCount++] = candidateIndex; } } } if (removeIndex != removeSize) { throw new HiveException("Not all batch indices removed"); } // if (!verifyMonotonicallyIncreasing(difference, differenceCount)) { // throw new HiveException("difference is not in sort order and unique"); // } return differenceCount; } /** * Remove (subtract) members from an array and produce the results into * a difference array. * @param all * The selected array containing all members. * @param allSize * The size of all. * @param remove * The indices to remove. They must all be present in input selected array. * @param removeSize * The size of remove. * @param difference * The resulting difference -- the all array indices not in the * remove array. * @return * The resulting size of the difference array. * @throws HiveException */ private int subtract(int[] all, int allSize, int[] remove, int removeSize, int[] difference) throws HiveException { // if (!verifyMonotonicallyIncreasing(remove, removeSize)) { // throw new HiveException("remove is not in sort order and unique"); // } int differenceCount = 0; // Determine which rows are left. int removeIndex = 0; for (int i = 0; i < allSize; i++) { int candidateIndex = all[i]; if (removeIndex < removeSize && candidateIndex == remove[removeIndex]) { removeIndex++; } else { difference[differenceCount++] = candidateIndex; } } if (removeIndex != removeSize) { throw new HiveException("Not all batch indices removed"); } return differenceCount; } /** * Sort merge two select arrays so the resulting array is ordered by (batch) index. * * @param selected1 * @param selected1Count * @param selected2 * @param selected2Count * @param sortMerged * The resulting sort merge of selected1 and selected2. * @return * The resulting size of the sortMerged array. * @throws HiveException */ private int sortMerge(int[] selected1, int selected1Count, int[] selected2, int selected2Count, int[] sortMerged) throws HiveException { // if (!verifyMonotonicallyIncreasing(selected1, selected1Count)) { // throw new HiveException("selected1 is not in sort order and unique"); // } // if (!verifyMonotonicallyIncreasing(selected2, selected2Count)) { // throw new HiveException("selected1 is not in sort order and unique"); // } int sortMergeCount = 0; int selected1Index = 0; int selected2Index = 0; for (int i = 0; i < selected1Count + selected2Count; i++) { if (selected1Index < selected1Count && selected2Index < selected2Count) { if (selected1[selected1Index] < selected2[selected2Index]) { sortMerged[sortMergeCount++] = selected1[selected1Index++]; } else { sortMerged[sortMergeCount++] = selected2[selected2Index++]; } } else if (selected1Index < selected1Count) { sortMerged[sortMergeCount++] = selected1[selected1Index++]; } else { sortMerged[sortMergeCount++] = selected2[selected2Index++]; } } // if (!verifyMonotonicallyIncreasing(sortMerged, sortMergeCount)) { // throw new HiveException("sortMerged is not in sort order and unique"); // } return sortMergeCount; } /** * Generate the outer join output results for one vectorized row batch. * * @param batch * The big table batch with any matching and any non matching rows both as * selected in use. * @param allMatchCount * Number of matches in allMatchs. * @param equalKeySeriesCount * Number of single value matches. * @param atLeastOneNonMatch * Whether at least one row was a non-match. * @param inputSelectedInUse * A copy of the batch's selectedInUse flag on input to the process method. * @param inputLogicalSize * The batch's size on input to the process method. * @param spillCount * Number of spills in spills. * @param hashMapResultCount * Number of entries in hashMapResults. */ public void finishOuter(VectorizedRowBatch batch, int allMatchCount, int equalKeySeriesCount, boolean atLeastOneNonMatch, boolean inputSelectedInUse, int inputLogicalSize, int spillCount, int hashMapResultCount) throws IOException, HiveException { // Get rid of spills before we start modifying the batch. if (spillCount > 0) { spillHashMapBatch(batch, (VectorMapJoinHashTableResult[]) hashMapResults, spills, spillHashMapResultIndices, spillCount); } int noMatchCount = 0; if (spillCount > 0) { // Subtract the spills to get all match and non-match rows. int nonSpillCount = subtractFromInputSelected( inputSelectedInUse, inputLogicalSize, spills, spillCount, nonSpills); if (isLogDebugEnabled) { LOG.debug("finishOuter spillCount > 0" + " nonSpills " + intArrayToRangesString(nonSpills, nonSpillCount)); } // Big table value expressions apply to ALL matching and non-matching rows. if (bigTableValueExpressions != null) { doValueExpr(batch, nonSpills, nonSpillCount); } if (atLeastOneNonMatch) { noMatchCount = subtract(nonSpills, nonSpillCount, allMatchs, allMatchCount, noMatchs); if (isLogDebugEnabled) { LOG.debug("finishOuter spillCount > 0" + " noMatchs " + intArrayToRangesString(noMatchs, noMatchCount)); } } } else { // Run value expressions over original (whole) input batch. doValueExprOnInputSelected(batch, inputSelectedInUse, inputLogicalSize); if (atLeastOneNonMatch) { noMatchCount = subtractFromInputSelected( inputSelectedInUse, inputLogicalSize, allMatchs, allMatchCount, noMatchs); if (isLogDebugEnabled) { LOG.debug("finishOuter spillCount == 0" + " noMatchs " + intArrayToRangesString(noMatchs, noMatchCount)); } } } // When we generate results into the overflow batch, we may still end up with fewer rows // in the big table batch. So, nulSel and the batch's selected array will be rebuilt with // just the big table rows that need to be forwarded, minus any rows processed with the // overflow batch. if (allMatchCount > 0) { int numSel = 0; for (int i = 0; i < equalKeySeriesCount; i++) { int hashMapResultIndex = equalKeySeriesHashMapResultIndices[i]; VectorMapJoinHashMapResult hashMapResult = hashMapResults[hashMapResultIndex]; int allMatchesIndex = equalKeySeriesAllMatchIndices[i]; boolean isSingleValue = equalKeySeriesIsSingleValue[i]; int duplicateCount = equalKeySeriesDuplicateCounts[i]; if (isSingleValue) { numSel = generateHashMapResultSingleValue( batch, hashMapResult, allMatchs, allMatchesIndex, duplicateCount, numSel); } else { generateHashMapResultMultiValue( batch, hashMapResult, allMatchs, allMatchesIndex, duplicateCount); } } // The number of single value rows that were generated in the big table batch. batch.size = numSel; batch.selectedInUse = true; if (isLogDebugEnabled) { LOG.debug("finishOuter allMatchCount > 0" + " batch.selected " + intArrayToRangesString(batch.selected, batch.size)); } } else { batch.size = 0; } if (noMatchCount > 0) { if (batch.size > 0) { generateOuterNulls(batch, noMatchs, noMatchCount); // Merge noMatchs and (match) selected. int mergeCount = sortMerge( noMatchs, noMatchCount, batch.selected, batch.size, merged); if (isLogDebugEnabled) { LOG.debug("finishOuter noMatchCount > 0 && batch.size > 0" + " merged " + intArrayToRangesString(merged, mergeCount)); } System.arraycopy(merged, 0, batch.selected, 0, mergeCount); batch.size = mergeCount; batch.selectedInUse = true; } else { // We can use the whole batch for output of no matches. generateOuterNullsRepeatedAll(batch); System.arraycopy(noMatchs, 0, batch.selected, 0, noMatchCount); batch.size = noMatchCount; batch.selectedInUse = true; if (isLogDebugEnabled) { LOG.debug("finishOuter noMatchCount > 0 && batch.size == 0" + " batch.selected " + intArrayToRangesString(batch.selected, batch.size)); } } } } /** * Generate the non matching outer join output results for one vectorized row batch. * * For each non matching row specified by parameter, generate nulls for the small table results. * * @param batch * The big table batch with any matching and any non matching rows both as * selected in use. * @param noMatchs * A subset of the rows of the batch that are non matches. * @param noMatchSize * Number of non matches in noMatchs. */ protected void generateOuterNulls(VectorizedRowBatch batch, int[] noMatchs, int noMatchSize) throws IOException, HiveException { // Set null information in the small table results area. for (int i = 0; i < noMatchSize; i++) { int batchIndex = noMatchs[i]; // Mark any scratch small table scratch columns that would normally receive a copy of the // key as null, too. for (int column : bigTableOuterKeyOutputVectorColumns) { ColumnVector colVector = batch.cols[column]; colVector.noNulls = false; colVector.isNull[batchIndex] = true; } // Small table values are set to null. for (int column : smallTableOutputVectorColumns) { ColumnVector colVector = batch.cols[column]; colVector.noNulls = false; colVector.isNull[batchIndex] = true; } } } /** * Generate the outer join output results for one vectorized row batch with a repeated key. * * Any filter expressions will apply now since hash map lookup for outer join is complete. * * @param batch * The big table batch with any matching and any non matching rows both as * selected in use. * @param joinResult * The hash map lookup result for the repeated key. * @param hashMapResults * The array of all hash map results for the batch. * @param someRowsFilteredOut * Whether some rows of the repeated key batch were knocked out by the filter. * @param inputSelectedInUse * A copy of the batch's selectedInUse flag on input to the process method. * @param inputLogicalSize * The batch's size on input to the process method. * @param scratch1 * Pre-allocated storage to internal use. * @param scratch2 * Pre-allocated storage to internal use. */ public void finishOuterRepeated(VectorizedRowBatch batch, JoinUtil.JoinResult joinResult, VectorMapJoinHashMapResult hashMapResult, boolean someRowsFilteredOut, boolean inputSelectedInUse, int inputLogicalSize) throws IOException, HiveException { // LOG.debug("finishOuterRepeated batch #" + batchCounter + " " + joinResult.name() + " batch.size " + batch.size + " someRowsFilteredOut " + someRowsFilteredOut); switch (joinResult) { case MATCH: // Rows we looked up as one repeated key are a match. But filtered out rows // need to be generated as non-matches, too. if (someRowsFilteredOut) { // For the filtered out rows that didn't (logically) get looked up in the hash table, // we need to generate no match results for those too... // Run value expressions over original (whole) input batch. doValueExprOnInputSelected(batch, inputSelectedInUse, inputLogicalSize); // Now calculate which rows were filtered out (they are logically no matches). // Determine which rows are non matches by determining the delta between inputSelected and // (current) batch selected. int noMatchCount = subtractFromInputSelected( inputSelectedInUse, inputLogicalSize, batch.selected, batch.size, noMatchs); generateOuterNulls(batch, noMatchs, noMatchCount); // Now generate the matchs. Single small table values will be put into the big table // batch and come back in matchs. Any multiple small table value results will go into // the overflow batch. generateHashMapResultRepeatedAll(batch, hashMapResult); // Merge noMatchs and (match) selected. int mergeCount = sortMerge( noMatchs, noMatchCount, batch.selected, batch.size, merged); System.arraycopy(merged, 0, batch.selected, 0, mergeCount); batch.size = mergeCount; batch.selectedInUse = true; } else { // Just run our value expressions over input batch. if (bigTableValueExpressions != null) { for(VectorExpression ve: bigTableValueExpressions) { ve.evaluate(batch); } } generateHashMapResultRepeatedAll(batch, hashMapResult); } break; case SPILL: // Rows we looked up as one repeated key need to spill. But filtered out rows // need to be generated as non-matches, too. spillBatchRepeated(batch, (VectorMapJoinHashTableResult) hashMapResult); // After using selected to generate spills, generate non-matches, if any. if (someRowsFilteredOut) { // Determine which rows are non matches by determining the delta between inputSelected and // (current) batch selected. int noMatchCount = subtractFromInputSelected( inputSelectedInUse, inputLogicalSize, batch.selected, batch.size, noMatchs); System.arraycopy(noMatchs, 0, batch.selected, 0, noMatchCount); batch.size = noMatchCount; batch.selectedInUse = true; generateOuterNullsRepeatedAll(batch); } else { batch.size = 0; } break; case NOMATCH: if (someRowsFilteredOut) { // When the repeated no match is due to filtering, we need to restore the // selected information. if (inputSelectedInUse) { System.arraycopy(inputSelected, 0, batch.selected, 0, inputLogicalSize); } batch.size = inputLogicalSize; } // Run our value expressions over whole batch. if (bigTableValueExpressions != null) { for(VectorExpression ve: bigTableValueExpressions) { ve.evaluate(batch); } } generateOuterNullsRepeatedAll(batch); break; } } /** * Generate the non-match outer join output results for the whole repeating vectorized * row batch. * * Each row will get nulls for all small table values. * * @param batch * The big table batch. */ protected void generateOuterNullsRepeatedAll(VectorizedRowBatch batch) throws HiveException { for (int column : smallTableOutputVectorColumns) { ColumnVector colVector = batch.cols[column]; colVector.noNulls = false; colVector.isNull[0] = true; colVector.isRepeating = true; } // Mark any scratch small table scratch columns that would normally receive a copy of the key // as null, too. for (int column : bigTableOuterKeyOutputVectorColumns) { ColumnVector colVector = batch.cols[column]; colVector.noNulls = false; colVector.isNull[0] = true; colVector.isRepeating = true; } } }