/** * 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; }
public CastExpr(Type targetType, Expr e, boolean isImplicit) { super(); Preconditions.checkArgument(targetType.isValid()); this.targetType_ = targetType; this.isImplicit_ = isImplicit; Preconditions.checkNotNull(e); if (isImplicit) { // replace existing implicit casts if (e instanceof CastExpr) { CastExpr castExpr = (CastExpr) e; if (castExpr.isImplicit()) e = castExpr.getChild(0); } children_.add(e); // Implicit casts don't call analyze() // TODO: this doesn't seem like the cleanest approach but there are places // we generate these (e.g. table loading) where there is no analyzer object. try { analyze(); computeNumDistinctValues(); } catch (AnalysisException ex) { Preconditions.checkState(false, "Implicit casts should never throw analysis exception."); } isAnalyzed_ = true; } else { children_.add(e); } }
/** Checks for type compatibility of column and expr. Returns compatible (possibly cast) expr. */ private Expr checkTypeCompatibility(Column column, Expr expr) throws AnalysisException { // Check for compatible type, and add casts to the selectListExprs if necessary. // We don't allow casting to a lower precision type. Type colType = column.getType(); Type exprType = expr.getType(); // Trivially compatible. if (colType.equals(exprType)) return expr; Type compatibleType = Type.getAssignmentCompatibleType(colType, exprType); // Incompatible types. if (!compatibleType.isValid()) { throw new AnalysisException( String.format( "Target table '%s' is incompatible with SELECT / PARTITION expressions.\n" + "Expression '%s' (type: %s) is not compatible with column '%s' (type: %s)", targetTableName_, expr.toSql(), exprType, column.getName(), colType)); } // Loss of precision when inserting into the table. if (!compatibleType.equals(colType) && !compatibleType.isNull()) { throw new AnalysisException( String.format( "Possible loss of precision for target table '%s'.\n" + "Expression '%s' (type: %s) would need to be cast to %s" + " for column '%s'", targetTableName_, expr.toSql(), exprType, colType, column.getName())); } // Add a cast to the selectListExpr to the higher type. return expr.castTo(compatibleType); }
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj instanceof CastExpr) { CastExpr other = (CastExpr) obj; return isImplicit_ == other.isImplicit_ && targetType_.equals(other.targetType_) && super.equals(obj); } // Ignore implicit casts when comparing expr trees. if (isImplicit_) return getChild(0).equals(obj); return false; }
public static void initBuiltins(Db db) { for (Type fromType : Type.getSupportedTypes()) { if (fromType.isNull()) continue; for (Type toType : Type.getSupportedTypes()) { if (toType.isNull()) continue; // Disable casting from string to boolean if (fromType.isStringType() && toType.isBoolean()) continue; // Disable casting from boolean/timestamp to decimal if ((fromType.isBoolean() || fromType.isDateType()) && toType.isDecimal()) { continue; } if (fromType.getPrimitiveType() == PrimitiveType.STRING && toType.getPrimitiveType() == PrimitiveType.CHAR) { // Allow casting from String to Char(N) String beSymbol = "impala::CastFunctions::CastToChar"; db.addBuiltin( ScalarFunction.createBuiltin( getFnName(ScalarType.CHAR), Lists.newArrayList((Type) ScalarType.STRING), false, ScalarType.CHAR, beSymbol, null, null, true)); continue; } if (fromType.getPrimitiveType() == PrimitiveType.CHAR && toType.getPrimitiveType() == PrimitiveType.CHAR) { // Allow casting from CHAR(N) to Char(N) String beSymbol = "impala::CastFunctions::CastToChar"; db.addBuiltin( ScalarFunction.createBuiltin( getFnName(ScalarType.CHAR), Lists.newArrayList((Type) ScalarType.createCharType(-1)), false, ScalarType.CHAR, beSymbol, null, null, true)); continue; } if (fromType.getPrimitiveType() == PrimitiveType.VARCHAR && toType.getPrimitiveType() == PrimitiveType.VARCHAR) { // Allow casting from VARCHAR(N) to VARCHAR(M) String beSymbol = "impala::CastFunctions::CastToStringVal"; db.addBuiltin( ScalarFunction.createBuiltin( getFnName(ScalarType.VARCHAR), Lists.newArrayList((Type) ScalarType.VARCHAR), false, ScalarType.VARCHAR, beSymbol, null, null, true)); continue; } if (fromType.getPrimitiveType() == PrimitiveType.VARCHAR && toType.getPrimitiveType() == PrimitiveType.CHAR) { // Allow casting from VARCHAR(N) to CHAR(M) String beSymbol = "impala::CastFunctions::CastToChar"; db.addBuiltin( ScalarFunction.createBuiltin( getFnName(ScalarType.CHAR), Lists.newArrayList((Type) ScalarType.VARCHAR), false, ScalarType.CHAR, beSymbol, null, null, true)); continue; } if (fromType.getPrimitiveType() == PrimitiveType.CHAR && toType.getPrimitiveType() == PrimitiveType.VARCHAR) { // Allow casting from CHAR(N) to VARCHAR(M) String beSymbol = "impala::CastFunctions::CastToStringVal"; db.addBuiltin( ScalarFunction.createBuiltin( getFnName(ScalarType.VARCHAR), Lists.newArrayList((Type) ScalarType.CHAR), false, ScalarType.VARCHAR, beSymbol, null, null, true)); continue; } // Disable no-op casts if (fromType.equals(toType) && !fromType.isDecimal()) continue; String beClass = toType.isDecimal() || fromType.isDecimal() ? "DecimalOperators" : "CastFunctions"; String beSymbol = "impala::" + beClass + "::CastTo" + Function.getUdfType(toType); db.addBuiltin( ScalarFunction.createBuiltin( getFnName(toType), Lists.newArrayList(fromType), false, toType, beSymbol, null, null, true)); } } }
private static String getFnName(Type targetType) { return "castTo" + targetType.getPrimitiveType().toString(); }
private void analyze() throws AnalysisException { targetType_.analyze(); if (targetType_.isComplexType()) { throw new AnalysisException("Unsupported cast to complex type: " + targetType_.toSql()); } boolean readyForCharCast = children_.get(0).getType().getPrimitiveType() == PrimitiveType.STRING || children_.get(0).getType().getPrimitiveType() == PrimitiveType.CHAR; if (targetType_.getPrimitiveType() == PrimitiveType.CHAR && !readyForCharCast) { // Back end functions only exist to cast string types to CHAR, there is not a cast // for every type since it is redundant with STRING. Casts to go through 2 casts: // (1) cast to string, to stringify the value // (2) cast to CHAR, to truncate or pad with spaces CastExpr tostring = new CastExpr(ScalarType.STRING, children_.get(0), true); tostring.analyze(); children_.set(0, tostring); } if (children_.get(0) instanceof NumericLiteral && targetType_.isFloatingPointType()) { // Special case casting a decimal literal to a floating point number. The // decimal literal can be interpreted as either and we want to avoid casts // since that can result in loss of accuracy. ((NumericLiteral) children_.get(0)).explicitlyCastToFloat(targetType_); } if (children_.get(0).getType().isNull()) { // Make sure BE never sees TYPE_NULL uncheckedCastChild(targetType_, 0); } // Ensure child has non-null type (even if it's a null literal). This is required // for the UDF interface. if (children_.get(0) instanceof NullLiteral) { NullLiteral nullChild = (NullLiteral) (children_.get(0)); nullChild.uncheckedCastTo(targetType_); } Type childType = children_.get(0).type_; Preconditions.checkState(!childType.isNull()); if (childType.equals(targetType_)) { noOp_ = true; type_ = targetType_; return; } FunctionName fnName = new FunctionName(Catalog.BUILTINS_DB, getFnName(targetType_)); Type[] args = {childType}; Function searchDesc = new Function(fnName, args, Type.INVALID, false); if (isImplicit_) { fn_ = Catalog.getBuiltin(searchDesc, CompareMode.IS_SUPERTYPE_OF); Preconditions.checkState(fn_ != null); } else { fn_ = Catalog.getBuiltin(searchDesc, CompareMode.IS_IDENTICAL); if (fn_ == null) { // allow for promotion from CHAR to STRING; only if no exact match is found fn_ = Catalog.getBuiltin(searchDesc.promoteCharsToStrings(), CompareMode.IS_IDENTICAL); } } if (fn_ == null) { throw new AnalysisException( "Invalid type cast of " + getChild(0).toSql() + " from " + childType + " to " + targetType_); } Preconditions.checkState( targetType_.matchesType(fn_.getReturnType()), targetType_ + " != " + fn_.getReturnType()); type_ = targetType_; }
@Override public String toSqlImpl() { if (isImplicit_) return getChild(0).toSql(); return "CAST(" + getChild(0).toSql() + " AS " + targetType_.toString() + ")"; }