blob: 4745eafe3baab739aca593caa1c68a8a385633d7 [file] [log] [blame]
/*
* 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.flink.sql.parser.node;
import org.apache.flink.sql.parser.ddl.SqlCreateTable;
import org.apache.flink.sql.parser.ddl.SqlCreateView;
import org.apache.flink.shaded.guava18.com.google.common.collect.ImmutableList;
import org.apache.calcite.sql.JoinType;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlInsert;
import org.apache.calcite.sql.SqlJoin;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlMatchRecognize;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlSelect;
import org.apache.calcite.sql.SqlSnapshot;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.util.Util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Converts a SQL parse tree. (consisting of
* {@link org.apache.calcite.sql.SqlNode} objects) into a relational node tree
* (consisting of {@link SqlTreeNode} objects)
*/
public class SqlToTreeConverter {
private final Map<String, SqlTreeNode> context = new HashMap<>();
private final List<SqlTreeNode> nodes = new ArrayList<>();
private final SqlValidator validator;
public SqlToTreeConverter(SqlValidator validator) {
this.validator = validator;
}
/**
* Converts a query's parse tree into a relation tree.
*
* @param query sql query parser tree
*/
public void convertSql(SqlNode query) {
SqlTreeNode node = convertQueryRecursive(query);
if (node != null) {
nodes.add(node);
}
}
public String getJSON() {
SqlTreeJSONGenerator generator = new SqlTreeJSONGenerator();
return generator.getJSON(nodes);
}
/**
* Recursively converts a query to a tree node.
*
* @param query Query
* @return Relational expression
*/
private SqlTreeNode convertQueryRecursive(SqlNode query) {
final SqlKind kind = query.getKind();
switch (kind) {
case SELECT:
return convertSelect((SqlSelect) query);
case INSERT:
return convertInsert((SqlInsert) query);
case CREATE_VIEW:
return convertView((SqlCreateView) query);
case CREATE_TABLE:
return registerTable((SqlCreateTable) query);
case UNION:
case INTERSECT:
case EXCEPT:
return convertSetOp((SqlCall) query);
case OTHER_DDL:
// ignore create function ...
return null;
default:
throw new AssertionError("not a query: " + query);
}
}
private SqlTreeNode convertView(SqlCreateView view) {
String viewName = view.getName();
SqlTreeNode input = convertQueryRecursive(view.getQuery());
context.put(viewName, input);
return input;
}
private SqlTreeNode convertInsert(SqlInsert insert) {
SqlTreeNode input = convertQueryRecursive(insert.getSource());
String tableName = insert.getTargetTable().toString();
return SqlTreeNodes.sink(insert.getParserPosition(), input, tableName);
}
private SqlTreeNode convertSelect(SqlSelect query) {
SqlTreeNode input = convertFrom(query.getFrom());
SqlTreeNode filter = convertWhere(input, query.getWhere());
if (filter != null) {
input = filter;
}
if (validator.isAggregate(query)) {
SqlNodeList groupList = query.getGroup();
input = SqlTreeNodes.group(groupList.getParserPosition(), input, groupList);
} else {
SqlNodeList selectList = query.getSelectList();
input = SqlTreeNodes.select(query.getParserPosition(), input, selectList);
}
return convertOrder(query, input);
}
private SqlTreeNode convertSetOp(SqlCall call) {
final SqlTreeNode left = convertQueryRecursive(call.operand(0));
final SqlTreeNode right = convertQueryRecursive(call.operand(1));
switch (call.getKind()) {
case UNION:
return SqlTreeNodes.union(call.getParserPosition(), ImmutableList.of(left, right));
case INTERSECT:
throw Util.unexpected(call.getKind());
case EXCEPT:
throw Util.unexpected(call.getKind());
default:
throw Util.unexpected(call.getKind());
}
}
private SqlTreeNode convertOrder(SqlSelect select, SqlTreeNode input) {
SqlNodeList orderBy = select.getOrderList();
if (orderBy == null || orderBy.getList().isEmpty()) {
return input;
}
return SqlTreeNodes.topn(orderBy.getParserPosition(), input, "");
}
private SqlTreeNode convertWhere(SqlTreeNode input, SqlNode where) {
if (where == null) {
return null;
}
return SqlTreeNodes.filter(where.getParserPosition(), input, where);
}
private SqlTreeNode convertFrom(SqlNode from) {
SqlCall call;
switch (from.getKind()) {
case IDENTIFIER:
String tableName = from.toString();
return context.get(tableName);
case AS:
return convertFrom(((SqlCall) from).operand(0));
case MATCH_RECOGNIZE:
return convertMatchRecognize((SqlCall) from);
case SNAPSHOT:
return convertTemporal((SqlSnapshot) from);
case JOIN:
return convertJoin((SqlJoin) from);
case SELECT:
case INTERSECT:
case EXCEPT:
case UNION:
return convertQueryRecursive(from);
case LATERAL:
call = (SqlCall) from;
return convertFrom((call.getOperandList().get(0)));
case COLLECTION_TABLE:
call = (SqlCall) from;
return SqlTreeNodes.udtf(from.getParserPosition(), call.getOperandList().get(0));
case VALUES:
throw new UnsupportedOperationException();
default:
throw new AssertionError("Not supported operator " + from);
}
}
private SqlTreeNode convertJoin(SqlJoin join) {
SqlNode left = join.getLeft();
SqlNode right = join.getRight();
SqlTreeNode leftNode = convertFrom(left);
SqlTreeNode rightNode = convertFrom(right);
if (rightNode instanceof SqlTreeNodes.SqlTableFunctionNode) {
// udtf
return SqlTreeNodes.correlate(join.getParserPosition(), leftNode, rightNode.explain());
} else {
JoinType joinType = join.getJoinType();
return SqlTreeNodes.join(join.getParserPosition(), leftNode, rightNode, joinType);
}
}
private SqlTreeNode convertTemporal(SqlSnapshot call) {
String tableName = call.getTableRef().toString();
return context.get(tableName);
}
private SqlTreeNode convertMatchRecognize(SqlCall call) {
SqlMatchRecognize matchRecognize = (SqlMatchRecognize) call;
SqlNode tableRef = matchRecognize.getTableRef();
SqlTreeNode input = convertFrom(tableRef);
SqlNode pattern = matchRecognize.getPattern();
return SqlTreeNodes.cep(call.getParserPosition(), input, pattern);
}
/**
* Register source and dim tables.
*/
public SqlTreeNode registerTable(SqlCreateTable node) {
SqlParserPos pos = node.getParserPosition();
String tableName = node.getTableName().toString();
SqlTreeNode table;
switch (node.getTableType()) {
case "SOURCE":
table = SqlTreeNodes.source(pos, tableName);
context.put(tableName, table);
return table;
case "SINK":
// ignore
return null;
default:
throw new IllegalArgumentException(
"Illegal table type of SqlCreateTable: " + node.getTableType());
}
}
}