/** * Returns a tuple descriptor for the aggregation/analytic's intermediate or final result, * depending on whether isOutputTuple is true or false. Also updates the appropriate substitution * map, and creates and registers auxiliary equality predicates between the grouping slots and the * grouping exprs. */ private TupleDescriptor createTupleDesc(Analyzer analyzer, boolean isOutputTuple) { TupleDescriptor result = analyzer .getDescTbl() .createTupleDescriptor(tupleDebugName() + (isOutputTuple ? "-out" : "-intermed")); List<Expr> exprs = Lists.newArrayListWithCapacity(groupingExprs_.size() + aggregateExprs_.size()); exprs.addAll(groupingExprs_); exprs.addAll(aggregateExprs_); int aggregateExprStartIndex = groupingExprs_.size(); for (int i = 0; i < exprs.size(); ++i) { Expr expr = exprs.get(i); SlotDescriptor slotDesc = analyzer.addSlotDescriptor(result); slotDesc.setLabel(expr.toSql()); slotDesc.setStats(ColumnStats.fromExpr(expr)); Preconditions.checkState(expr.getType().isValid()); slotDesc.setType(expr.getType()); if (i < aggregateExprStartIndex) { // register equivalence between grouping slot and grouping expr; // do this only when the grouping expr isn't a constant, otherwise // it'll simply show up as a gratuitous HAVING predicate // (which would actually be incorrect if the constant happens to be NULL) if (!expr.isConstant()) { analyzer.createAuxEquivPredicate(new SlotRef(slotDesc), expr.clone()); } } else { Preconditions.checkArgument(expr instanceof FunctionCallExpr); FunctionCallExpr aggExpr = (FunctionCallExpr) expr; if (aggExpr.isMergeAggFn()) { slotDesc.setLabel(aggExpr.getChild(0).toSql()); } else { slotDesc.setLabel(aggExpr.toSql()); } // count(*) is non-nullable. if (aggExpr.getFnName().getFunction().equals("count")) { // TODO: Consider making nullability a property of types or of builtin agg fns. // row_number, rank, and dense_rank are non-nullable as well. slotDesc.setIsNullable(false); } if (!isOutputTuple) { Type intermediateType = ((AggregateFunction) aggExpr.fn_).getIntermediateType(); if (intermediateType != null) { // Use the output type as intermediate if the function has a wildcard decimal. if (!intermediateType.isWildcardDecimal()) { slotDesc.setType(intermediateType); } else { Preconditions.checkState(expr.getType().isDecimal()); } } } } } String prefix = (isOutputTuple ? "result " : "intermediate "); LOG.trace(prefix + " tuple=" + result.debugString()); return result; }
/* * Create a map from COUNT([ALL]) -> zeroifnull(COUNT([ALL])) if * i) There is no GROUP-BY, and * ii) There are other distinct aggregates to be evaluated. * This transformation is necessary for COUNT to correctly return 0 for empty * input relations. */ private Expr.SubstitutionMap createCountAllMap(List<FunctionCallExpr> aggExprs, Analyzer analyzer) throws AuthorizationException, AnalysisException { Expr.SubstitutionMap scalarCountAllMap = new Expr.SubstitutionMap(); if (groupingExprs_ != null && !groupingExprs_.isEmpty()) { // There are grouping expressions, so no substitution needs to be done. return scalarCountAllMap; } com.google.common.base.Predicate<FunctionCallExpr> isNotDistinctPred = new com.google.common.base.Predicate<FunctionCallExpr>() { public boolean apply(FunctionCallExpr expr) { return !expr.isDistinct(); } }; if (Iterables.all(aggExprs, isNotDistinctPred)) { // Only [ALL] aggs, so no substitution needs to be done. return scalarCountAllMap; } com.google.common.base.Predicate<FunctionCallExpr> isCountPred = new com.google.common.base.Predicate<FunctionCallExpr>() { public boolean apply(FunctionCallExpr expr) { return expr.getFnName().getFunction().equals("count"); } }; Iterable<FunctionCallExpr> countAllAggs = Iterables.filter(aggExprs, Predicates.and(isCountPred, isNotDistinctPred)); for (FunctionCallExpr countAllAgg : countAllAggs) { // Replace COUNT(ALL) with zeroifnull(COUNT(ALL)) ArrayList<Expr> zeroIfNullParam = Lists.newArrayList(countAllAgg.clone(null)); FunctionCallExpr zeroIfNull = new FunctionCallExpr("zeroifnull", zeroIfNullParam); zeroIfNull.analyze(analyzer); scalarCountAllMap.addMapping(countAllAgg, zeroIfNull); } return scalarCountAllMap; }
/** Build smap AVG -> SUM/COUNT; assumes that select list and having clause have been analyzed. */ private Expr.SubstitutionMap createAvgSMap( ArrayList<FunctionCallExpr> aggExprs, Analyzer analyzer) throws AnalysisException, AuthorizationException { Expr.SubstitutionMap result = new Expr.SubstitutionMap(); for (FunctionCallExpr aggExpr : aggExprs) { if (!aggExpr.getFnName().getFunction().equals("avg")) continue; // Transform avg(TIMESTAMP) to cast(avg(cast(TIMESTAMP as DOUBLE)) as TIMESTAMP) CastExpr inCastExpr = null; if (aggExpr.getChild(0).type_.getPrimitiveType() == PrimitiveType.TIMESTAMP) { inCastExpr = new CastExpr(ColumnType.DOUBLE, aggExpr.getChild(0).clone(null), false); } List<Expr> sumInputExprs = Lists.newArrayList( aggExpr.getChild(0).type_.getPrimitiveType() == PrimitiveType.TIMESTAMP ? inCastExpr : aggExpr.getChild(0).clone(null)); List<Expr> countInputExpr = Lists.newArrayList(aggExpr.getChild(0).clone(null)); FunctionCallExpr sumExpr = new FunctionCallExpr("sum", new FunctionParams(aggExpr.isDistinct(), sumInputExprs)); FunctionCallExpr countExpr = new FunctionCallExpr("count", new FunctionParams(aggExpr.isDistinct(), countInputExpr)); ArithmeticExpr divExpr = new ArithmeticExpr(ArithmeticExpr.Operator.DIVIDE, sumExpr, countExpr); if (aggExpr.getChild(0).type_.getPrimitiveType() == PrimitiveType.TIMESTAMP) { CastExpr outCastExpr = new CastExpr(ColumnType.TIMESTAMP, divExpr, false); outCastExpr.analyze(analyzer); result.addMapping(aggExpr, outCastExpr); } else { divExpr.analyze(analyzer); result.addMapping(aggExpr, divExpr); } } LOG.debug("avg smap: " + result.debugString()); return result; }