/* * 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.drill.exec.planner.sql.parser; import java.util.List; import java.util.Map; import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlIdentifier; import org.apache.calcite.sql.SqlJoin; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.SqlOrderBy; import org.apache.calcite.sql.SqlSelect; import org.apache.calcite.sql.SqlSetOption; import org.apache.calcite.sql.util.SqlShuttle; import org.apache.calcite.sql.util.SqlVisitor; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; /** * Implementation of {@link SqlVisitor} that converts bracketed compound {@link SqlIdentifier} to bracket-less compound * {@link SqlIdentifier} (also known as {@link DrillCompoundIdentifier}) to provide ease of use while querying complex * types. * <p/> * For example, this visitor converts {@code a['b'][4]['c']} to {@code a.b[4].c} */ public class CompoundIdentifierConverter extends SqlShuttle { // private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CompoundIdentifierConverter.class); private boolean enableComplex = true; @Override public SqlNode visit(SqlIdentifier id) { if(id instanceof DrillCompoundIdentifier){ if(enableComplex){ return ((DrillCompoundIdentifier) id).getAsSqlNode(); }else{ return ((DrillCompoundIdentifier) id).getAsCompoundIdentifier(); } }else{ return id; } } @Override public SqlNode visit(final SqlCall call) { // Handler creates a new copy of 'call' only if one or more operands // change. ArgHandler<SqlNode> argHandler = new ComplexExpressionAware(call); call.getOperator().acceptCall(this, call, false, argHandler); return argHandler.result(); } private class ComplexExpressionAware implements ArgHandler<SqlNode> { boolean update; SqlNode[] clonedOperands; RewriteType[] rewriteTypes; private final SqlCall call; public ComplexExpressionAware(SqlCall call) { this.call = call; this.update = false; final List<SqlNode> operands = call.getOperandList(); this.clonedOperands = operands.toArray(new SqlNode[operands.size()]); rewriteTypes = REWRITE_RULES.get(call.getClass()); } @Override public SqlNode result() { if (update) { return call.getOperator().createCall( call.getFunctionQuantifier(), call.getParserPosition(), clonedOperands); } else { return call; } } @Override public SqlNode visitChild( SqlVisitor<SqlNode> visitor, SqlNode expr, int i, SqlNode operand) { if (operand == null) { return null; } boolean localEnableComplex = enableComplex; if(rewriteTypes != null){ switch(rewriteTypes[i]){ case DISABLE: enableComplex = false; break; case ENABLE: enableComplex = true; } } SqlNode newOperand = operand.accept(CompoundIdentifierConverter.this); enableComplex = localEnableComplex; if (newOperand != operand) { update = true; } clonedOperands[i] = newOperand; return newOperand; } } static final Map<Class<? extends SqlCall>, RewriteType[]> REWRITE_RULES; enum RewriteType { UNCHANGED, DISABLE, ENABLE; } static { final RewriteType E =RewriteType.ENABLE; final RewriteType D =RewriteType.DISABLE; final RewriteType U =RewriteType.UNCHANGED; /* This map stores the rules that instruct each SqlCall class which data field needs to be rewritten if that data field is a CompoundIdentifier Key : Each rule corresponds to a SqlCall class; value: It is an array of RewriteType, each being associated with a data field in that class. For example, there are four data fields (query, orderList, offset, fetch) in org.eigenbase.sql.SqlOrderBy. Since only orderList needs to be written, RewriteType[] should be R(D, E, D, D). */ Map<Class<? extends SqlCall>, RewriteType[]> rules = Maps.newHashMap(); //SqlNodeList keywordList, //SqlNodeList selectList, //SqlNode fromClause, //SqlNode whereClause, //SqlNodeList groupBy, //SqlNode having, //SqlNodeList windowDecls, //SqlNodeList orderBy, //SqlNode offset, //SqlNode fetch, rules.put(SqlSelect.class, R(D, E, D, E, E, E, E, E, D, D)); rules.put(SqlCreateTable.class, R(D, D, D, E, D)); rules.put(SqlCreateView.class, R(D, E, E, D)); rules.put(SqlDescribeTable.class, R(D, D, E)); rules.put(SqlDropView.class, R(D, D)); rules.put(SqlShowFiles.class, R(D)); rules.put(SqlShowSchemas.class, R(D, D)); rules.put(SqlUseSchema.class, R(D)); rules.put(SqlJoin.class, R(D, D, D, D, D, E)); rules.put(SqlOrderBy.class, R(D, E, D, D)); rules.put(SqlDropTable.class, R(D, D)); rules.put(SqlRefreshMetadata.class, R(D)); rules.put(SqlSetOption.class, R(D, D, D)); rules.put(SqlDescribeSchema.class, R(D)); rules.put(SqlCreateFunction.class, R(D)); rules.put(SqlDropFunction.class, R(D)); REWRITE_RULES = ImmutableMap.copyOf(rules); } // Each type in the input arguments refers to // each data field in the class private static RewriteType[] R(RewriteType... types){ return types; } }