Java tutorial
/* * 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.kylin.rest.util; import java.sql.SQLException; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.tuple.Triple; import org.apache.kylin.common.KylinConfig; import org.apache.kylin.common.util.ClassUtil; import org.apache.kylin.metadata.MetadataManager; import org.apache.kylin.metadata.model.ComputedColumnDesc; import org.apache.kylin.metadata.model.DataModelDesc; import org.apache.kylin.metadata.model.tool.CalciteParser; import org.apache.kylin.metadata.querymeta.SelectedColumnMeta; import org.apache.kylin.query.routing.NoRealizationFoundException; import org.apache.kylin.source.adhocquery.IPushDownConverter; import org.apache.kylin.source.adhocquery.IPushDownRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; public class PushDownUtil { private static final Logger logger = LoggerFactory.getLogger(PushDownUtil.class); public static boolean doPushDownQuery(String project, String sql, List<List<String>> results, List<SelectedColumnMeta> columnMetas, SQLException sqlException) throws Exception { boolean isExpectedCause = (ExceptionUtils.getRootCause(sqlException).getClass() .equals(NoRealizationFoundException.class)); KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv(); if (isExpectedCause && kylinConfig.isPushDownEnabled()) { logger.info("Query failed to utilize pre-calculation, routing to other engines", sqlException); IPushDownRunner runner = (IPushDownRunner) ClassUtil .newInstance(kylinConfig.getPushDownRunnerClassName()); IPushDownConverter converter = (IPushDownConverter) ClassUtil .newInstance(kylinConfig.getPushDownConverterClassName()); runner.init(kylinConfig); logger.debug("Query pushdown runner {}", runner); String expandCC = restoreComputedColumnToExpr(sql, project); if (!StringUtils.equals(expandCC, sql)) { logger.info("computed column in sql is expanded to: " + expandCC); } String adhocSql = converter.convert(expandCC); if (!adhocSql.equals(expandCC)) { logger.info("the query is converted to {} according to kylin.query.pushdown.converter-class-name", adhocSql); } runner.executeQuery(adhocSql, results, columnMetas); return true; } else { throw sqlException; } } private final static Pattern identifierInSqlPattern = Pattern.compile( //find pattern like "table"."column" or "column" "((?<![\\p{L}_0-9\\.\\\"])(\\\"[\\p{L}_0-9]+\\\"\\.)?(\\\"[\\p{L}_0-9]+\\\")(?![\\p{L}_0-9\\.\\\"]))" + "|" //find pattern like table.column or column + "((?<![\\p{L}_0-9\\.\\\"])([\\p{L}_0-9]+\\.)?([\\p{L}_0-9]+)(?![\\p{L}_0-9\\.\\\"]))"); private final static Pattern identifierInExprPattern = Pattern.compile( // a.b.c "((?<![\\p{L}_0-9\\.\\\"])([\\p{L}_0-9]+\\.)([\\p{L}_0-9]+\\.)([\\p{L}_0-9]+)(?![\\p{L}_0-9\\.\\\"]))"); private final static Pattern endWithAsPattern = Pattern.compile("\\s+as\\s+$", Pattern.CASE_INSENSITIVE); public static String restoreComputedColumnToExpr(String beforeSql, String project) { final MetadataManager metadataManager = MetadataManager.getInstance(KylinConfig.getInstanceFromEnv()); List<DataModelDesc> dataModelDescs = metadataManager.getModels(project); String afterSql = beforeSql; for (DataModelDesc dataModelDesc : dataModelDescs) { for (ComputedColumnDesc computedColumnDesc : dataModelDesc.getComputedColumnDescs()) { afterSql = restoreComputedColumnToExpr(afterSql, computedColumnDesc); } } return afterSql; } static String restoreComputedColumnToExpr(String sql, ComputedColumnDesc computedColumnDesc) { String ccName = computedColumnDesc.getColumnName(); List<Triple<Integer, Integer, String>> replacements = Lists.newArrayList(); Matcher matcher = identifierInSqlPattern.matcher(sql); while (matcher.find()) { if (matcher.group(1) != null) { //with quote case: "TABLE"."COLUMN" String quotedColumnName = matcher.group(3); Preconditions.checkNotNull(quotedColumnName); String columnName = StringUtils.strip(quotedColumnName, "\""); if (!columnName.equalsIgnoreCase(ccName)) { continue; } if (matcher.group(2) != null) { // table name exist String quotedTableAlias = StringUtils.strip(matcher.group(2), "."); String tableAlias = StringUtils.strip(quotedTableAlias, "\""); replacements.add(Triple.of(matcher.start(1), matcher.end(1), replaceIdentifierInExpr(computedColumnDesc.getExpression(), tableAlias, true))); } else { //only column if (endWithAsPattern.matcher(sql.substring(0, matcher.start(1))).find()) { //select DEAL_AMOUNT as "deal_amount" case continue; } replacements.add(Triple.of(matcher.start(1), matcher.end(1), replaceIdentifierInExpr(computedColumnDesc.getExpression(), null, true))); } } else if (matcher.group(4) != null) { //without quote case: table.column or simply column String columnName = matcher.group(6); Preconditions.checkNotNull(columnName); if (!columnName.equalsIgnoreCase(ccName)) { continue; } if (matcher.group(5) != null) { //table name exist String tableAlias = StringUtils.strip(matcher.group(5), "."); replacements.add(Triple.of(matcher.start(4), matcher.end(4), replaceIdentifierInExpr(computedColumnDesc.getExpression(), tableAlias, false))); } else { //only column if (endWithAsPattern.matcher(sql.substring(0, matcher.start(4))).find()) { //select DEAL_AMOUNT as deal_amount case continue; } replacements.add(Triple.of(matcher.start(4), matcher.end(4), replaceIdentifierInExpr(computedColumnDesc.getExpression(), null, false))); } } } Collections.reverse(replacements); for (Triple<Integer, Integer, String> triple : replacements) { sql = sql.substring(0, triple.getLeft()) + "(" + triple.getRight() + ")" + sql.substring(triple.getMiddle()); } return sql; } static String replaceIdentifierInExpr(String expr, String tableAlias, boolean quoted) { if (tableAlias == null) { return expr; } return CalciteParser.insertAliasInExpr(expr, tableAlias); } }