-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[CALCITE-1581] UDTF like in hive #1138
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -900,6 +900,18 @@ ExInst<CalciteException> invalidTypesForComparison(String clazzName0, String op, | |
|
||
@BaseMessage("Not a valid input for REGEXP_REPLACE: ''{0}''") | ||
ExInst<CalciteException> invalidInputForRegexpReplace(String value); | ||
|
||
@BaseMessage("Table function is not allowed in select list in current SQL conformance level") | ||
ExInst<SqlValidatorException> notAllowTableFunctionInSelect(); | ||
|
||
@BaseMessage("''{0}'' should be a table function") | ||
ExInst<SqlValidatorException> exceptTableFunction(String name); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well this exception means we except a table function for |
||
@BaseMessage("Only one table function is allowed in select list") | ||
ExInst<SqlValidatorException> onlyOneTableFunctionAllowedInSelect(); | ||
|
||
@BaseMessage("Table function is not allowed in aggregate statement") | ||
ExInst<SqlValidatorException> notAllowTableFunctionInAggregate(); | ||
} | ||
|
||
// End CalciteResource.java |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ | |
import org.apache.calcite.sql.validate.SqlValidatorScope; | ||
import org.apache.calcite.util.Util; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import static org.apache.calcite.util.Static.RESOURCE; | ||
|
@@ -102,12 +103,26 @@ public void validateCall( | |
// we don't want to validate the identifier. | ||
final List<SqlNode> operands = call.getOperandList(); | ||
assert operands.size() == 2; | ||
assert operands.get(1) instanceof SqlIdentifier; | ||
operands.get(0).validateExpr(validator, scope); | ||
SqlIdentifier id = (SqlIdentifier) operands.get(1); | ||
if (!id.isSimple()) { | ||
throw validator.newValidationError(id, | ||
RESOURCE.aliasMustBeSimpleIdentifier()); | ||
|
||
SqlNode asIdentifier = operands.get(1); | ||
assert asIdentifier instanceof SqlIdentifier | ||
|| (asIdentifier instanceof SqlNodeList | ||
&& validator.getConformance().allowSelectTableFunction()); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should throw exception if the conformance does not allow table function instead of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a check for this in the Parser.jj, So this should not happen here. I use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simple rule of thumb. If you throw an exception (as opposed to using assert) then you must add a test for that exception. If the parser has already checked, and ensured that the condition is impossible, then you will find it impossible to write a test case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank @julianhyde . There is a test case in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused. In |
||
List<SqlNode> ids = new ArrayList<>(); | ||
if (asIdentifier instanceof SqlIdentifier) { | ||
ids.add(operands.get(1)); | ||
} else { | ||
ids.addAll(((SqlNodeList) operands.get(1)).getList()); | ||
} | ||
for (int i = 0; i < ids.size(); i++) { | ||
assert ids.get(i) instanceof SqlIdentifier; | ||
SqlIdentifier id = (SqlIdentifier) ids.get(i); | ||
if (!id.isSimple()) { | ||
throw validator.newValidationError(id, | ||
RESOURCE.aliasMustBeSimpleIdentifier()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indent -- 4 spaces There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for your suggestion. |
||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1143,6 +1143,21 @@ private void visitChild(SqlNode node) { | |
return check(type); | ||
} | ||
} | ||
|
||
/** | ||
* Whether the selectItem is a table function node in the select. | ||
* eg. "select table_func(1) as (f0,f1)" | ||
* | ||
* @param selectItem select item | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should leave a empty line before |
||
* @return true if this selectItem is a table function call | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for correct me. |
||
public static boolean isTableFunctionInSelect(SqlNode selectItem) { | ||
if (selectItem.getKind() == SqlKind.AS) { | ||
SqlBasicCall call = (SqlBasicCall) selectItem; | ||
return call.getOperands()[1] instanceof SqlNodeList; | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
// End SqlUtil.java |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,7 @@ public interface SqlConformance { | |
@Deprecated // to be removed before 2.0 | ||
SqlConformanceEnum PRAGMATIC_2003 = SqlConformanceEnum.PRAGMATIC_2003; | ||
|
||
SqlConformanceEnum HIVE = SqlConformanceEnum.HIVE; | ||
/** | ||
* Whether this dialect supports features from a wide variety of | ||
* dialects. This is enabled for the Babel parser, disabled otherwise. | ||
|
@@ -408,6 +409,13 @@ public interface SqlConformance { | |
* false otherwise. | ||
*/ | ||
boolean allowPluralTimeUnits(); | ||
|
||
/** | ||
* Whether SELECT can contain a table function. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about SELECT -> select list ? |
||
* <p>For example, consider the query | ||
* <blockquote><pre> SELECT SPLIT(col) AS (F0, F1) FROM A </pre> </blockquote> | ||
*/ | ||
boolean allowSelectTableFunction(); | ||
} | ||
|
||
// End SqlConformance.java |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,7 +69,11 @@ public enum SqlConformanceEnum implements SqlConformance { | |
|
||
/** Conformance value that instructs Calcite to use SQL semantics | ||
* consistent with Microsoft SQL Server version 2008. */ | ||
SQL_SERVER_2008; | ||
SQL_SERVER_2008, | ||
|
||
/** Conformance value that instructs Calcite to use SQL semantics | ||
* consistent with Hive version. */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the word of |
||
HIVE; | ||
|
||
public boolean isLiberal() { | ||
switch (this) { | ||
|
@@ -314,6 +318,14 @@ public boolean allowExtendedTrim() { | |
} | ||
} | ||
|
||
public boolean allowSelectTableFunction() { | ||
switch (this) { | ||
case HIVE: | ||
return true; | ||
default: | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
// End SqlConformanceEnum.java |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -294,6 +294,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints { | |
// Flag saying if we enable the implicit type coercion. | ||
private boolean enableTypeCoercion; | ||
|
||
// Mapping the table function and the select node. | ||
private final Map<SqlSelect, SqlBasicCall> selectTableFunctions = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it -> its? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for reminding. |
||
new IdentityHashMap<>(); | ||
//~ Constructors ----------------------------------------------------------- | ||
|
||
/** | ||
|
@@ -1067,6 +1070,10 @@ public SqlValidatorScope getOverScope(SqlNode node) { | |
return scopes.get(node); | ||
} | ||
|
||
public SqlBasicCall getTableFunctionInSelect(SqlSelect select) { | ||
return selectTableFunctions.get(select); | ||
} | ||
|
||
private SqlValidatorNamespace getNamespace(SqlNode node, | ||
SqlValidatorScope scope) { | ||
if (node instanceof SqlIdentifier && scope instanceof DelegatingScope) { | ||
|
@@ -4137,6 +4144,14 @@ protected RelDataType validateSelectList( | |
expandedSelectItems, | ||
aliases, | ||
fieldList); | ||
} else if (SqlUtil.isTableFunctionInSelect(selectItem)) { | ||
handleTableFunctionInSelect( | ||
select, | ||
(SqlBasicCall) selectItem, | ||
expandedSelectItems, | ||
aliases, | ||
fieldList | ||
); | ||
} else { | ||
expandSelectItem( | ||
selectItem, | ||
|
@@ -4247,6 +4262,114 @@ private void handleScalarSubQuery( | |
fieldList.add(Pair.of(alias, nodeType)); | ||
} | ||
|
||
/** | ||
* Process table function found in select list.Checks that is | ||
* actually a table function and validate the table function | ||
* count in Select list. | ||
* | ||
* @param parentSelect Base SqlSelect | ||
* @param selectItem Child select items from select list | ||
* @param expandedSelectItems Select items after processing | ||
* @param aliases Built from user or system values | ||
* @param fields Built up entries for each select list entry | ||
*/ | ||
private void handleTableFunctionInSelect( | ||
SqlSelect parentSelect, | ||
SqlBasicCall selectItem, | ||
List<SqlNode> expandedSelectItems, | ||
Set<String> aliases, | ||
List<Map.Entry<String, RelDataType>> fields) { | ||
SqlBasicCall functionCall = (SqlBasicCall) selectItem.getOperands()[0]; | ||
SqlFunction function = (SqlFunction) functionCall.getOperator(); | ||
// Check whether there are more than one table function in select list. | ||
for (SqlNode item : parentSelect.getSelectList()) { | ||
if (SqlUtil.isTableFunctionInSelect(item) | ||
&& item != selectItem) { | ||
throw newValidationError(parentSelect.getSelectList(), | ||
RESOURCE.onlyOneTableFunctionAllowedInSelect()); | ||
} | ||
} | ||
|
||
// Change the function category to USER_DEFINED_TABLE_FUNCTION. | ||
// It is because that in sql-select list, the SqlFunctionCategory is USER_DEFINED_FUNCTION | ||
// for a SqlUnresolvedFunction,so we should do this change. | ||
if (function instanceof SqlUnresolvedFunction) { | ||
if (!function.getFunctionType().isTableFunction()) { | ||
SqlFunction newFunction = | ||
new SqlUnresolvedFunction(function.getNameAsId(), | ||
function.getReturnTypeInference(), | ||
function.getOperandTypeInference(), | ||
function.getOperandTypeChecker(), | ||
function.getParamTypes(), | ||
SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION); | ||
functionCall.setOperator(newFunction); | ||
function = newFunction; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain a bit more about this transformation? I am a little confused. |
||
} | ||
} | ||
// Check functionCall whether is a table function | ||
List<SqlOperator> overloads = new ArrayList<>(); | ||
opTab.lookupOperatorOverloads(function.getNameAsId(), | ||
function.getFunctionType(), | ||
function.getSyntax(), overloads, catalogReader.nameMatcher()); | ||
if (overloads.size() == 0) { | ||
throw newValidationError(functionCall, | ||
RESOURCE.exceptTableFunction(function.getName())); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, this happened in the case "select func() as (t0,t1)", the |
||
// Check the parent select whether is a aggregate statement | ||
if (isAggregate(parentSelect)) { | ||
throw newValidationError(functionCall, | ||
RESOURCE.notAllowTableFunctionInAggregate()); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should do this check at the beginning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about putting the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well this check only happened after the check of table function exists in the select statement.So It cannot be placed somewhere before here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it! Thanks. |
||
SqlNodeList aliasNodes | ||
= (SqlNodeList) selectItem.getOperands()[1]; | ||
List<String> aliasList = new ArrayList<>(); | ||
for (SqlNode aliasNode : aliasNodes) { | ||
aliasList.add(deriveAlias(aliasNode, aliasList.size())); | ||
} | ||
aliases.addAll(aliasList); | ||
|
||
String tableAlias = functionCall.getOperator().getName(); | ||
// Expand the table function alias | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we just use the function name as the alias prefix, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for your suggestion. done! |
||
for (String alias : aliasList) { | ||
SqlIdentifier id = new SqlIdentifier(Lists.newArrayList | ||
(tableAlias, alias), SqlParserPos.ZERO); | ||
expandedSelectItems.add(id); | ||
} | ||
|
||
// Register namespace for table function | ||
SqlValidatorScope fromScope = getFromScope(parentSelect); | ||
ProcedureNamespace tableNs = new ProcedureNamespace(this, | ||
fromScope, functionCall, selectItem); | ||
tableNs.validateImpl(unknownType); | ||
registerNamespace(null, null, | ||
tableNs, false); | ||
AliasNamespace aliasNs = new AliasNamespace(this, | ||
selectItem, parentSelect); | ||
aliasNs.validateImpl(unknownType); | ||
registerNamespace(getSelectScope(parentSelect), | ||
tableAlias, aliasNs, false); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the format of these pieces of code is not expected. |
||
// Create a table scope for table function | ||
TableScope tableScope = new TableScope(fromScope, parentSelect); | ||
if (fromScope instanceof ListScope) { | ||
for (ScopeChild child : ((ListScope) fromScope).children) { | ||
tableScope.addChild(child.namespace, child.name, child.nullable); | ||
} | ||
} | ||
scopes.put(functionCall, tableScope); | ||
// Associate the select with the table function | ||
selectTableFunctions.put(parentSelect, functionCall); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm skeptical about this member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||
RelDataType type = aliasNs.getRowType(); | ||
setValidatedNodeType(selectItem, type); | ||
for (int i = 0; i < aliasList.size(); i++) { | ||
fields.add( | ||
Pair.of( | ||
aliasList.get(i), type.getFieldList() | ||
.get(i).getType())); | ||
} | ||
} | ||
|
||
/** | ||
* Derives a row-type for INSERT and UPDATE operations. | ||
* | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename
ids
tocolumnAliases
andid
tocolumnAlias
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for you suggestions.