blob: adbba56dc817d3712586b81e23e2de8f25a92ad3 [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.carbondata.core.scan.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.carbondata.common.logging.LogServiceFactory;
import org.apache.carbondata.core.index.IndexFilter;
import org.apache.carbondata.core.metadata.datatype.DataTypes;
import org.apache.carbondata.core.metadata.schema.table.CarbonTable;
import org.apache.carbondata.core.metadata.schema.table.column.CarbonDimension;
import org.apache.carbondata.core.metadata.schema.table.column.CarbonMeasure;
import org.apache.carbondata.core.util.DataTypeConverter;
import org.apache.log4j.Logger;
public class QueryModelBuilder {
private CarbonTable table;
private QueryProjection projection;
private IndexFilter indexFilter;
private DataTypeConverter dataTypeConverter;
private boolean forcedDetailRawQuery;
private boolean readPageByPage;
private boolean convertToRangeFilter = true;
/**
* log information
*/
private static final Logger LOGGER =
LogServiceFactory.getLogService(QueryModelBuilder.class.getName());
public QueryModelBuilder(CarbonTable table) {
this.table = table;
}
public QueryModelBuilder projectColumns(String[] projectionColumns) {
Objects.requireNonNull(projectionColumns);
String factTableName = table.getTableName();
QueryProjection projection = new QueryProjection();
int i = 0;
for (String projectionColumnName : projectionColumns) {
CarbonDimension dimension = table.getDimensionByName(projectionColumnName);
if (dimension != null) {
CarbonDimension complexParentDimension = dimension.getComplexParentDimension();
if (null != complexParentDimension && dimension.getDataType() == DataTypes.DATE) {
if (!isAlreadyExists(complexParentDimension, projection.getDimensions())) {
projection.addDimension(complexParentDimension, i);
i++;
}
} else {
projection.addDimension(dimension, i);
i++;
}
} else {
CarbonMeasure measure = table.getMeasureByName(projectionColumnName);
if (measure == null) {
throw new RuntimeException(
projectionColumnName + " column not found in the table " + factTableName);
}
projection.addMeasure(measure, i);
i++;
}
}
projection = optimizeProjectionForComplexColumns(projection, projectionColumns, factTableName);
List<String> projectionDimensionAndMeasures = new ArrayList<>();
this.projection = projection;
for (ProjectionDimension projectionDimension : projection.getDimensions()) {
projectionDimensionAndMeasures.add(projectionDimension.getColumnName());
}
for (ProjectionMeasure projectionMeasure : projection.getMeasures()) {
projectionDimensionAndMeasures.add(projectionMeasure.getColumnName());
}
LOGGER.info("Projection Columns: " + projectionDimensionAndMeasures);
return this;
}
/**
* For complex dimensions, check if the dimension already exists in the projection list or not
*
* @param dimension
* @param projectionDimensions
* @return
*/
private boolean isAlreadyExists(CarbonDimension dimension,
List<ProjectionDimension> projectionDimensions) {
boolean exists = false;
for (ProjectionDimension projectionDimension : projectionDimensions) {
if (dimension.getColName().equals(projectionDimension.getColumnName())) {
exists = true;
break;
}
}
return exists;
}
private QueryProjection optimizeProjectionForComplexColumns(QueryProjection projection,
String[] projectionColumns, String factTableName) {
// Get the List of Complex Column Projection.
// The optimization techniques which can be applied are
// A. Merging in Driver Side
// B. Merging in the result Collector side.
// Merging is driver side cases are
// Driver merging will eliminate one of the CarbonDimension.
// Executor merging will merge the column output in Result Collector.
// In this routine we are going to do driver merging and leave executor merging.
Map<Integer, List<Integer>> complexColumnMap = new HashMap<>();
List<ProjectionDimension> carbonDimensions = projection.getDimensions();
// Traverse and find out if the top most parent of projection column is already there
List<CarbonDimension> projectionDimensionToBeMerged = new ArrayList<>();
for (ProjectionDimension projectionDimension : carbonDimensions) {
CarbonDimension complexParentDimension =
projectionDimension.getDimension().getComplexParentDimension();
if (null != complexParentDimension && isAlreadyExists(complexParentDimension,
carbonDimensions)) {
projectionDimensionToBeMerged.add(projectionDimension.getDimension());
}
}
if (projectionDimensionToBeMerged.size() != 0) {
projection =
removeMergedDimensions(projectionDimensionToBeMerged, projectionColumns, factTableName);
carbonDimensions = projection.getDimensions();
}
for (ProjectionDimension cols : carbonDimensions) {
// get all the Projections with Parent Ordinal Set.
if (null != cols.getDimension().getComplexParentDimension()) {
if (complexColumnMap.get(cols.getDimension().getComplexParentDimension().getOrdinal())
!= null) {
List<Integer> childColumns =
complexColumnMap.get(cols.getDimension().getComplexParentDimension().getOrdinal());
childColumns.add(cols.getDimension().getOrdinal());
complexColumnMap
.put(cols.getDimension().getComplexParentDimension().getOrdinal(), childColumns);
} else {
List<Integer> childColumns = new ArrayList<>();
childColumns.add(cols.getDimension().getOrdinal());
complexColumnMap
.put(cols.getDimension().getComplexParentDimension().getOrdinal(), childColumns);
}
}
}
// Traverse the Map to Find any columns are parent.
for (Map.Entry<Integer, List<Integer>> entry : complexColumnMap.entrySet()) {
List<Integer> childOrdinals = entry.getValue();
if (childOrdinals.size() > 1) {
// In case of more that one child, have to check if the child columns are in the same path
// and have a common parent.
Collections.sort(childOrdinals);
List<CarbonDimension> mergedDimensions = mergeChildColumns(childOrdinals);
if (mergedDimensions.size() > 0) {
projection = removeMergedDimensions(mergedDimensions, projectionColumns, factTableName);
}
}
}
return projection;
}
/**
* Remove the dimensions from the projection list which are merged
*
* @param mergedDimensions
* @param projectionColumns
* @param factTableName
* @return
*/
private QueryProjection removeMergedDimensions(List<CarbonDimension> mergedDimensions,
String[] projectionColumns, String factTableName) {
QueryProjection queryProjection = new QueryProjection();
int i = 0;
for (String projectionColumnName : projectionColumns) {
CarbonDimension dimension = table.getDimensionByName(projectionColumnName);
if (dimension != null) {
if (!mergedDimensions.contains(dimension)) {
if (!isAlreadyExists(dimension, queryProjection.getDimensions())) {
queryProjection.addDimension(dimension, i);
i++;
}
}
} else {
CarbonMeasure measure = table.getMeasureByName(projectionColumnName);
if (measure == null) {
throw new RuntimeException(
projectionColumnName + " column not found in the table " + factTableName);
}
queryProjection.addMeasure(measure, i);
i++;
}
}
return queryProjection;
}
private List<CarbonDimension> mergeChildColumns(List<Integer> childOrdinals) {
// Check If children if they are in the path of not.
List<CarbonDimension> mergedChild = new ArrayList<>();
List<CarbonDimension> dimList = table.getVisibleDimensions();
for (int i = 0; i < childOrdinals.size(); i++) {
for (int j = i; j < childOrdinals.size(); j++) {
CarbonDimension parentDimension = getDimensionBasedOnOrdinal(dimList, childOrdinals.get(i));
CarbonDimension childDimension = getDimensionBasedOnOrdinal(dimList, childOrdinals.get(j));
if (!mergedChild.contains(childOrdinals.get(j)) && checkChildrenInSamePath(parentDimension,
childDimension)) {
mergedChild.add(childDimension);
}
}
}
return mergedChild;
}
private boolean checkChildrenInSamePath(CarbonDimension parentDimension,
CarbonDimension childDimension) {
if (parentDimension.getColName().equals(childDimension.getColName())) {
return false;
} else if (checkForChildColumns(parentDimension, childDimension)) {
return true;
} else {
return false;
}
}
private boolean checkForChildColumns(CarbonDimension parentDimension,
CarbonDimension childDimension) {
boolean output = false;
if (parentDimension.getOrdinal() == childDimension.getOrdinal()) {
output = true;
} else if (parentDimension.getNumberOfChild() > 0) {
for (int i = 0; i < parentDimension.getNumberOfChild() && !output; i++) {
output =
checkForChildColumns(parentDimension.getListOfChildDimensions().get(i), childDimension);
}
} else {
output = false;
}
return output;
}
private CarbonDimension getDimensionBasedOnOrdinal(List<CarbonDimension> dimList,
Integer ordinal) {
for (CarbonDimension dims : dimList) {
if (dims.getOrdinal() == ordinal) {
return dims;
} else if (dims.getNumberOfChild() > 0) {
CarbonDimension dimensionBasedOnOrdinal =
getDimensionBasedOnOrdinal(dims.getListOfChildDimensions(), ordinal);
if (null != dimensionBasedOnOrdinal) {
return dimensionBasedOnOrdinal;
}
}
}
return null;
}
public QueryModelBuilder projectAllColumns() {
QueryProjection projection = new QueryProjection();
List<CarbonDimension> dimensions = table.getVisibleDimensions();
for (int i = 0; i < dimensions.size(); i++) {
projection.addDimension(dimensions.get(i), i);
}
List<CarbonMeasure> measures = table.getVisibleMeasures();
for (int i = 0; i < measures.size(); i++) {
projection.addMeasure(measures.get(i), i);
}
this.projection = projection;
return this;
}
public QueryModelBuilder filterExpression(IndexFilter filterExpression) {
this.indexFilter = filterExpression;
return this;
}
public QueryModelBuilder dataConverter(DataTypeConverter dataTypeConverter) {
this.dataTypeConverter = dataTypeConverter;
return this;
}
public QueryModelBuilder enableForcedDetailRawQuery() {
this.forcedDetailRawQuery = true;
return this;
}
public QueryModelBuilder convertToRangeFilter(boolean convertToRangeFilter) {
this.convertToRangeFilter = convertToRangeFilter;
return this;
}
public boolean isConvertToRangeFilter() {
return this.convertToRangeFilter;
}
public void enableReadPageByPage() {
this.readPageByPage = true;
}
public QueryModel build() {
QueryModel queryModel = QueryModel.newInstance(table);
queryModel.setConverter(dataTypeConverter);
queryModel.setForcedDetailRawQuery(forcedDetailRawQuery);
queryModel.setReadPageByPage(readPageByPage);
queryModel.setProjection(projection);
if (table.isTransactionalTable() && !table.hasColumnDrift()) {
// set the filter to the query model in order to filter blocklet before scan
boolean[] isFilterDimensions = new boolean[table.getDimensionOrdinalMax()];
boolean[] isFilterMeasures = new boolean[table.getAllMeasures().size()];
queryModel.setIsFilterDimensions(isFilterDimensions);
queryModel.setIsFilterMeasures(isFilterMeasures);
// In case of Dictionary Include Range Column we do not optimize the range expression
if (indexFilter != null) {
if (isConvertToRangeFilter()) {
indexFilter.processFilterExpression(isFilterDimensions, isFilterMeasures);
} else {
indexFilter.processFilterExpressionWithoutRange(isFilterDimensions, isFilterMeasures);
}
}
}
queryModel.setIndexFilter(indexFilter);
return queryModel;
}
}