Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
CastExpr.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 
26 import com.cloudera.impala.thrift.TExpr;
27 import com.cloudera.impala.thrift.TExprNode;
28 import com.cloudera.impala.thrift.TExprNodeType;
29 import com.google.common.base.Objects;
30 import com.google.common.base.Preconditions;
31 import com.google.common.collect.Lists;
32 
33 public class CastExpr extends Expr {
34  // Only set for explicit casts. Null for implicit casts.
35  private final TypeDef targetTypeDef_;
36 
37  // True if this is a "pre-analyzed" implicit cast.
38  private final boolean isImplicit_;
39 
40  // True if this cast does not change the type.
41  private boolean noOp_ = false;
42 
46  public CastExpr(Type targetType, Expr e) {
47  super();
48  Preconditions.checkState(targetType.isValid());
49  Preconditions.checkNotNull(e);
50  type_ = targetType;
51  targetTypeDef_ = null;
52  isImplicit_ = true;
53  // replace existing implicit casts
54  if (e instanceof CastExpr) {
55  CastExpr castExpr = (CastExpr) e;
56  if (castExpr.isImplicit()) e = castExpr.getChild(0);
57  }
58  children_.add(e);
59 
60  // Implicit casts don't call analyze()
61  // TODO: this doesn't seem like the cleanest approach but there are places
62  // we generate these (e.g. table loading) where there is no analyzer object.
63  try {
64  analyze();
66  } catch (AnalysisException ex) {
67  Preconditions.checkState(false,
68  "Implicit casts should never throw analysis exception.");
69  }
70  isAnalyzed_ = true;
71  }
72 
76  public CastExpr(TypeDef targetTypeDef, Expr e) {
77  Preconditions.checkNotNull(targetTypeDef);
78  Preconditions.checkNotNull(e);
79  isImplicit_ = false;
80  targetTypeDef_ = targetTypeDef;
81  children_.add(e);
82  }
83 
87  protected CastExpr(CastExpr other) {
88  super(other);
89  targetTypeDef_ = other.targetTypeDef_;
90  isImplicit_ = other.isImplicit_;
91  noOp_ = other.noOp_;
92  }
93 
94  private static String getFnName(Type targetType) {
95  return "castTo" + targetType.getPrimitiveType().toString();
96  }
97 
98  public static void initBuiltins(Db db) {
99  for (Type fromType : Type.getSupportedTypes()) {
100  if (fromType.isNull()) continue;
101  for (Type toType : Type.getSupportedTypes()) {
102  if (toType.isNull()) continue;
103  // Disable casting from string to boolean
104  if (fromType.isStringType() && toType.isBoolean()) continue;
105  // Disable casting from boolean/timestamp to decimal
106  if ((fromType.isBoolean() || fromType.isDateType()) && toType.isDecimal()) {
107  continue;
108  }
109  if (fromType.getPrimitiveType() == PrimitiveType.STRING
110  && toType.getPrimitiveType() == PrimitiveType.CHAR) {
111  // Allow casting from String to Char(N)
112  String beSymbol = "impala::CastFunctions::CastToChar";
113  db.addBuiltin(ScalarFunction.createBuiltin(getFnName(ScalarType.CHAR),
114  Lists.newArrayList((Type) ScalarType.STRING), false, ScalarType.CHAR,
115  beSymbol, null, null, true));
116  continue;
117  }
118  if (fromType.getPrimitiveType() == PrimitiveType.CHAR
119  && toType.getPrimitiveType() == PrimitiveType.CHAR) {
120  // Allow casting from CHAR(N) to Char(N)
121  String beSymbol = "impala::CastFunctions::CastToChar";
122  db.addBuiltin(ScalarFunction.createBuiltin(getFnName(ScalarType.CHAR),
123  Lists.newArrayList((Type) ScalarType.createCharType(-1)), false,
124  ScalarType.CHAR, beSymbol, null, null, true));
125  continue;
126  }
127  if (fromType.getPrimitiveType() == PrimitiveType.VARCHAR
128  && toType.getPrimitiveType() == PrimitiveType.VARCHAR) {
129  // Allow casting from VARCHAR(N) to VARCHAR(M)
130  String beSymbol = "impala::CastFunctions::CastToStringVal";
131  db.addBuiltin(ScalarFunction.createBuiltin(getFnName(ScalarType.VARCHAR),
132  Lists.newArrayList((Type) ScalarType.VARCHAR), false, ScalarType.VARCHAR,
133  beSymbol, null, null, true));
134  continue;
135  }
136  if (fromType.getPrimitiveType() == PrimitiveType.VARCHAR
137  && toType.getPrimitiveType() == PrimitiveType.CHAR) {
138  // Allow casting from VARCHAR(N) to CHAR(M)
139  String beSymbol = "impala::CastFunctions::CastToChar";
140  db.addBuiltin(ScalarFunction.createBuiltin(getFnName(ScalarType.CHAR),
141  Lists.newArrayList((Type) ScalarType.VARCHAR), false, ScalarType.CHAR,
142  beSymbol, null, null, true));
143  continue;
144  }
145  if (fromType.getPrimitiveType() == PrimitiveType.CHAR
146  && toType.getPrimitiveType() == PrimitiveType.VARCHAR) {
147  // Allow casting from CHAR(N) to VARCHAR(M)
148  String beSymbol = "impala::CastFunctions::CastToStringVal";
149  db.addBuiltin(ScalarFunction.createBuiltin(getFnName(ScalarType.VARCHAR),
150  Lists.newArrayList((Type) ScalarType.CHAR), false, ScalarType.VARCHAR,
151  beSymbol, null, null, true));
152  continue;
153  }
154  // Disable no-op casts
155  if (fromType.equals(toType) && !fromType.isDecimal()) continue;
156  String beClass = toType.isDecimal() || fromType.isDecimal() ?
157  "DecimalOperators" : "CastFunctions";
158  String beSymbol = "impala::" + beClass + "::CastTo" + Function.getUdfType(toType);
159  db.addBuiltin(ScalarFunction.createBuiltin(getFnName(toType),
160  Lists.newArrayList(fromType), false, toType, beSymbol,
161  null, null, true));
162  }
163  }
164  }
165 
166  @Override
167  public String toSqlImpl() {
168  if (isImplicit_) return getChild(0).toSql();
169  return "CAST(" + getChild(0).toSql() + " AS " + targetTypeDef_.toString() + ")";
170  }
171 
172  @Override
173  protected void treeToThriftHelper(TExpr container) {
174  if (noOp_) {
175  getChild(0).treeToThriftHelper(container);
176  return;
177  }
178  super.treeToThriftHelper(container);
179  }
180 
181  @Override
182  protected void toThrift(TExprNode msg) {
183  msg.node_type = TExprNodeType.FUNCTION_CALL;
184  }
185 
186  @Override
187  public String debugString() {
188  return Objects.toStringHelper(this)
189  .add("isImplicit", isImplicit_)
190  .add("target", type_)
191  .addValue(super.debugString())
192  .toString();
193  }
194 
195  public boolean isImplicit() { return isImplicit_; }
196 
197  @Override
198  public void analyze(Analyzer analyzer) throws AnalysisException {
199  if (isAnalyzed_) return;
200  Preconditions.checkState(!isImplicit_);
201  super.analyze(analyzer);
202  targetTypeDef_.analyze(analyzer);
203  type_ = targetTypeDef_.getType();
204  analyze();
205  }
206 
207  private void analyze() throws AnalysisException {
208  Preconditions.checkNotNull(type_);
209  if (type_.isComplexType()) {
210  throw new AnalysisException(
211  "Unsupported cast to complex type: " + type_.toSql());
212  }
213 
214  boolean readyForCharCast =
215  children_.get(0).getType().getPrimitiveType() == PrimitiveType.STRING ||
216  children_.get(0).getType().getPrimitiveType() == PrimitiveType.CHAR;
217  if (type_.getPrimitiveType() == PrimitiveType.CHAR && !readyForCharCast) {
218  // Back end functions only exist to cast string types to CHAR, there is not a cast
219  // for every type since it is redundant with STRING. Casts to go through 2 casts:
220  // (1) cast to string, to stringify the value
221  // (2) cast to CHAR, to truncate or pad with spaces
222  CastExpr tostring = new CastExpr(ScalarType.STRING, children_.get(0));
223  tostring.analyze();
224  children_.set(0, tostring);
225  }
226 
227  if (children_.get(0) instanceof NumericLiteral && type_.isFloatingPointType()) {
228  // Special case casting a decimal literal to a floating point number. The
229  // decimal literal can be interpreted as either and we want to avoid casts
230  // since that can result in loss of accuracy.
231  ((NumericLiteral)children_.get(0)).explicitlyCastToFloat(type_);
232  }
233 
234  if (children_.get(0).getType().isNull()) {
235  // Make sure BE never sees TYPE_NULL
237  }
238 
239  // Ensure child has non-null type (even if it's a null literal). This is required
240  // for the UDF interface.
241  if (children_.get(0) instanceof NullLiteral) {
242  NullLiteral nullChild = (NullLiteral)(children_.get(0));
243  nullChild.uncheckedCastTo(type_);
244  }
245 
246  Type childType = children_.get(0).type_;
247  Preconditions.checkState(!childType.isNull());
248  if (childType.equals(type_)) {
249  noOp_ = true;
250  return;
251  }
252 
254  Type[] args = { childType };
255  Function searchDesc = new Function(fnName, args, Type.INVALID, false);
256  if (isImplicit_) {
257  fn_ = Catalog.getBuiltin(searchDesc, CompareMode.IS_SUPERTYPE_OF);
258  Preconditions.checkState(fn_ != null);
259  } else {
260  fn_ = Catalog.getBuiltin(searchDesc, CompareMode.IS_IDENTICAL);
261  if (fn_ == null) {
262  // allow for promotion from CHAR to STRING; only if no exact match is found
263  fn_ = Catalog.getBuiltin(searchDesc.promoteCharsToStrings(),
265  }
266  }
267  if (fn_ == null) {
268  throw new AnalysisException("Invalid type cast of " + getChild(0).toSql() +
269  " from " + childType + " to " + type_);
270  }
271 
272  Preconditions.checkState(type_.matchesType(fn_.getReturnType()),
273  type_ + " != " + fn_.getReturnType());
274  }
275 
279  @Override
281  if (isImplicit_) {
282  // we don't expect to see to consecutive implicit casts
283  Preconditions.checkState(
284  !(getChild(0) instanceof CastExpr) || !((CastExpr) getChild(0)).isImplicit());
285  return getChild(0);
286  } else {
287  return this;
288  }
289  }
290 
291  @Override
292  public boolean equals(Object obj) {
293  if (this == obj) return true;
294  if (obj instanceof CastExpr) {
295  CastExpr other = (CastExpr) obj;
296  return isImplicit_ == other.isImplicit_
297  && type_.equals(other.type_)
298  && super.equals(obj);
299  }
300  // Ignore implicit casts when comparing expr trees.
301  if (isImplicit_) return getChild(0).equals(obj);
302  return false;
303  }
304 
305  @Override
306  public Expr clone() { return new CastExpr(this); }
307 }
void uncheckedCastChild(Type targetType, int childIndex)
Definition: Expr.java:1024
static final ScalarType CHAR
Definition: Type.java:65
static final ScalarType VARCHAR
Definition: Type.java:64
static final ScalarType STRING
Definition: Type.java:53
PrimitiveType getPrimitiveType()
Definition: Type.java:188
static final String BUILTINS_DB
Definition: Catalog.java:61
static ScalarType createCharType(int len)
Definition: ScalarType.java:93
CastExpr(TypeDef targetTypeDef, Expr e)
Definition: CastExpr.java:76
void analyze(Analyzer analyzer)
Definition: CastExpr.java:198
PrimitiveType
Definition: types.h:27
static String getFnName(Type targetType)
Definition: CastExpr.java:94
void treeToThriftHelper(TExpr container)
Definition: CastExpr.java:173
static final ScalarType INVALID
Definition: Type.java:44
static ArrayList< ScalarType > getSupportedTypes()
Definition: Type.java:109
CastExpr(Type targetType, Expr e)
Definition: CastExpr.java:46