OmniSciDB  b28c0d5765
 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 
4 
5 import static org.apache.calcite.runtime.Resources.BaseMessage;
6 import static org.apache.calcite.runtime.Resources.ExInst;
7 
9 
10 import org.apache.calcite.linq4j.Ord;
11 import org.apache.calcite.rel.type.RelDataType;
12 import org.apache.calcite.rel.type.RelDataTypeField;
13 import org.apache.calcite.runtime.CalciteException;
14 import org.apache.calcite.runtime.Resources;
15 import org.apache.calcite.sql.SqlBasicCall;
16 import org.apache.calcite.sql.SqlCall;
17 import org.apache.calcite.sql.SqlCallBinding;
18 import org.apache.calcite.sql.SqlFunctionCategory;
19 import org.apache.calcite.sql.SqlIdentifier;
20 import org.apache.calcite.sql.SqlKind;
21 import org.apache.calcite.sql.SqlNode;
22 import org.apache.calcite.sql.SqlOperandCountRange;
24 import org.apache.calcite.sql.SqlSyntax;
25 import org.apache.calcite.sql.SqlUtil;
26 import org.apache.calcite.sql.type.SqlOperandCountRanges;
27 import org.apache.calcite.sql.type.SqlOperandTypeChecker;
28 import org.apache.calcite.sql.type.SqlTypeFamily;
29 import org.apache.calcite.sql.type.SqlTypeName;
30 import org.apache.calcite.sql.validate.SqlNameMatchers;
31 import org.apache.calcite.sql.validate.SqlValidator;
32 import org.apache.calcite.sql.validate.SqlValidatorException;
33 import org.apache.calcite.sql.validate.SqlValidatorScope;
34 
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.List;
39 import java.util.Set;
40 import java.util.stream.Collectors;
41 
42 public class ExtTableFunctionTypeChecker implements SqlOperandTypeChecker {
44 
46  this.opTable = opTable;
47  }
48 
49  public boolean isOptional(int argIndex) {
50  // We need to mark all arguments as optional, otherwise Calcite may invalidate some
51  // valid calls during the validation process. This is because if it previously bound
52  // the call to a UDTF operator that receives more arguments, it fills up the missing
53  // arguments with DEFAULTs. DEFAULT arguments however will not get to the typechecking
54  // stage if they are not optional for that oeprator.
55  return true;
56  }
57 
59  SqlCallBinding callBinding,
60  SqlNode node,
61  int iFormalOperand) {
62  SqlCall permutedCall = callBinding.permutedCall();
63  SqlNode permutedOperand = permutedCall.operand(iFormalOperand);
64  RelDataType type;
65 
66  // For candidate calls to incompatible operators, type inference of operands may fail.
67  // In that case, we just catch the exception and invalidade the candidate.
68  try {
69  type = callBinding.getValidator().deriveType(
70  callBinding.getScope(), permutedOperand);
71  } catch (Exception e) {
72  return false;
73  }
74  SqlTypeName typeName = type.getSqlTypeName();
75  SqlTypeFamily formalTypeFamily =
76  toSqlTypeName(tf.getArgTypes().get(iFormalOperand)).getFamily();
77 
78  if (typeName == SqlTypeName.CURSOR) {
79  SqlCall cursorCall = (SqlCall) permutedOperand;
80  RelDataType cursorType = callBinding.getValidator().deriveType(
81  callBinding.getScope(), cursorCall.operand(0));
82  return doesCursorOperandTypeMatch(tf, iFormalOperand, cursorType);
83  } else {
84  return formalTypeFamily.getTypeNames().contains(typeName);
85  }
86  }
87 
88  public boolean checkOperandTypes(SqlCallBinding callBinding, boolean throwOnFailure) {
89  Set<ExtTableFunction> candidateOverloads = new HashSet<ExtTableFunction>(
90  getOperatorOverloads(callBinding.getOperator()));
91 
92  for (SqlNode operand : callBinding.getCall().getOperandList()) {
93  if (operand != null && operand.getKind() == SqlKind.ARGUMENT_ASSIGNMENT) {
94  final SqlCall assignmentCall = (SqlCall) operand;
95  final SqlIdentifier id = assignmentCall.operand(1);
96  final String paramName = id.getSimple();
97  if (!candidateOverloads.stream().anyMatch(
98  tf -> tf.getParamNames().contains(paramName))) {
99  throw newExtTableFunctionNameError(callBinding, operand, id.getSimple());
100  }
101  }
102  }
103 
104  // Remove all candidates whose number of formal args doesn't match the
105  // call's number of real args.
106  candidateOverloads.removeIf(
107  tf -> tf.getArgTypes().size() != callBinding.getOperandCount());
108 
109  SqlNode[] operandArray = new SqlNode[callBinding.getCall().getOperandList().size()];
110  for (Ord<SqlNode> arg : Ord.zip(callBinding.getCall().getOperandList())) {
111  operandArray[arg.i] = arg.e;
112  }
113 
114  // Construct a candidate call binding for each overload. We need to do this because
115  // type inference of operands may differ depending on which operator is used. Thus,
116  // typechecking needs to be done on a candidate call-by-call basis.
117  HashMap<ExtTableFunction, SqlCallBinding> candidateBindings =
118  new HashMap<>(candidateOverloads.size());
119  for (ExtTableFunction tf : candidateOverloads) {
120  SqlBasicCall newCall = new SqlBasicCall(
121  tf, operandArray, callBinding.getCall().getParserPosition());
122  SqlCallBinding candidateBinding = new SqlCallBinding(
123  callBinding.getValidator(), callBinding.getScope(), newCall);
124  candidateBindings.put(tf, candidateBinding);
125  }
126 
127  for (int i = 0; i < operandArray.length; i++) {
128  int idx = i;
129  candidateOverloads.removeIf(tf
131  tf, candidateBindings.get(tf), operandArray[idx], idx));
132  }
133 
134  // If there are no candidates left, the call is invalid.
135  if (candidateOverloads.size() == 0) {
136  if (throwOnFailure) {
137  throw(newExtTableFunctionSignatureError(callBinding));
138  }
139  return false;
140  }
141 
142  // If there are candidates left, and the current bound operator
143  // is not one of them, rewrite the call to use a better binding.
144  if (!candidateOverloads.isEmpty()
145  && !candidateOverloads.contains(callBinding.getOperator())) {
146  ExtTableFunction optimal = candidateOverloads.iterator().next();
147  ((SqlBasicCall) callBinding.getCall()).setOperator(optimal);
148  }
149 
150  return true;
151  }
152 
154  ExtTableFunction tf, int iFormalOperand, RelDataType actualOperand) {
155  String formalOperandName = tf.getExtendedParamNames().get(iFormalOperand);
156  List<ExtArgumentType> formalFieldTypes =
157  tf.getCursorFieldTypes().get(formalOperandName);
158  List<RelDataTypeField> actualFieldList = actualOperand.getFieldList();
159 
160  // runtime functions may not have CURSOR field type information, so we default
161  // to old behavior of assuming they typecheck
162  if (formalFieldTypes.size() == 0) {
163  System.out.println(
164  "Warning: UDTF has no CURSOR field subtype data. Proceeding assuming CURSOR typechecks.");
165  return true;
166  }
167 
168  int iFormal = 0;
169  int iActual = 0;
170  while (iActual < actualFieldList.size() && iFormal < formalFieldTypes.size()) {
171  ExtArgumentType extType = formalFieldTypes.get(iFormal);
172  SqlTypeName formalType = toSqlTypeName(extType);
173  SqlTypeName actualType = actualFieldList.get(iActual).getValue().getSqlTypeName();
174 
175  if (formalType == SqlTypeName.COLUMN_LIST) {
176  ExtArgumentType colListSubtype = getValueType(extType);
177  SqlTypeName colListType = toSqlTypeName(colListSubtype);
178 
179  if (actualType != colListType) {
180  return false;
181  }
182 
183  int colListSize = 0;
184  int numFormalArgumentsLeft = (formalFieldTypes.size() - 1) - iFormal;
185  while (iActual + colListSize
186  < (actualFieldList.size() - numFormalArgumentsLeft)) {
187  actualType =
188  actualFieldList.get(iActual + colListSize).getValue().getSqlTypeName();
189  if (actualType != colListType) {
190  break;
191  }
192  colListSize++;
193  }
194  iActual += colListSize - 1;
195  } else if (formalType == SqlTypeName.ARRAY) {
196  SqlTypeName formalArraySubtype =
197  toSqlTypeName(getValueType(getValueType(extType)));
198  SqlTypeName actualArraySubtype = actualFieldList.get(iActual)
199  .getValue()
200  .getComponentType()
201  .getSqlTypeName();
202  if (formalArraySubtype != actualArraySubtype) {
203  return false;
204  }
205  } else if (formalType != actualType) {
206  return false;
207  }
208  iFormal++;
209  iActual++;
210  }
211 
212  if (iActual < actualFieldList.size()) {
213  return false;
214  }
215 
216  return true;
217  }
218 
219  public List<ExtTableFunction> getOperatorOverloads(SqlOperator op) {
220  List<SqlOperator> overloads = new ArrayList<>();
221  opTable.lookupOperatorOverloads(op.getNameAsId(),
222  SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION,
223  SqlSyntax.FUNCTION,
224  overloads,
225  SqlNameMatchers.liberal());
226 
227  return overloads.stream()
228  .filter(p -> p instanceof ExtTableFunction)
229  .map(p -> (ExtTableFunction) p)
230  .collect(Collectors.toList());
231  }
232 
233  public SqlOperandCountRange getOperandCountRange() {
234  return SqlOperandCountRanges.any();
235  }
236 
237  public String getAllowedSignatures(SqlOperator op, String opName) {
238  List<ExtTableFunction> overloads = getOperatorOverloads(op);
239  return String.join(System.lineSeparator() + "\t",
240  overloads.stream()
241  .map(tf -> tf.getExtendedSignature())
242  .collect(Collectors.toList()));
243  }
244 
245  public Consistency getConsistency() {
246  return Consistency.NONE;
247  }
248 
249  public CalciteException newExtTableFunctionNameError(
250  SqlCallBinding callBinding, SqlNode operand, String operandName) {
251  return callBinding.getValidator().newValidationError(operand,
253  callBinding.getOperator().getName(), operandName));
254  }
255 
256  public CalciteException newExtTableFunctionSignatureError(SqlCallBinding callBinding) {
257  return callBinding.getValidator().newValidationError(callBinding.permutedCall(),
258  UDTF_ERRORS.typeMismatch(callBinding.getOperator().getName(),
259  getCallSignature(callBinding,
260  callBinding.getValidator(),
261  callBinding.getScope()),
262  System.getProperty("line.separator") + "\t"
263  + callBinding.getOperator().getAllowedSignatures()));
264  }
265 
266  // Returns a call signature with detailed CURSOR type information, to emit better error
267  // messages
268  public String getCallSignature(
269  SqlCallBinding callBinding, SqlValidator validator, SqlValidatorScope scope) {
270  List<String> signatureList = new ArrayList<>();
271  for (final SqlNode operand : callBinding.permutedCall().getOperandList()) {
272  final RelDataType argType = validator.deriveType(scope, operand);
273  if (null == argType) {
274  continue;
275  } else if (argType.getSqlTypeName() == SqlTypeName.CURSOR) {
276  SqlCall cursorCall = (SqlCall) operand;
277  RelDataType cursorType = callBinding.getValidator().deriveType(
278  callBinding.getScope(), cursorCall.operand(0));
279  StringBuilder cursorTypeName = new StringBuilder();
280  cursorTypeName.append("CURSOR[");
281  for (int j = 0; j < cursorType.getFieldList().size(); j++) {
282  if (j > 0) {
283  cursorTypeName.append(",");
284  }
285  cursorTypeName.append(
286  cursorType.getFieldList().get(j).getType().getSqlTypeName());
287  }
288  cursorTypeName.append("]");
289  signatureList.add(cursorTypeName.toString());
290  } else {
291  signatureList.add(argType.toString());
292  }
293  }
294  return SqlUtil.getOperatorSignature(callBinding.getOperator(), signatureList);
295  }
296 
297  public interface ExtTableFunctionErrors {
298  @BaseMessage(
299  "No candidate for User-defined Table Function ''{0}'' with input parameter named ''{1}''")
300  ExInst<SqlValidatorException>
301  paramNameMismatch(String udtf, String wrongParamName);
302 
303  @BaseMessage(
304  "Cannot apply User-defined Table Function ''{0}'' to arguments of type {1}. Supported form(s): {2}")
305  ExInst<SqlValidatorException>
306  typeMismatch(String udtf, String call, String overloads);
307  }
308 
309  public static final ExtTableFunctionErrors UDTF_ERRORS =
310  Resources.create(ExtTableFunctionErrors.class);
311 }
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)
List< ExtTableFunction > getOperatorOverloads(SqlOperator op)
ExInst< SqlValidatorException > typeMismatch(String udtf, String call, String overloads)
boolean doesCursorOperandTypeMatch(ExtTableFunction tf, int iFormalOperand, RelDataType actualOperand)
boolean checkOperandTypes(SqlCallBinding callBinding, boolean throwOnFailure)
String getCallSignature(SqlCallBinding callBinding, SqlValidator validator, SqlValidatorScope scope)
std::string typeName(const T *v)
Definition: toString.h:103
boolean doesOperandTypeMatch(ExtTableFunction tf, SqlCallBinding callBinding, SqlNode node, int iFormalOperand)