OmniSciDB  72c90bc290
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ExtTableFunctionTypeChecker.java
Go to the documentation of this file.
1 package com.mapd.calcite.parser;
2 
3 import static org.apache.calcite.runtime.Resources.BaseMessage;
4 import static org.apache.calcite.runtime.Resources.ExInst;
5 
7 
8 import org.apache.calcite.linq4j.Ord;
9 import org.apache.calcite.rel.type.RelDataType;
10 import org.apache.calcite.runtime.CalciteException;
11 import org.apache.calcite.runtime.Resources;
12 import org.apache.calcite.sql.SqlBasicCall;
13 import org.apache.calcite.sql.SqlCall;
14 import org.apache.calcite.sql.SqlCallBinding;
15 import org.apache.calcite.sql.SqlFunctionCategory;
16 import org.apache.calcite.sql.SqlIdentifier;
17 import org.apache.calcite.sql.SqlKind;
18 import org.apache.calcite.sql.SqlNode;
19 import org.apache.calcite.sql.SqlOperandCountRange;
21 import org.apache.calcite.sql.SqlSyntax;
22 import org.apache.calcite.sql.SqlUtil;
23 import org.apache.calcite.sql.type.SqlOperandCountRanges;
24 import org.apache.calcite.sql.type.SqlOperandTypeChecker;
25 import org.apache.calcite.sql.type.SqlTypeName;
26 import org.apache.calcite.sql.validate.SqlNameMatchers;
27 import org.apache.calcite.sql.validate.SqlValidator;
28 import org.apache.calcite.sql.validate.SqlValidatorException;
29 import org.apache.calcite.sql.validate.SqlValidatorScope;
30 
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.NoSuchElementException;
38 import java.util.Set;
39 import java.util.stream.Collectors;
40 import java.util.stream.IntStream;
41 
42 public class ExtTableFunctionTypeChecker implements SqlOperandTypeChecker {
44 
46  this.opTable = opTable;
47  }
48 
49  /*
50  * This function is meant to check optionality when typechecking single
51  * operators. Since we perform type checking of all possible overloads
52  * of an operator, we use each UDTF's isArgumentOptional() method instead.
53  */
54  public boolean isOptional(int argIndex) {
55  return false;
56  }
57 
58  public boolean checkOperandTypes(SqlCallBinding callBinding, boolean throwOnFailure) {
59  Set<ExtTableFunction> candidateOverloads = new HashSet<ExtTableFunction>(
60  getOperatorOverloads(callBinding.getOperator()));
61 
62  for (SqlNode operand : callBinding.getCall().getOperandList()) {
63  if (operand != null && operand.getKind() == SqlKind.ARGUMENT_ASSIGNMENT) {
64  final SqlCall assignmentCall = (SqlCall) operand;
65  final SqlIdentifier id = assignmentCall.operand(1);
66  final String paramName = id.getSimple();
67  if (!candidateOverloads.stream().anyMatch(
68  tf -> tf.getParamNames().contains(paramName))) {
69  throw newExtTableFunctionNameError(callBinding, operand, id.getSimple());
70  }
71  }
72  }
73 
74  // Remove all candidates whose number of formal args doesn't match the
75  // call's number of real args (accounting for possible default args).
76  candidateOverloads.removeIf(tf
77  -> (callBinding.getOperandCount()
78  < (tf.getArgTypes().size() - tf.getNumOptionalArguments()))
79  || (callBinding.getOperandCount() > tf.getArgTypes().size()));
80 
81  SqlNode[] operandArray = new SqlNode[callBinding.getCall().getOperandList().size()];
82  for (Ord<SqlNode> arg : Ord.zip(callBinding.getCall().getOperandList())) {
83  operandArray[arg.i] = arg.e;
84  }
85 
86  // Construct a candidate call binding for each overload. We need to do this because
87  // type inference of operands may differ depending on which operator is used. Thus,
88  // typechecking needs to be done on a candidate call-by-call basis.
89  HashMap<ExtTableFunction, SqlCallBinding> candidateBindings =
90  new HashMap<>(candidateOverloads.size());
91  for (ExtTableFunction tf : candidateOverloads) {
92  SqlBasicCall newCall = new SqlBasicCall(
93  tf, operandArray, callBinding.getCall().getParserPosition());
94  SqlCallBinding candidateBinding = new SqlCallBinding(
95  callBinding.getValidator(), callBinding.getScope(), newCall);
96  candidateBindings.put(tf, candidateBinding);
97  }
98 
99  // remove candidate calls that have DEFAULT values for operands which are
100  // mandatory
101  candidateOverloads.removeIf(tf
102  -> IntStream
103  .range(0,
104  candidateBindings.get(tf)
105  .permutedCall()
106  .getOperandList()
107  .size())
108  .anyMatch(idx
109  -> candidateBindings.get(tf)
110  .permutedCall()
111  .operand(idx)
112  .getKind()
113  == SqlKind.DEFAULT
114  && !tf.isArgumentOptional(idx)));
115 
116  // Compute a typechecking score for each candidate, taking into account type promotion
118  (HeavyDBTypeCoercion) callBinding.getValidator().getTypeCoercion();
119  Map<ExtTableFunction, Integer> scoredCandidates =
120  new HashMap<ExtTableFunction, Integer>();
121  candidateOverloads.stream().forEach(udtf
122  -> scoredCandidates.put(udtf,
123  tc.calculateTypeCoercionScore(candidateBindings.get(udtf), udtf)));
124 
125  // Use the candidate with minimum cost
126  ExtTableFunction minCandidate;
127  try {
128  minCandidate = Collections
129  .min(scoredCandidates.entrySet()
130  .stream()
131  .filter(entry -> entry.getValue() >= 0)
132  .collect(Collectors.toSet()),
133  Map.Entry.comparingByValue())
134  .getKey();
135  } catch (NoSuchElementException e) {
136  // If there are no candidates left, the call is invalid.
137  if (throwOnFailure) {
138  throw(newExtTableFunctionSignatureError(callBinding));
139  }
140  return false;
141  }
142 
143  if (scoredCandidates.get(minCandidate) > 0) {
144  tc.extTableFunctionTypeCoercion(candidateBindings.get(minCandidate), minCandidate);
145  }
146 
147  // If there are candidates left, and the current bound operator
148  // is not one of them, rewrite the call to use a better binding.
149  ((SqlBasicCall) callBinding.getCall()).setOperator(minCandidate);
150 
151  return true;
152  }
153 
154  public List<ExtTableFunction> getOperatorOverloads(SqlOperator op) {
155  List<SqlOperator> overloads = new ArrayList<>();
156  opTable.lookupOperatorOverloads(op.getNameAsId(),
157  SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION,
158  SqlSyntax.FUNCTION,
159  overloads,
160  SqlNameMatchers.liberal());
161 
162  return overloads.stream()
163  .filter(p -> p instanceof ExtTableFunction)
164  .map(p -> (ExtTableFunction) p)
165  .collect(Collectors.toList());
166  }
167 
168  public SqlOperandCountRange getOperandCountRange() {
169  return SqlOperandCountRanges.any();
170  }
171 
172  public String getAllowedSignatures(SqlOperator op, String opName) {
173  List<ExtTableFunction> overloads = getOperatorOverloads(op);
174  return String.join(System.lineSeparator() + "\t",
175  overloads.stream()
176  .map(tf -> tf.getExtendedSignature())
177  .collect(Collectors.toList()));
178  }
179 
180  public Consistency getConsistency() {
181  return Consistency.NONE;
182  }
183 
184  public CalciteException newExtTableFunctionNameError(
185  SqlCallBinding callBinding, SqlNode operand, String operandName) {
186  return callBinding.getValidator().newValidationError(operand,
188  callBinding.getOperator().getName(), operandName));
189  }
190 
191  public CalciteException newExtTableFunctionSignatureError(SqlCallBinding callBinding) {
192  return callBinding.getValidator().newValidationError(callBinding.permutedCall(),
193  UDTF_ERRORS.typeMismatch(callBinding.getOperator().getName(),
194  getCallSignature(callBinding,
195  callBinding.getValidator(),
196  callBinding.getScope()),
197  System.getProperty("line.separator") + "\t"
198  + callBinding.getOperator().getAllowedSignatures()));
199  }
200 
201  // Returns a call signature with detailed CURSOR type information, to emit
202  // better error messages
203  public String getCallSignature(
204  SqlCallBinding callBinding, SqlValidator validator, SqlValidatorScope scope) {
205  List<String> signatureList = new ArrayList<>();
206  for (final SqlNode operand : callBinding.permutedCall().getOperandList()) {
207  final RelDataType argType = validator.deriveType(scope, operand);
208  if (null == argType) {
209  continue;
210  } else if (argType.getSqlTypeName() == SqlTypeName.CURSOR) {
211  SqlCall cursorCall = (SqlCall) operand;
212  RelDataType cursorType = callBinding.getValidator().deriveType(
213  callBinding.getScope(), cursorCall.operand(0));
214  StringBuilder cursorTypeName = new StringBuilder();
215  cursorTypeName.append("CURSOR[");
216  for (int j = 0; j < cursorType.getFieldList().size(); j++) {
217  if (j > 0) {
218  cursorTypeName.append(",");
219  }
220  cursorTypeName.append(
221  cursorType.getFieldList().get(j).getType().getSqlTypeName());
222  }
223  cursorTypeName.append("]");
224  signatureList.add(cursorTypeName.toString());
225  } else {
226  signatureList.add(argType.toString());
227  }
228  }
229  return SqlUtil.getOperatorSignature(callBinding.getOperator(), signatureList);
230  }
231 
232  public interface ExtTableFunctionErrors {
233  @BaseMessage(
234  "No candidate for User-defined Table Function ''{0}'' with input parameter named ''{1}''")
235  ExInst<SqlValidatorException>
236  paramNameMismatch(String udtf, String wrongParamName);
237 
238  @BaseMessage(
239  "Cannot apply User-defined Table Function ''{0}'' to arguments of type {1}. Supported form(s): {2}")
240  ExInst<SqlValidatorException>
241  typeMismatch(String udtf, String call, String overloads);
242  }
243 
244  public static final ExtTableFunctionErrors UDTF_ERRORS =
245  Resources.create(ExtTableFunctionErrors.class);
246 }
bool contains(const T &container, const U &element)
Definition: misc.h:195
CalciteException newExtTableFunctionNameError(SqlCallBinding callBinding, SqlNode operand, String operandName)
ExInst< SqlValidatorException > paramNameMismatch(String udtf, String wrongParamName)
CalciteException newExtTableFunctionSignatureError(SqlCallBinding callBinding)
int calculateTypeCoercionScore(SqlCallBinding callBinding, ExtTableFunction udtf)
List< ExtTableFunction > getOperatorOverloads(SqlOperator op)
ExInst< SqlValidatorException > typeMismatch(String udtf, String call, String overloads)
boolean checkOperandTypes(SqlCallBinding callBinding, boolean throwOnFailure)
String getCallSignature(SqlCallBinding callBinding, SqlValidator validator, SqlValidatorScope scope)