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