OmniSciDB  f17484ade4
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
HeavyDBTypeCoercion.java
Go to the documentation of this file.
1 package com.mapd.calcite.parser;
2 
4 
6 
7 import org.apache.calcite.rel.type.RelDataType;
8 import org.apache.calcite.rel.type.RelDataTypeFactory;
9 import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
10 import org.apache.calcite.rel.type.RelDataTypeField;
11 import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
12 import org.apache.calcite.sql.SqlBasicCall;
13 import org.apache.calcite.sql.SqlBinaryOperator;
14 import org.apache.calcite.sql.SqlCall;
15 import org.apache.calcite.sql.SqlCallBinding;
16 import org.apache.calcite.sql.SqlKind;
17 import org.apache.calcite.sql.SqlNode;
18 import org.apache.calcite.sql.SqlSelect;
19 import org.apache.calcite.sql.type.ArraySqlType;
20 import org.apache.calcite.sql.type.IntervalSqlType;
21 import org.apache.calcite.sql.type.SqlTypeName;
22 import org.apache.calcite.sql.type.SqlTypeUtil;
23 import org.apache.calcite.sql.validate.SqlValidator;
24 import org.apache.calcite.sql.validate.SqlValidatorScope;
25 import org.apache.calcite.sql.validate.implicit.TypeCoercionImpl;
26 
27 import java.util.ArrayList;
28 import java.util.List;
29 
30 public class HeavyDBTypeCoercion extends TypeCoercionImpl {
31  public HeavyDBTypeCoercion(RelDataTypeFactory typeFactory, SqlValidator validator) {
32  super(typeFactory, validator);
33  }
34 
42  SqlCallBinding callBinding, ExtTableFunction udtf) {
43  final List<ExtArgumentType> paramTypes = udtf.getArgTypes();
44  SqlCall permutedCall = callBinding.permutedCall();
45  assert paramTypes != null;
46  int score = 0;
47  for (int i = 0; i < permutedCall.operandCount(); i++) {
48  SqlNode operand = permutedCall.operand(i);
49 
50  // DEFAULT operands should always type-check without any casting (they will be
51  // filled in later during sql2rel translation)
52  if (operand.getKind() == SqlKind.DEFAULT) {
53  continue;
54  }
55 
56  RelDataType actualRelType = validator.deriveType(callBinding.getScope(), operand);
57  RelDataType formalRelType = toRelDataType(paramTypes.get(i), factory);
58 
59  if (actualRelType.getSqlTypeName() == SqlTypeName.CURSOR) {
60  SqlCall cursorCall = (SqlCall) operand;
61  int cursorScore = calculateScoreForCursorOperand(
62  cursorCall.operand(0), i, callBinding.getScope(), udtf);
63  if (cursorScore < 0) {
64  return -1;
65  }
66  score += cursorScore;
67  } else if (actualRelType != formalRelType) {
68  if (SqlTypeUtil.isInterval(actualRelType)
69  && SqlTypeUtil.isInterval(formalRelType)) {
70  // intervals only need to match on whether they're YearMonth or Day
71  IntervalSqlType actualInterval = (IntervalSqlType) actualRelType;
72  IntervalSqlType formalInterval = (IntervalSqlType) formalRelType;
73  if (actualInterval.getIntervalQualifier().isYearMonth()
74  == formalInterval.getIntervalQualifier().isYearMonth()) {
75  continue;
76  } else {
77  return -1;
78  }
79  }
80  RelDataType widerType = getWiderTypeForTwo(actualRelType, formalRelType, false);
81  if (widerType == null) {
82  return -1;
83  } else if (!SqlTypeUtil.isTimestamp(widerType)
84  && SqlTypeUtil.sameNamedType(formalRelType, actualRelType)) {
85  // TIMESTAMPs of different precision need coercion, but other structurally equal
86  // types do not (i.e VARCHAR(1) and VARCHAR(10))
87  continue;
88  } else if (actualRelType == widerType) {
89  return -1;
90  } else if (widerType != actualRelType) {
91  score += getScoreForTypes(widerType, actualRelType, false);
92  }
93  }
94  }
95  return score;
96  }
97 
102  private int calculateScoreForCursorOperand(SqlNode cursorOperand,
103  int index,
104  SqlValidatorScope scope,
105  ExtTableFunction udtf) {
106  int score = 0;
107 
108  String formalOperandName = udtf.getExtendedParamNames().get(index);
109  List<ExtArgumentType> formalFieldTypes =
110  udtf.getCursorFieldTypes().get(formalOperandName);
111 
112  if (formalFieldTypes == null || formalFieldTypes.size() == 0) {
113  System.out.println(
114  "Warning: UDTF has no CURSOR field subtype data. Proceeding assuming CURSOR typechecks.");
115  // penalize implementations without subtype data, to favor ones that have better
116  // type matches
117  return 1000;
118  }
119 
120  switch (cursorOperand.getKind()) {
121  case SELECT: {
122  SqlSelect selectNode = (SqlSelect) cursorOperand;
123 
124  int iFormal = 0, iActual = 0;
125  for (; iActual < selectNode.getSelectList().size()
126  && iFormal < formalFieldTypes.size();
127  iActual++, iFormal++) {
128  SqlNode selectOperand = selectNode.getSelectList().get(iActual);
129  ExtArgumentType extType = formalFieldTypes.get(iFormal);
130  RelDataType formalRelType = toRelDataType(extType, factory);
131  RelDataType actualRelType = factory.createTypeWithNullability(
132  validator.deriveType(scope, selectOperand), true);
133  RelDataType widerType = getWiderTypeForTwo(formalRelType, actualRelType, false);
134 
135  if (formalRelType.getSqlTypeName() == SqlTypeName.COLUMN_LIST) {
136  ExtArgumentType colListSubtype = getValueType(extType);
137  RelDataType formalSubtype = toRelDataType(colListSubtype, factory);
138 
139  if (isArrayType(colListSubtype)
140  && actualRelType.getSqlTypeName() == SqlTypeName.ARRAY) {
141  ArraySqlType formalArrayType = (ArraySqlType) formalSubtype;
142  ArraySqlType actualArrayType = (ArraySqlType) actualRelType;
143  if (!SqlTypeUtil.sameNamedType(formalArrayType.getComponentType(),
144  actualArrayType.getComponentType())) {
145  // Arrays are not castable, so arrays whose underlying types don't match
146  // can fail early
147  return -1;
148  }
149  }
150 
151  widerType = getWiderTypeForTwo(actualRelType, formalSubtype, false);
152  if (!SqlTypeUtil.sameNamedType(actualRelType, formalSubtype)) {
153  if (widerType == null || widerType == actualRelType) {
154  // no common type, or actual type is wider than formal
155  return -1;
156  }
157  }
158 
159  int colListSize = 0;
160  int numFormalArgumentsLeft = (formalFieldTypes.size() - 1) - iFormal;
161  int maxColListSize =
162  selectNode.getSelectList().size() - numFormalArgumentsLeft - iActual;
163  while (colListSize < maxColListSize) {
164  SqlNode curOperand = selectNode.getSelectList().get(iActual + colListSize);
165  actualRelType = scope.getValidator().deriveType(scope, curOperand);
166  widerType = getWiderTypeForTwo(formalSubtype, actualRelType, false);
167  if (!SqlTypeUtil.sameNamedType(actualRelType, formalSubtype)) {
168  if (widerType == null
169  || !SqlTypeUtil.sameNamedType(widerType, formalSubtype)) {
170  // no common type, or actual type is wider than formal
171  break;
172  } else if (widerType != formalSubtype) {
173  // formal subtype is narrower than widerType, we do not support
174  // downcasting
175  break;
176  } else {
177  score += getScoreForTypes(widerType, actualRelType, true);
178  }
179  } else {
180  // Calcite considers the result of some binary operations as a wider type,
181  // even though they're not passed to the backend as such (FLOAT + literal
182  // == DOUBLE, for instance). We penalize these so that literal operands
183  // are casted regardless. (See QE-788)
184  score += shouldCoerceBinOpOperand(curOperand, widerType, scope) ? 100 : 0;
185  }
186  colListSize++;
187  }
188  iActual += colListSize - 1;
189  } else if (actualRelType != formalRelType) {
190  if (widerType == null) {
191  // no common wider type
192  return -1;
193  } else if (!SqlTypeUtil.isTimestamp(widerType)
194  && SqlTypeUtil.sameNamedType(formalRelType, actualRelType)) {
195  // TIMESTAMPs of different precision need coercion, but other structurally
196  // equal types do not (i.e VARCHAR(1) and VARCHAR(10))
197  continue;
198  } else if (actualRelType == widerType
199  || !SqlTypeUtil.sameNamedType(widerType, formalRelType)) {
200  // formal type is narrower than widerType or the provided actualType, we do
201  // not support downcasting
202  return -1;
203  } else {
204  score += getScoreForTypes(widerType, actualRelType, true);
205  }
206  } else {
207  // Calcite considers the result of some binary operations as a wider type,
208  // even though they're not passed to the backend as such (FLOAT + literal
209  // == DOUBLE, for instance). We penalize these so that literal operands
210  // are casted regardless. (See QE-788)
211  score += shouldCoerceBinOpOperand(selectOperand, widerType, scope) ? 100 : 0;
212  }
213  }
214 
215  if (iActual < selectNode.getSelectList().size()) {
216  return -1;
217  }
218  return score;
219  }
220  default: {
221  System.out.println("Unsupported subquery kind in UDTF CURSOR input argument: "
222  + cursorOperand.getKind());
223  return -1;
224  }
225  }
226  }
227 
233  public RelDataType getWiderTypeForTwo(
234  RelDataType type1, RelDataType type2, boolean stringPromotion) {
235  RelDataType returnType = super.getWiderTypeForTwo(type1, type2, stringPromotion);
236  if (SqlTypeUtil.isTimestamp(type1) && SqlTypeUtil.isTimestamp(type2)) {
237  returnType = (type1.getPrecision() > type2.getPrecision()) ? type1 : type2;
238  } else if ((SqlTypeUtil.isDouble(type1) || SqlTypeUtil.isDouble(type2))
239  && (SqlTypeUtil.isApproximateNumeric(type1)
240  && SqlTypeUtil.isApproximateNumeric(type2))) {
241  returnType = factory.createTypeWithNullability(
242  factory.createSqlType(SqlTypeName.DOUBLE), true);
243  }
244  return returnType;
245  }
246 
252  SqlCallBinding callBinding, ExtTableFunction udtf) {
253  boolean coerced = false;
254  final List<ExtArgumentType> paramTypes = udtf.getArgTypes();
255  SqlCall permutedCall = callBinding.permutedCall();
256  for (int i = 0; i < permutedCall.operandCount(); i++) {
257  SqlNode operand = permutedCall.operand(i);
258  if (operand.getKind() == SqlKind.DEFAULT) {
259  // DEFAULT operands don't need to be coerced, they will be filled in by
260  // appropriately typed constants later
261  continue;
262  }
263 
264  RelDataType actualRelType = validator.deriveType(callBinding.getScope(), operand);
265 
266  if (actualRelType.getSqlTypeName() == SqlTypeName.CURSOR) {
267  SqlCall cursorCall = (SqlCall) operand;
269  callBinding.getScope(), permutedCall, i, cursorCall.operand(0), udtf);
270  }
271 
272  RelDataType formalRelType = toRelDataType(paramTypes.get(i), factory);
273  if (actualRelType != formalRelType) {
274  if (SqlTypeUtil.isInterval(actualRelType)
275  && SqlTypeUtil.isInterval(formalRelType)) {
276  IntervalSqlType actualInterval = (IntervalSqlType) actualRelType;
277  IntervalSqlType formalInterval = (IntervalSqlType) formalRelType;
278  if (actualInterval.getIntervalQualifier().isYearMonth()
279  == formalInterval.getIntervalQualifier().isYearMonth()) {
280  continue;
281  }
282  }
283  RelDataType widerType = getWiderTypeForTwo(actualRelType, formalRelType, false);
284  if (!SqlTypeUtil.isTimestamp(widerType)
285  && SqlTypeUtil.sameNamedType(formalRelType, actualRelType)) {
286  // TIMESTAMPs of different precision need coercion, but other structurally equal
287  // types do not (i.e VARCHAR(1) and VARCHAR(10))
288  continue;
289  }
290  coerced = coerceOperandType(callBinding.getScope(), permutedCall, i, widerType)
291  || coerced;
292  }
293  }
294  return coerced;
295  }
296 
297  private void coerceCursorType(SqlValidatorScope scope,
298  SqlCall call,
299  int index,
300  SqlNode cursorOperand,
301  ExtTableFunction udtf) {
302  String formalOperandName = udtf.getExtendedParamNames().get(index);
303  List<ExtArgumentType> formalFieldTypes =
304  udtf.getCursorFieldTypes().get(formalOperandName);
305  if (formalFieldTypes == null || formalFieldTypes.size() == 0) {
306  return;
307  }
308 
309  switch (cursorOperand.getKind()) {
310  case SELECT: {
311  SqlSelect selectNode = (SqlSelect) cursorOperand;
312  int iFormal = 0, iActual = 0;
313  List<RelDataTypeField> newValidatedTypeList = new ArrayList<>();
314  for (; iActual < selectNode.getSelectList().size()
315  && iFormal < formalFieldTypes.size();
316  iFormal++, iActual++) {
317  SqlNode selectOperand = selectNode.getSelectList().get(iActual);
318  ExtArgumentType extType = formalFieldTypes.get(iFormal);
319  RelDataType formalRelType = toRelDataType(extType, factory);
320  RelDataType actualRelType = validator.deriveType(scope, selectOperand);
321  RelDataType widerType = getWiderTypeForTwo(formalRelType, actualRelType, false);
322 
323  if (isColumnArrayType(extType) || isColumnListArrayType(extType)) {
324  // Arrays can't be casted so don't bother trying
325  updateValidatedType(newValidatedTypeList, selectNode, iActual);
326  continue;
327  }
328 
329  if (formalRelType.getSqlTypeName() == SqlTypeName.COLUMN_LIST) {
330  ExtArgumentType colListSubtype = getValueType(extType);
331  RelDataType formalSubtype = toRelDataType(colListSubtype, factory);
332  widerType = getWiderTypeForTwo(actualRelType, formalSubtype, false);
333 
334  int colListSize = 0;
335  int numFormalArgumentsLeft = (formalFieldTypes.size() - 1) - iFormal;
336  int maxColListSize =
337  selectNode.getSelectList().size() - numFormalArgumentsLeft - iActual;
338  while (colListSize < maxColListSize) {
339  SqlNode curOperand = selectNode.getSelectList().get(iActual + colListSize);
340  actualRelType = scope.getValidator().deriveType(scope, curOperand);
341  widerType = getWiderTypeForTwo(formalSubtype, actualRelType, false);
342  if (!SqlTypeUtil.sameNamedType(actualRelType, formalSubtype)) {
343  if (widerType == null) {
344  break;
345  } else if (actualRelType != widerType) {
346  coerceColumnType(scope,
347  selectNode.getSelectList(),
348  iActual + colListSize,
349  widerType);
350  }
351  } else if (shouldCoerceBinOpOperand(curOperand, widerType, scope)) {
352  coerceBinOpOperand((SqlBasicCall) curOperand, widerType, scope);
353  }
354 
356  newValidatedTypeList, selectNode, iActual + colListSize);
357  colListSize++;
358  }
359  iActual += colListSize - 1;
360  } else if (actualRelType != formalRelType) {
361  if (!SqlTypeUtil.isTimestamp(widerType)
362  && SqlTypeUtil.sameNamedType(actualRelType, formalRelType)) {
363  updateValidatedType(newValidatedTypeList, selectNode, iActual);
364  continue;
365  }
366  if (widerType != actualRelType) {
367  coerceColumnType(scope, selectNode.getSelectList(), iActual, widerType);
368  }
369  updateValidatedType(newValidatedTypeList, selectNode, iActual);
370  } else {
371  if (shouldCoerceBinOpOperand(selectOperand, widerType, scope)) {
372  coerceBinOpOperand((SqlBasicCall) selectOperand, widerType, scope);
373  }
374  // keep old validated type for argument that was not coerced
375  updateValidatedType(newValidatedTypeList, selectNode, iActual);
376  }
377  }
378  RelDataType newCursorStructType = factory.createStructType(newValidatedTypeList);
379  RelDataType newCursorType = factory.createTypeWithNullability(newCursorStructType,
380  validator.getValidatedNodeType(selectNode).isNullable());
381  validator.setValidatedNodeType(selectNode, newCursorType);
382  break;
383  }
384  default: {
385  return;
386  }
387  }
388  }
389 
395  @Override
396  protected boolean needToCast(
397  SqlValidatorScope scope, SqlNode node, RelDataType toType) {
398  RelDataType fromType = validator.deriveType(scope, node);
399  // This depends on the fact that type validate happens before coercion.
400  // We do not have inferred type for some node, i.e. LOCALTIME.
401  if (fromType == null) {
402  return false;
403  }
404 
405  // This prevents that we cast a JavaType to normal RelDataType.
406  if (fromType instanceof RelDataTypeFactoryImpl.JavaType
407  && toType.getSqlTypeName() == fromType.getSqlTypeName()) {
408  return false;
409  }
410 
411  // Do not make a cast when we don't know specific type (ANY) of the origin node.
412  if (toType.getSqlTypeName() == SqlTypeName.ANY
413  || fromType.getSqlTypeName() == SqlTypeName.ANY) {
414  return false;
415  }
416 
417  // No need to cast between char and varchar.
418  if (SqlTypeUtil.isCharacter(toType) && SqlTypeUtil.isCharacter(fromType)) {
419  return false;
420  }
421 
422  // Implicit type coercion does not handle nullability.
423  if (SqlTypeUtil.equalSansNullability(factory, fromType, toType)) {
424  return false;
425  }
426  // Should keep sync with rules in SqlTypeCoercionRule.
427  assert SqlTypeUtil.canCastFrom(toType, fromType, true);
428  return true;
429  }
430 
434  private void updateValidatedType(
435  List<RelDataTypeField> typeList, SqlSelect selectNode, int operandIndex) {
436  SqlNode operand = selectNode.getSelectList().get(operandIndex);
437  RelDataType newType = validator.getValidatedNodeType(operand);
438  if (operand instanceof SqlCall) {
439  SqlCall asCall = (SqlCall) operand;
440  if (asCall.getOperator().getKind() == SqlKind.AS) {
441  newType = validator.getValidatedNodeType(asCall.operand(0));
442  }
443  }
444  RelDataTypeField oldTypeField =
445  validator.getValidatedNodeType(selectNode).getFieldList().get(operandIndex);
446  RelDataTypeField newTypeField = new RelDataTypeFieldImpl(
447  oldTypeField.getName(), oldTypeField.getIndex(), newType);
448  typeList.add(newTypeField);
449  }
450 
457  private int getScoreForTypes(
458  RelDataType targetType, RelDataType originalType, boolean isCursorArgument) {
459  // we assume casting columns is 100x more expensive than casting scalars
460  int baseScore = isCursorArgument ? 100 : 1;
461  switch (originalType.getSqlTypeName()) {
462  case TINYINT:
463  case SMALLINT:
464  case INTEGER:
465  case BIGINT: {
466  int multiplier = 1;
467  if (SqlTypeUtil.isApproximateNumeric(targetType)) {
468  // should favor keeping integer types over promoting to floating point
469  multiplier = 10;
470  } /* TODO: Re-enable cast to string types after ColumnList binding is resolved
471  else if (SqlTypeUtil.inCharFamily(targetType)) {
472  // promoting to char types should be a last resort
473  multiplier = 1000;
474  }*/
475  return baseScore * multiplier;
476  }
477  default: {
478  // promoting to char types should be a last resort
479  /* TODO: Re-enable cast to string types after ColumnList binding is resolved
480  int multiplier = (SqlTypeUtil.inCharFamily(targetType) ? 1000 : 1);
481  */
482  return baseScore ;
483  }
484  }
485  }
486 
490  private void coerceBinOpOperand(
491  SqlBasicCall binOp, RelDataType targetType, SqlValidatorScope scope) {
492  if (binOp.getKind() == SqlKind.AS) {
493  binOp = binOp.operand(0);
494  }
495  coerceOperandType(scope, binOp, 0, targetType);
496  coerceOperandType(scope, binOp, 1, targetType);
497  }
498 
507  private boolean shouldCoerceBinOpOperand(
508  SqlNode op, RelDataType targetType, SqlValidatorScope scope) {
509  if (op instanceof SqlBasicCall) {
510  SqlBasicCall asCall = (SqlBasicCall) op;
511  if (asCall.getOperator().getKind() == SqlKind.AS) {
512  SqlNode op2 = asCall.operand(0);
513  if (op2 instanceof SqlBasicCall) {
514  asCall = (SqlBasicCall) op2;
515  } else {
516  return false;
517  }
518  }
519  if (asCall.getOperator() instanceof SqlBinaryOperator) {
520  SqlNode lhs = asCall.operand(0);
521  SqlNode rhs = asCall.operand(1);
522  RelDataType lhsType = validator.deriveType(scope, lhs);
523  RelDataType rhsType = validator.deriveType(scope, rhs);
524  // if neither operand is already the wider type, and at least one is a literal,
525  // depending on precedence the result might falsely typecheck as the wider type
526  if (lhsType != targetType && rhsType != targetType
527  && (lhs.getKind() == SqlKind.LITERAL
528  || rhs.getKind() == SqlKind.LITERAL)) {
529  return true;
530  }
531  }
532  }
533  return false;
534  }
535 }
void updateValidatedType(List< RelDataTypeField > typeList, SqlSelect selectNode, int operandIndex)
boolean extTableFunctionTypeCoercion(SqlCallBinding callBinding, ExtTableFunction udtf)
boolean needToCast(SqlValidatorScope scope, SqlNode node, RelDataType toType)
boolean shouldCoerceBinOpOperand(SqlNode op, RelDataType targetType, SqlValidatorScope scope)
void coerceBinOpOperand(SqlBasicCall binOp, RelDataType targetType, SqlValidatorScope scope)
int getScoreForTypes(RelDataType targetType, RelDataType originalType, boolean isCursorArgument)
int calculateTypeCoercionScore(SqlCallBinding callBinding, ExtTableFunction udtf)
int calculateScoreForCursorOperand(SqlNode cursorOperand, int index, SqlValidatorScope scope, ExtTableFunction udtf)
void coerceCursorType(SqlValidatorScope scope, SqlCall call, int index, SqlNode cursorOperand, ExtTableFunction udtf)
HeavyDBTypeCoercion(RelDataTypeFactory typeFactory, SqlValidator validator)
RelDataType getWiderTypeForTwo(RelDataType type1, RelDataType type2, boolean stringPromotion)