LateralViewRef.java
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.doris.analysis;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.Function.NullableMode;
import org.apache.doris.catalog.InlineView;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.UserException;
import org.apache.doris.qe.GlobalVariable;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import java.util.List;
/**
* lateralView: LATERAL VIEW udtf(expression) tableAlias AS columnAlias (',' columnAlias)
* fromClause: FROM baseTable (lateralView)
*/
public class LateralViewRef extends TableRef {
private Expr expr;
private String viewName;
private String columnName;
private TableRef relatedTableRef;
// after analyzed
private FunctionCallExpr fnExpr;
private List<SlotRef> originSlotRefList = Lists.newArrayList();
private InlineView view;
private SlotRef explodeSlotRef;
public LateralViewRef(Expr expr, String viewName, String columnName) {
super(null, viewName);
this.expr = expr;
this.viewName = viewName;
this.columnName = columnName;
}
public void setRelatedTable(TableRef relatedTableRef) {
this.relatedTableRef = relatedTableRef;
}
public FunctionCallExpr getFnExpr() {
return fnExpr;
}
@Override
public void analyze(Analyzer analyzer) throws UserException {
if (isAnalyzed) {
return;
}
Preconditions.checkNotNull(relatedTableRef);
// analyze function and slot
if (!(expr instanceof FunctionCallExpr)) {
throw new AnalysisException("Only support function call expr in lateral view");
}
analyzeFunctionExpr(analyzer);
// analyze lateral view
desc = analyzer.registerTableRef(this);
explodeSlotRef = new SlotRef(new TableName(null, null, viewName), columnName);
explodeSlotRef.analyze(analyzer);
explodeSlotRef.getDesc().setIsNullable(
explodeSlotRef.getDesc().getIsNullable() || relatedTableRef.getDesc().getSlots()
.stream().anyMatch(slotDescriptor -> slotDescriptor.getIsNullable()));
isAnalyzed = true; // true now that we have assigned desc
}
@Override
public TableRef clone() {
return new LateralViewRef(this.expr.clone(), this.viewName, this.columnName);
}
private void analyzeFunctionExpr(Analyzer analyzer) throws AnalysisException {
fnExpr = (FunctionCallExpr) expr;
fnExpr.setTableFnCall(true);
checkAndSupplyDefaultTableName(fnExpr);
fnExpr.analyze(analyzer);
for (Expr expr : fnExpr.getChildren()) {
checkScalarFunction(expr);
}
}
@Override
public TupleDescriptor createTupleDescriptor(Analyzer analyzer) throws AnalysisException {
// Create a fake catalog table for the lateral view
List<Column> columnList = Lists.newArrayList();
columnList.add(new Column(columnName, fnExpr.getFn().getReturnType(), false, null,
fnExpr.getFn().getNullableMode() == NullableMode.ALWAYS_NULLABLE, null, ""));
view = new InlineView(viewName, columnList);
// Create the non-materialized tuple and set the fake table in it.
TupleDescriptor result = analyzer.getDescTbl().createTupleDescriptor();
result.setTable(view);
return result;
}
public void materializeRequiredSlots(ExprSubstitutionMap baseTblSmap, Analyzer analyzer) throws AnalysisException {
Expr substituteFnExpr = fnExpr;
if (relatedTableRef instanceof InlineViewRef) {
substituteFnExpr = fnExpr.trySubstitute(baseTblSmap, analyzer, false);
}
substituteFnExpr.collect(SlotRef.class, originSlotRefList);
for (SlotRef originSlotRef : originSlotRefList) {
originSlotRef.getDesc().setIsMaterialized(true);
}
explodeSlotRef.getDesc().setIsMaterialized(true);
}
// The default table name must be origin table name
// If there is table name in slot ref which is different from origin, it will thrown exception.
private void checkAndSupplyDefaultTableName(FunctionCallExpr expr) throws AnalysisException {
List<SlotRef> slotRefList = Lists.newArrayList();
expr.collect(SlotRef.class, slotRefList);
TableName relatedTableName = relatedTableRef.getAliasAsName();
for (SlotRef slotRef : slotRefList) {
TableName tableName = slotRef.getOriginTableName();
if (tableName == null) {
// t1 lateral view explode_split(k1, ",")
slotRef.setTblName(relatedTableName.cloneWithoutAnalyze());
return;
}
if (tableName.getDb() != null && !tableName.getDb().equalsIgnoreCase(relatedTableName.getDb())) {
// db2.t1 lateral view explode_split(db1.t1.k1, ",")
throw new AnalysisException("The column " + slotRef.toSql()
+ " in lateral view must come from the origin table "
+ relatedTableRef.toSql());
}
if (tableName.getTbl() != null) {
switch (GlobalVariable.lowerCaseTableNames) {
case 0:
if (tableName.getTbl().equals(relatedTableName.getTbl())) {
// t1 lateral view explode_split(t1.k1, ",")
tableName.setDb(relatedTableName.getDb());
return;
}
break;
case 1:
case 2:
if (tableName.getTbl().equalsIgnoreCase(relatedTableName.getTbl())) {
tableName.setTbl(relatedTableName.getTbl());
tableName.setDb(relatedTableName.getDb());
return;
}
break;
default:
throw new AnalysisException("Not support specify table name in table function "
+ "when config.lower_case_table_names is not 0, 1 or 2");
}
// t1 lateral view explode_split(t2.k1, ",")
throw new AnalysisException("The column " + slotRef.toSql()
+ " in lateral view must come from the origin table "
+ relatedTableName.toSql());
}
}
}
// 1. it must be a scalar function
private void checkScalarFunction(Expr child0) throws AnalysisException {
List<GroupingFunctionCallExpr> groupingFunctionCallExprList = Lists.newArrayList();
child0.collect(GroupingFunctionCallExpr.class, groupingFunctionCallExprList);
if (!groupingFunctionCallExprList.isEmpty()) {
throw new AnalysisException("Grouping function are not allowed in lateral view.");
}
if (child0.containsAggregate()) {
throw new AnalysisException("Agg function are not allowed in lateral view.");
}
List<AnalyticExpr> analyticExprList = Lists.newArrayList();
child0.collect(AnalyticExpr.class, analyticExprList);
if (!analyticExprList.isEmpty()) {
throw new AnalysisException("Analytic expr are not allowed in lateral view.");
}
List<Subquery> subqueryList = Lists.newArrayList();
child0.collect(Subquery.class, subqueryList);
if (!subqueryList.isEmpty()) {
throw new AnalysisException("Subquery is not allowed in lateral view");
}
}
@Override
public String toSql() {
return "lateral view " + expr.toSql() + " `" + viewName + "` as `" + columnName + "`";
}
@Override
public String toString() {
return toSql();
}
@Override
public void reset() {
isAnalyzed = false;
expr.reset();
fnExpr = null;
originSlotRefList = Lists.newArrayList();
view = null;
explodeSlotRef = null;
// There is no need to call the reset function of @relatedTableRef here.
// The main reason is that @lateralViewRef itself is an attribute of @relatedTableRef
// The reset of @lateralViewRef happens in the reset() of @relatedTableRef.
}
}