Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
FunctionCallExpr.java
Go to the documentation of this file.
1 // Copyright 2012 Cloudera Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.cloudera.impala.analysis;
16 
17 import java.util.List;
18 
28 import com.cloudera.impala.common.TreeNode;
29 import com.cloudera.impala.thrift.TAggregateExpr;
30 import com.cloudera.impala.thrift.TExprNode;
31 import com.cloudera.impala.thrift.TExprNodeType;
32 import com.cloudera.impala.thrift.TFunctionBinaryType;
33 import com.google.common.base.Joiner;
34 import com.google.common.base.Objects;
35 import com.google.common.base.Preconditions;
36 
37 public class FunctionCallExpr extends Expr {
38  private final FunctionName fnName_;
39  private final FunctionParams params_;
40  private boolean isAnalyticFnCall_ = false;
41  private boolean isInternalFnCall_ = false;
42 
43  // Indicates whether this is a merge aggregation function that should use the merge
44  // instead of the update symbol. This flag also affects the behavior of
45  // resetAnalysisState() which is used during expr substitution.
46  private final boolean isMergeAggFn_;
47 
48  // Printed in toSqlImpl(), if set. Used for merge agg fns.
49  private String label_;
50 
51  public FunctionCallExpr(String functionName, List<Expr> params) {
52  this(new FunctionName(functionName), new FunctionParams(false, params));
53  }
54 
55  public FunctionCallExpr(FunctionName fnName, List<Expr> params) {
56  this(fnName, new FunctionParams(false, params));
57  }
58 
59  public FunctionCallExpr(FunctionName fnName, FunctionParams params) {
60  this(fnName, params, false);
61  }
62 
64  FunctionName fnName, FunctionParams params, boolean isMergeAggFn) {
65  super();
66  fnName_ = fnName;
67  params_ = params;
69  if (params.exprs() != null) children_.addAll(params.exprs());
70  }
71 
76  public static Expr createExpr(FunctionName fnName, FunctionParams params) {
77  FunctionCallExpr functionCallExpr = new FunctionCallExpr(fnName, params);
78  if (fnName.getFnNamePath().get(0).equals("decode")
79  && (fnName.getDb() == null) || Catalog.BUILTINS_DB.equals(fnName.getDb())) {
80  // If someone created the DECODE function before it became a builtin, they can
81  // continue to use it by the fully qualified name.
82  return new CaseExpr(functionCallExpr);
83  }
84  return functionCallExpr;
85  }
86 
92  FunctionCallExpr agg, List<Expr> params) {
93  Preconditions.checkState(agg.isAnalyzed_);
94  Preconditions.checkState(agg.isAggregateFunction());
96  agg.fnName_, new FunctionParams(false, params), true);
97  // Inherit the function object from 'agg'.
98  result.fn_ = agg.fn_;
99  result.type_ = agg.type_;
100  // Set an explicit label based on the input agg.
101  if (agg.isMergeAggFn_) {
102  result.label_ = agg.label_;
103  } else {
104  // fn(input) becomes fn:merge(input).
105  result.label_ = agg.toSql().replaceFirst(agg.fnName_.toString(),
106  agg.fnName_.toString() + ":merge");
107  }
108  Preconditions.checkState(!result.type_.isWildcardDecimal());
109  return result;
110  }
111 
116  super(other);
117  fnName_ = other.fnName_;
118  isAnalyticFnCall_ = other.isAnalyticFnCall_;
119  isInternalFnCall_ = other.isInternalFnCall_;
120  isMergeAggFn_ = other.isMergeAggFn_;
121  // No need to deep clone the params, its exprs are already in children_.
122  params_ = other.params_;
123  label_ = other.label_;
124  }
125 
126  public boolean isMergeAggFn() { return isMergeAggFn_; }
127 
128  @Override
129  public void resetAnalysisState() {
130  isAnalyzed_ = false;
131  // Resolving merge agg functions after substitution may fail e.g., if the
132  // intermediate agg type is not the same as the output type. Preserve the original
133  // fn_ such that analyze() hits the special-case code for merge agg fns that
134  // handles this case.
135  if (!isMergeAggFn_) fn_ = null;
136  }
137 
138  @Override
139  public boolean equals(Object obj) {
140  if (!super.equals(obj)) return false;
142  return fnName_.equals(o.fnName_) &&
143  params_.isDistinct() == o.params_.isDistinct() &&
144  params_.isStar() == o.params_.isStar();
145  }
146 
147  @Override
148  public String toSqlImpl() {
149  if (label_ != null) return label_;
150  // Merge agg fns should have an explicit label.
151  Preconditions.checkState(!isMergeAggFn_);
152  StringBuilder sb = new StringBuilder();
153  sb.append(fnName_).append("(");
154  if (params_.isStar()) sb.append("*");
155  if (params_.isDistinct()) sb.append("DISTINCT ");
156  sb.append(Joiner.on(", ").join(childrenToSql())).append(")");
157  return sb.toString();
158  }
159 
160  @Override
161  public String debugString() {
162  return Objects.toStringHelper(this)
163  .add("name", fnName_)
164  .add("isStar", params_.isStar())
165  .add("isDistinct", params_.isDistinct())
166  .addValue(super.debugString())
167  .toString();
168  }
169 
170  public FunctionParams getParams() { return params_; }
171  public boolean isScalarFunction() {
172  Preconditions.checkNotNull(fn_);
173  return fn_ instanceof ScalarFunction ;
174  }
175 
176  public Type getReturnType() {
177  Preconditions.checkNotNull(fn_);
178  return fn_.getReturnType();
179  }
180 
184  public boolean isAggregateFunction() {
185  Preconditions.checkNotNull(fn_);
186  return fn_ instanceof AggregateFunction && !isAnalyticFnCall_;
187  }
188 
193  public boolean returnsNonNullOnEmpty() {
194  Preconditions.checkNotNull(fn_);
195  return fn_ instanceof AggregateFunction &&
196  ((AggregateFunction)fn_).returnsNonNullOnEmpty();
197  }
198 
199  public boolean isDistinct() {
200  Preconditions.checkState(isAggregateFunction());
201  return params_.isDistinct();
202  }
203 
204  public FunctionName getFnName() { return fnName_; }
205  public void setIsAnalyticFnCall(boolean v) { isAnalyticFnCall_ = v; }
206  public void setIsInternalFnCall(boolean v) { isInternalFnCall_ = v; }
207 
208  @Override
209  protected void toThrift(TExprNode msg) {
211  msg.node_type = TExprNodeType.AGGREGATE_EXPR;
212  if (!isAnalyticFnCall_) msg.setAgg_expr(new TAggregateExpr(isMergeAggFn_));
213  } else {
214  msg.node_type = TExprNodeType.FUNCTION_CALL;
215  }
216  }
217 
221  @Override
222  public boolean isConstant() {
223  if (fn_ != null && fn_ instanceof AggregateFunction) return false;
224  return super.isConstant();
225  }
226 
227  // Provide better error message for some aggregate builtins. These can be
228  // a bit more user friendly than a generic function not found.
229  // TODO: should we bother to do this? We could also improve the general
230  // error messages. For example, listing the alternatives.
231  protected String getFunctionNotFoundError(Type[] argTypes) {
232  if (fnName_.isBuiltin()) {
233  // Some custom error message for builtins
234  if (params_.isStar()) {
235  return "'*' can only be used in conjunction with COUNT";
236  }
237  if (fnName_.getFunction().equalsIgnoreCase("count")) {
238  if (!params_.isDistinct() && argTypes.length > 1) {
239  return "COUNT must have DISTINCT for multiple arguments: " + toSql();
240  }
241  }
242  if (fnName_.getFunction().equalsIgnoreCase("sum")) {
243  return "SUM requires a numeric parameter: " + toSql();
244  }
245  if (fnName_.getFunction().equalsIgnoreCase("avg")) {
246  return "AVG requires a numeric or timestamp parameter: " + toSql();
247  }
248  }
249 
250  String[] argTypesSql = new String[argTypes.length];
251  for (int i = 0; i < argTypes.length; ++i) {
252  argTypesSql[i] = argTypes[i].toSql();
253  }
254  return String.format(
255  "No matching function with signature: %s(%s).",
256  fnName_, params_.isStar() ? "*" : Joiner.on(", ").join(argTypesSql));
257  }
258 
271  Preconditions.checkState(type_.isWildcardDecimal());
272  Preconditions.checkState(fn_.getBinaryType() == TFunctionBinaryType.BUILTIN);
273  Preconditions.checkState(children_.size() > 0);
274 
275  // Find first decimal input (some functions, such as if(), begin with non-decimal
276  // arguments).
277  ScalarType childType = null;
278  for (Expr child : children_) {
279  if (child.type_.isDecimal()) {
280  childType = (ScalarType) child.type_;
281  break;
282  }
283  }
284  Preconditions.checkState(childType != null && !childType.isWildcardDecimal());
285  Type returnType = childType;
286 
287  if (fnName_.getFunction().equalsIgnoreCase("sum")) {
288  return childType.getMaxResolutionType();
289  }
290 
291  int digitsBefore = childType.decimalPrecision() - childType.decimalScale();
292  int digitsAfter = childType.decimalScale();
293  if (fnName_.getFunction().equalsIgnoreCase("ceil") ||
294  fnName_.getFunction().equalsIgnoreCase("ceiling") ||
295  fnName_.getFunction().equals("floor")) {
296  // These functions just return with scale 0 but can trigger rounding. We need
297  // to increase the precision by 1 to handle that.
298  ++digitsBefore;
299  digitsAfter = 0;
300  } else if (fnName_.getFunction().equalsIgnoreCase("truncate") ||
301  fnName_.getFunction().equalsIgnoreCase("round")) {
302  if (children_.size() > 1) {
303  // The second argument to these functions is the desired scale, otherwise
304  // the default is 0.
305  Preconditions.checkState(children_.size() == 2);
306  if (children_.get(1).isNullLiteral()) {
308  "() cannot be called with a NULL second argument.");
309  }
310 
311  if (!children_.get(1).isConstant()) {
312  // We don't allow calling truncate or round with a non-constant second
313  // (desired scale) argument. e.g. select round(col1, col2). This would
314  // mean we don't know the scale of the resulting type and would need some
315  // kind of dynamic type handling which is not yet possible. This seems like
316  // a reasonable restriction.
318  "() must be called with a constant second argument.");
319  }
321  children_.get(1), analyzer.getQueryCtx());
322  digitsAfter = (int)scaleLiteral.getLongValue();
323  if (Math.abs(digitsAfter) > ScalarType.MAX_SCALE) {
324  throw new AnalysisException("Cannot round/truncate to scales greater than " +
325  ScalarType.MAX_SCALE + ".");
326  }
327  // Round/Truncate to a negative scale means to round to the digit before
328  // the decimal e.g. round(1234.56, -2) would be 1200.
329  // The resulting scale is always 0.
330  digitsAfter = Math.max(digitsAfter, 0);
331  } else {
332  // Round()/Truncate() with no second argument.
333  digitsAfter = 0;
334  }
335 
336  if (fnName_.getFunction().equalsIgnoreCase("round") &&
337  digitsAfter < childType.decimalScale()) {
338  // If we are rounding to fewer decimal places, it's possible we need another
339  // digit before the decimal.
340  ++digitsBefore;
341  }
342  }
343  Preconditions.checkState(returnType.isDecimal() && !returnType.isWildcardDecimal());
344  return ScalarType.createDecimalTypeInternal(digitsBefore + digitsAfter, digitsAfter);
345  }
346 
347  @Override
348  public void analyze(Analyzer analyzer) throws AnalysisException {
349  if (isAnalyzed_) return;
350  super.analyze(analyzer);
351  fnName_.analyze(analyzer);
352 
353  if (isMergeAggFn_) {
354  // This is the function call expr after splitting up to a merge aggregation.
355  // The function has already been analyzed so just do the minimal sanity
356  // check here.
358  Preconditions.checkNotNull(aggFn);
359  Type intermediateType = aggFn.getIntermediateType();
360  if (intermediateType == null) intermediateType = type_;
361  Preconditions.checkState(!type_.isWildcardDecimal());
362  return;
363  }
364 
365  Type[] argTypes = collectChildReturnTypes();
366 
367  // User needs DB access.
368  Db db = analyzer.getDb(fnName_.getDb(), Privilege.VIEW_METADATA, true);
369  if (!db.containsFunction(fnName_.getFunction())) {
370  throw new AnalysisException(fnName_ + "() unknown");
371  }
372 
373  if (fnName_.getFunction().equals("count") && params_.isDistinct()) {
374  // Treat COUNT(DISTINCT ...) special because of how we do the rewrite.
375  // There is no version of COUNT() that takes more than 1 argument but after
376  // the rewrite, we only need count(*).
377  // TODO: fix how we rewrite count distinct.
378  argTypes = new Type[0];
379  Function searchDesc = new Function(fnName_, argTypes, Type.INVALID, false);
380  fn_ = db.getFunction(searchDesc, Function.CompareMode.IS_SUPERTYPE_OF);
381  type_ = fn_.getReturnType();
382  // Make sure BE doesn't see any TYPE_NULL exprs
383  for (int i = 0; i < children_.size(); ++i) {
384  if (getChild(i).getType().isNull()) {
386  }
387  }
388  return;
389  }
390 
391  // TODO: We allow implicit cast from string->timestamp but only
392  // support avg(timestamp). This means avg(string_col) would work
393  // from our casting rules. This is not right.
394  // We need to revisit where implicit casts are allowed for string
395  // to timestamp
396  if (fnName_.getFunction().equalsIgnoreCase("avg") &&
397  children_.size() == 1 && children_.get(0).getType().isStringType()) {
398  throw new AnalysisException(
399  "AVG requires a numeric or timestamp parameter: " + toSql());
400  }
401 
402  Function searchDesc = new Function(fnName_, argTypes, Type.INVALID, false);
403  fn_ = db.getFunction(searchDesc, Function.CompareMode.IS_SUPERTYPE_OF);
404 
405  if (fn_ == null || (!isInternalFnCall_ && !fn_.userVisible())) {
406  throw new AnalysisException(getFunctionNotFoundError(argTypes));
407  }
408 
409  if (isAggregateFunction()) {
410  // subexprs must not contain aggregates
411  if (TreeNode.contains(children_, Expr.isAggregatePredicate())) {
412  throw new AnalysisException(
413  "aggregate function must not contain aggregate parameters: " + this.toSql());
414  }
415 
416  // .. or analytic exprs
417  if (Expr.contains(children_, AnalyticExpr.class)) {
418  throw new AnalysisException(
419  "aggregate function must not contain analytic parameters: " + this.toSql());
420  }
421 
422  // The catalog contains count() with no arguments to handle count(*) but don't
423  // accept count().
424  // TODO: can this be handled more cleanly. It does seem like a special case since
425  // no other aggregate functions (currently) can accept '*'.
426  if (fnName_.getFunction().equalsIgnoreCase("count") &&
427  !params_.isStar() && children_.size() == 0) {
428  throw new AnalysisException("count() is not allowed.");
429  }
430 
431  // TODO: the distinct rewrite does not handle this but why?
432  if (params_.isDistinct()) {
433  if (fnName_.getFunction().equalsIgnoreCase("group_concat")) {
434  throw new AnalysisException("GROUP_CONCAT() does not support DISTINCT.");
435  }
436  if (fn_.getBinaryType() != TFunctionBinaryType.BUILTIN) {
437  throw new AnalysisException("User defined aggregates do not support DISTINCT.");
438  }
439  }
440 
442  if (aggFn.ignoresDistinct()) params_.setIsDistinct(false);
443  }
444 
446  if (fn_ instanceof AggregateFunction
447  && ((AggregateFunction) fn_).isAnalyticFn()
448  && !((AggregateFunction) fn_).isAggregateFn()
449  && !isAnalyticFnCall_) {
450  throw new AnalysisException(
451  "Analytic function requires an OVER clause: " + toSql());
452  }
453 
454  castForFunctionCall(false);
455  type_ = fn_.getReturnType();
456  if (type_.isDecimal() && type_.isWildcardDecimal()) {
457  type_ = resolveDecimalReturnType(analyzer);
458  }
459 
460  // We do not allow any function to return a type CHAR or VARCHAR
461  // TODO add support for CHAR(N) and VARCHAR(N) return values in post 2.0,
462  // support for this was not added to the backend in 2.0
463  if (type_.isWildcardChar() || type_.isWildcardVarchar()) {
465  }
466  }
467 
473  throws AnalysisException {
474  if (params.isStar()) {
475  throw new AnalysisException("Cannot pass '*' to scalar function.");
476  }
477  if (params.isDistinct()) {
478  throw new AnalysisException("Cannot pass 'DISTINCT' to scalar function.");
479  }
480  }
481 
482  @Override
483  public Expr clone() { return new FunctionCallExpr(this); }
484 }
List< String > childrenToSql()
Definition: Expr.java:531
void uncheckedCastChild(Type targetType, int childIndex)
Definition: Expr.java:1024
FunctionCallExpr(FunctionName fnName, List< Expr > params)
static com.google.common.base.Predicate< Expr > isAggregatePredicate()
Definition: Expr.java:523
void castForFunctionCall(boolean ignoreWildcardDecimals)
Definition: Expr.java:312
TFunctionBinaryType getBinaryType()
Definition: Function.java:123
static final ScalarType STRING
Definition: Type.java:53
static LiteralExpr create(String value, Type type)
static Expr createExpr(FunctionName fnName, FunctionParams params)
static void validateScalarFnParams(FunctionParams params)
static final ScalarType BOOLEAN
Definition: Type.java:46
FunctionCallExpr(String functionName, List< Expr > params)
FunctionCallExpr(FunctionName fnName, FunctionParams params, boolean isMergeAggFn)
static FunctionCallExpr createMergeAggCall(FunctionCallExpr agg, List< Expr > params)
boolean containsFunction(String name)
Definition: Db.java:131
static final ScalarType INVALID
Definition: Type.java:44
FunctionCallExpr(FunctionName fnName, FunctionParams params)