OmniSciDB  a987f07e93
 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  if (actualType != SqlTypeName.ARRAY) {
197  return false;
198  }
199 
200  SqlTypeName formalArraySubtype =
201  toSqlTypeName(getValueType(getValueType(extType)));
202  SqlTypeName actualArraySubtype = actualFieldList.get(iActual)
203  .getValue()
204  .getComponentType()
205  .getSqlTypeName();
206  if (formalArraySubtype != actualArraySubtype) {
207  return false;
208  }
209  } else if (formalType != actualType) {
210  return false;
211  }
212  iFormal++;
213  iActual++;
214  }
215 
216  if (iActual < actualFieldList.size()) {
217  return false;
218  }
219 
220  return true;
221  }
222 
223  public List<ExtTableFunction> getOperatorOverloads(SqlOperator op) {
224  List<SqlOperator> overloads = new ArrayList<>();
225  opTable.lookupOperatorOverloads(op.getNameAsId(),
226  SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION,
227  SqlSyntax.FUNCTION,
228  overloads,
229  SqlNameMatchers.liberal());
230 
231  return overloads.stream()
232  .filter(p -> p instanceof ExtTableFunction)
233  .map(p -> (ExtTableFunction) p)
234  .collect(Collectors.toList());
235  }
236 
237  public SqlOperandCountRange getOperandCountRange() {
238  return SqlOperandCountRanges.any();
239  }
240 
241  public String getAllowedSignatures(SqlOperator op, String opName) {
242  List<ExtTableFunction> overloads = getOperatorOverloads(op);
243  return String.join(System.lineSeparator() + "\t",
244  overloads.stream()
245  .map(tf -> tf.getExtendedSignature())
246  .collect(Collectors.toList()));
247  }
248 
249  public Consistency getConsistency() {
250  return Consistency.NONE;
251  }
252 
253  public CalciteException newExtTableFunctionNameError(
254  SqlCallBinding callBinding, SqlNode operand, String operandName) {
255  return callBinding.getValidator().newValidationError(operand,
257  callBinding.getOperator().getName(), operandName));
258  }
259 
260  public CalciteException newExtTableFunctionSignatureError(SqlCallBinding callBinding) {
261  return callBinding.getValidator().newValidationError(callBinding.permutedCall(),
262  UDTF_ERRORS.typeMismatch(callBinding.getOperator().getName(),
263  getCallSignature(callBinding,
264  callBinding.getValidator(),
265  callBinding.getScope()),
266  System.getProperty("line.separator") + "\t"
267  + callBinding.getOperator().getAllowedSignatures()));
268  }
269 
270  // Returns a call signature with detailed CURSOR type information, to emit better error
271  // messages
272  public String getCallSignature(
273  SqlCallBinding callBinding, SqlValidator validator, SqlValidatorScope scope) {
274  List<String> signatureList = new ArrayList<>();
275  for (final SqlNode operand : callBinding.permutedCall().getOperandList()) {
276  final RelDataType argType = validator.deriveType(scope, operand);
277  if (null == argType) {
278  continue;
279  } else if (argType.getSqlTypeName() == SqlTypeName.CURSOR) {
280  SqlCall cursorCall = (SqlCall) operand;
281  RelDataType cursorType = callBinding.getValidator().deriveType(
282  callBinding.getScope(), cursorCall.operand(0));
283  StringBuilder cursorTypeName = new StringBuilder();
284  cursorTypeName.append("CURSOR[");
285  for (int j = 0; j < cursorType.getFieldList().size(); j++) {
286  if (j > 0) {
287  cursorTypeName.append(",");
288  }
289  cursorTypeName.append(
290  cursorType.getFieldList().get(j).getType().getSqlTypeName());
291  }
292  cursorTypeName.append("]");
293  signatureList.add(cursorTypeName.toString());
294  } else {
295  signatureList.add(argType.toString());
296  }
297  }
298  return SqlUtil.getOperatorSignature(callBinding.getOperator(), signatureList);
299  }
300 
301  public interface ExtTableFunctionErrors {
302  @BaseMessage(
303  "No candidate for User-defined Table Function ''{0}'' with input parameter named ''{1}''")
304  ExInst<SqlValidatorException>
305  paramNameMismatch(String udtf, String wrongParamName);
306 
307  @BaseMessage(
308  "Cannot apply User-defined Table Function ''{0}'' to arguments of type {1}. Supported form(s): {2}")
309  ExInst<SqlValidatorException>
310  typeMismatch(String udtf, String call, String overloads);
311  }
312 
313  public static final ExtTableFunctionErrors UDTF_ERRORS =
314  Resources.create(ExtTableFunctionErrors.class);
315 }
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)