KYLIN-2631 Seek to next model when no cube in current model satisfies query
Project: http://git-wip-us.apache.org/repos/asf/kylin/repo Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/ad67fd76 Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/ad67fd76 Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/ad67fd76 Branch: refs/heads/2.0.x-hbase0.98 Commit: ad67fd763237306ddba0d0946ab024fcdb7ab604 Parents: 0f68486 Author: Hongbin Ma <mahong...@apache.org> Authored: Fri May 19 21:12:26 2017 +0800 Committer: Hongbin Ma <mahong...@apache.org> Committed: Fri May 19 21:12:34 2017 +0800 ---------------------------------------------------------------------- .../apache/kylin/metadata/model/TblColRef.java | 43 ++-- .../kylin/metadata/realization/SQLDigest.java | 2 + .../gtrecord/GTCubeStorageQueryBase.java | 3 - .../kylin/query/relnode/OLAPTableScan.java | 13 +- .../relnode/OLAPToEnumerableConverter.java | 11 +- .../kylin/query/routing/ModelChooser.java | 219 ------------------ .../apache/kylin/query/routing/QueryRouter.java | 2 +- .../kylin/query/routing/RealizationChooser.java | 224 +++++++++++++++++++ 8 files changed, 271 insertions(+), 246 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kylin/blob/ad67fd76/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java ---------------------------------------------------------------------- diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java b/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java index c74f4e8..1410841 100644 --- a/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java +++ b/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java @@ -56,7 +56,7 @@ public class TblColRef implements Serializable { public static TblColRef newInnerColumn(String columnName, InnerDataTypeEnum dataType) { return newInnerColumn(columnName, dataType, null); } - + // used by projection rewrite, see OLAPProjectRel public static TblColRef newInnerColumn(String columnName, InnerDataTypeEnum dataType, String parserDescription) { ColumnDesc column = new ColumnDesc(); @@ -68,27 +68,30 @@ public class TblColRef implements Serializable { colRef.parserDescription = parserDescription; return colRef; } - + private static final DataModelDesc UNKNOWN_MODEL = new DataModelDesc(); static { UNKNOWN_MODEL.setName("UNKNOWN_MODEL"); } - + public static TableRef tableForUnknownModel(String tempTableAlias, TableDesc table) { return new TableRef(UNKNOWN_MODEL, tempTableAlias, table); } - + public static TblColRef columnForUnknownModel(TableRef table, ColumnDesc colDesc) { checkArgument(table.getModel() == UNKNOWN_MODEL); return new TblColRef(table, colDesc); } - + public static void fixUnknownModel(DataModelDesc model, String alias, TblColRef col) { checkArgument(col.table.getModel() == UNKNOWN_MODEL || col.table.getModel() == model); TableRef tableRef = model.findTable(alias); checkArgument(tableRef.getTableDesc() == col.column.getTable()); - col.table = tableRef; - col.identity = null; + col.fixTableRef(tableRef); + } + + public static void unfixUnknownModel(TblColRef col) { + col.unfixTableRef(); } // for test mainly @@ -101,10 +104,11 @@ public class TblColRef implements Serializable { desc.init(table); return new TblColRef(desc); } - + // ============================================================================ private TableRef table; + private TableRef backupTable;// only used in fixTableRef() private ColumnDesc column; private String identity; private String parserDescription; @@ -112,13 +116,24 @@ public class TblColRef implements Serializable { TblColRef(ColumnDesc column) { this.column = column; } - + TblColRef(TableRef table, ColumnDesc column) { checkArgument(table.getTableDesc() == column.getTable()); this.table = table; this.column = column; } - + + public void fixTableRef(TableRef tableRef) { + this.backupTable = this.table; + this.table = tableRef; + this.identity = null; + } + + public void unfixTableRef() { + this.table = backupTable; + this.identity = null; + } + public ColumnDesc getColumnDesc() { return column; } @@ -130,15 +145,15 @@ public class TblColRef implements Serializable { public TableRef getTableRef() { return table; } - + public boolean isQualified() { return table != null; } - + public String getTableAlias() { return table != null ? table.getAlias() : "UNKNOWN_ALIAS"; } - + public String getTable() { if (column.getTable() == null) { return null; @@ -209,7 +224,7 @@ public class TblColRef implements Serializable { public String toString() { if (isInnerColumn() && parserDescription != null) return parserDescription; - + String alias = table == null ? "UNKNOWN_MODEL" : table.getAlias(); String tableName = column.getTable() == null ? "NULL" : column.getTable().getName(); String tableIdentity = column.getTable() == null ? "NULL" : column.getTable().getIdentity(); http://git-wip-us.apache.org/repos/asf/kylin/blob/ad67fd76/core-metadata/src/main/java/org/apache/kylin/metadata/realization/SQLDigest.java ---------------------------------------------------------------------- diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/realization/SQLDigest.java b/core-metadata/src/main/java/org/apache/kylin/metadata/realization/SQLDigest.java index 03ff3ff..9ce65bb 100644 --- a/core-metadata/src/main/java/org/apache/kylin/metadata/realization/SQLDigest.java +++ b/core-metadata/src/main/java/org/apache/kylin/metadata/realization/SQLDigest.java @@ -96,6 +96,8 @@ public class SQLDigest { this.sortOrders = sortOrders; this.isRawQuery = isRawQuery(); this.limitPrecedesAggr = limitPrecedesAggr; + + this.includeSubqueryJoinParticipants(); } private boolean isRawQuery() { http://git-wip-us.apache.org/repos/asf/kylin/blob/ad67fd76/core-storage/src/main/java/org/apache/kylin/storage/gtrecord/GTCubeStorageQueryBase.java ---------------------------------------------------------------------- diff --git a/core-storage/src/main/java/org/apache/kylin/storage/gtrecord/GTCubeStorageQueryBase.java b/core-storage/src/main/java/org/apache/kylin/storage/gtrecord/GTCubeStorageQueryBase.java index d050b4b..6c2ff5d 100644 --- a/core-storage/src/main/java/org/apache/kylin/storage/gtrecord/GTCubeStorageQueryBase.java +++ b/core-storage/src/main/java/org/apache/kylin/storage/gtrecord/GTCubeStorageQueryBase.java @@ -99,9 +99,6 @@ public abstract class GTCubeStorageQueryBase implements IStorageQuery { protected GTCubeStorageQueryRequest getStorageQueryRequest(StorageContext context, SQLDigest sqlDigest, TupleInfo returnTupleInfo) { context.setStorageQuery(this); - //deal with participant columns in subquery join - sqlDigest.includeSubqueryJoinParticipants(); - //cope with queries with no aggregations RawQueryLastHacker.hackNoAggregations(sqlDigest, cubeDesc, returnTupleInfo); http://git-wip-us.apache.org/repos/asf/kylin/blob/ad67fd76/query/src/main/java/org/apache/kylin/query/relnode/OLAPTableScan.java ---------------------------------------------------------------------- diff --git a/query/src/main/java/org/apache/kylin/query/relnode/OLAPTableScan.java b/query/src/main/java/org/apache/kylin/query/relnode/OLAPTableScan.java index a424818..b583291 100644 --- a/query/src/main/java/org/apache/kylin/query/relnode/OLAPTableScan.java +++ b/query/src/main/java/org/apache/kylin/query/relnode/OLAPTableScan.java @@ -87,6 +87,7 @@ public class OLAPTableScan extends TableScan implements OLAPRel, EnumerableRel { private final String tableName; private final int[] fields; private String alias; + private String backupAlias; private ColumnRowType columnRowType; private OLAPContext context; @@ -212,7 +213,7 @@ public class OLAPTableScan extends TableScan implements OLAPRel, EnumerableRel { if (implementor.getContext() == null || !(implementor.getParentNode() instanceof OLAPJoinRel) || implementor.isNewOLAPContextRequired()) { implementor.allocateContext(); } - + columnRowType = buildColumnRowType(); context = implementor.getContext(); context.allTableScans.add(this); @@ -262,9 +263,19 @@ public class OLAPTableScan extends TableScan implements OLAPRel, EnumerableRel { for (TblColRef col : columnRowType.getAllColumns()) { TblColRef.fixUnknownModel(model, newAlias, col); } + + this.backupAlias = this.alias; this.alias = newAlias; } + public void unfixColumnRowTypeWithModel() { + this.alias = this.backupAlias; + + for (TblColRef col : columnRowType.getAllColumns()) { + TblColRef.unfixUnknownModel(col); + } + } + @Override public EnumerableRel implementEnumerable(List<EnumerableRel> inputs) { return this; http://git-wip-us.apache.org/repos/asf/kylin/blob/ad67fd76/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java ---------------------------------------------------------------------- diff --git a/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java b/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java index 4a49d04..7ac86b2 100644 --- a/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java +++ b/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java @@ -18,7 +18,6 @@ package org.apache.kylin.query.relnode; -import java.util.IdentityHashMap; import java.util.List; import java.util.Set; @@ -45,9 +44,7 @@ import org.apache.kylin.common.util.ClassUtil; import org.apache.kylin.metadata.filter.ColumnTupleFilter; import org.apache.kylin.metadata.filter.TupleFilter; import org.apache.kylin.metadata.model.TblColRef; -import org.apache.kylin.metadata.realization.IRealization; -import org.apache.kylin.query.routing.ModelChooser; -import org.apache.kylin.query.routing.QueryRouter; +import org.apache.kylin.query.routing.RealizationChooser; import org.apache.kylin.query.schema.OLAPTable; import com.google.common.collect.Lists; @@ -79,19 +76,17 @@ public class OLAPToEnumerableConverter extends ConverterImpl implements Enumerab System.out.println("EXECUTION PLAN BEFORE REWRITE"); System.out.println(dumpPlan); } - + // post-order travel children OLAPRel.OLAPImplementor olapImplementor = new OLAPRel.OLAPImplementor(); olapImplementor.visitChild(getInput(), this); // identify model List<OLAPContext> contexts = listContextsHavingScan(); - IdentityHashMap<OLAPContext, Set<IRealization>> candidates = ModelChooser.selectModel(contexts); + RealizationChooser.selectRealization(contexts); // identify realization for each context for (OLAPContext context : contexts) { - IRealization realization = QueryRouter.selectRealization(context, candidates.get(context)); - context.realization = realization; doAccessControl(context); } http://git-wip-us.apache.org/repos/asf/kylin/blob/ad67fd76/query/src/main/java/org/apache/kylin/query/routing/ModelChooser.java ---------------------------------------------------------------------- diff --git a/query/src/main/java/org/apache/kylin/query/routing/ModelChooser.java b/query/src/main/java/org/apache/kylin/query/routing/ModelChooser.java deleted file mode 100644 index f842345..0000000 --- a/query/src/main/java/org/apache/kylin/query/routing/ModelChooser.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * 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.query.routing; - -import java.util.Comparator; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -import org.apache.kylin.common.KylinConfig; -import org.apache.kylin.cube.CubeInstance; -import org.apache.kylin.metadata.model.ColumnDesc; -import org.apache.kylin.metadata.model.DataModelDesc; -import org.apache.kylin.metadata.model.JoinDesc; -import org.apache.kylin.metadata.model.JoinTableDesc; -import org.apache.kylin.metadata.model.JoinsTree; -import org.apache.kylin.metadata.model.TableRef; -import org.apache.kylin.metadata.model.TblColRef; -import org.apache.kylin.metadata.project.ProjectManager; -import org.apache.kylin.metadata.realization.IRealization; -import org.apache.kylin.query.relnode.OLAPContext; -import org.apache.kylin.query.relnode.OLAPTableScan; -import org.apache.kylin.query.routing.rules.RemoveBlackoutRealizationsRule; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -public class ModelChooser { - - // select models for given contexts, return realization candidates for each context - public static IdentityHashMap<OLAPContext, Set<IRealization>> selectModel(List<OLAPContext> contexts) { - - IdentityHashMap<OLAPContext, Set<IRealization>> candidates = new IdentityHashMap<>(); - - // attempt one model for all contexts - // Set<IRealization> reals = attemptSelectModel(contexts); - // if (reals != null) { - // for (OLAPContext ctx : contexts) { - // candidates.put(ctx, reals); - // } - // return candidates; - // } - - // try different model for different context - for (OLAPContext ctx : contexts) { - Set<IRealization> reals = attemptSelectModel(ImmutableList.of(ctx)); - if (reals == null) - throw new NoRealizationFoundException("No model found for" + toErrorMsg(ctx)); - - candidates.put(ctx, reals); - } - return candidates; - } - - private static Set<IRealization> attemptSelectModel(List<OLAPContext> contexts) { - Map<DataModelDesc, Set<IRealization>> modelMap = makeOrderedModelMap(contexts); - - for (DataModelDesc model : modelMap.keySet()) { - Map<String, String> aliasMap = matches(model, contexts); - if (aliasMap != null) { - for (OLAPContext ctx : contexts) - fixModel(ctx, model, aliasMap); - return modelMap.get(model); - } - } - return null; - } - - private static String toErrorMsg(OLAPContext ctx) { - StringBuilder buf = new StringBuilder(); - buf.append(ctx.firstTableScan); - for (JoinDesc join : ctx.joins) - buf.append(", ").append(join); - return buf.toString(); - } - - private static Map<String, String> matches(DataModelDesc model, List<OLAPContext> contexts) { - Map<String, String> result = Maps.newHashMap(); - - for (OLAPContext ctx : contexts) { - TableRef firstTable = ctx.firstTableScan.getTableRef(); - - Map<String, String> matchUp = null; - - if (ctx.joins.isEmpty() && model.isLookupTable(firstTable.getTableIdentity())) { - // one lookup table - String modelAlias = model.findFirstTable(firstTable.getTableIdentity()).getAlias(); - matchUp = ImmutableMap.of(firstTable.getAlias(), modelAlias); - } else if (ctx.joins.size() != ctx.allTableScans.size() - 1) { - // has hanging tables - throw new NoRealizationFoundException("Please adjust the sequence of join tables and put subquery or temporary table after lookup tables. " + toErrorMsg(ctx)); - } else { - // normal big joins - if (ctx.joinsTree == null) { - ctx.joinsTree = new JoinsTree(firstTable, ctx.joins); - } - matchUp = ctx.joinsTree.matches(model.getJoinsTree(), result); - } - - if (matchUp == null) - return null; - - result.putAll(matchUp); - } - return result; - } - - private static Map<DataModelDesc, Set<IRealization>> makeOrderedModelMap(List<OLAPContext> contexts) { - // the first context, which is the top most context, contains all columns from all contexts - OLAPContext first = contexts.get(0); - KylinConfig kylinConfig = first.olapSchema.getConfig(); - String projectName = first.olapSchema.getProjectName(); - String factTableName = first.firstTableScan.getOlapTable().getTableName(); - Set<IRealization> realizations = ProjectManager.getInstance(kylinConfig).getRealizationsByTable(projectName, factTableName); - - final Map<DataModelDesc, Set<IRealization>> models = Maps.newHashMap(); - final Map<DataModelDesc, RealizationCost> costs = Maps.newHashMap(); - for (IRealization real : realizations) { - if (real.isReady() == false) - continue; - if (containsAll(real.getAllColumnDescs(), first.allColumns) == false) - continue; - if (RemoveBlackoutRealizationsRule.accept(real) == false) - continue; - - RealizationCost cost = new RealizationCost(real); - DataModelDesc m = real.getModel(); - Set<IRealization> set = models.get(m); - if (set == null) { - set = Sets.newHashSet(); - set.add(real); - models.put(m, set); - costs.put(m, cost); - } else { - set.add(real); - RealizationCost curCost = costs.get(m); - if (cost.compareTo(curCost) < 0) - costs.put(m, cost); - } - } - - // order model by cheapest realization cost - TreeMap<DataModelDesc, Set<IRealization>> result = Maps.newTreeMap(new Comparator<DataModelDesc>() { - @Override - public int compare(DataModelDesc o1, DataModelDesc o2) { - RealizationCost c1 = costs.get(o1); - RealizationCost c2 = costs.get(o2); - int comp = c1.compareTo(c2); - if (comp == 0) - comp = o1.getName().compareTo(o2.getName()); - return comp; - } - }); - result.putAll(models); - - return result; - } - - private static boolean containsAll(Set<ColumnDesc> allColumnDescs, Set<TblColRef> allColumns) { - for (TblColRef col : allColumns) { - if (allColumnDescs.contains(col.getColumnDesc()) == false) - return false; - } - return true; - } - - private static void fixModel(OLAPContext context, DataModelDesc model, Map<String, String> aliasMap) { - for (OLAPTableScan tableScan : context.allTableScans) { - tableScan.fixColumnRowTypeWithModel(model, aliasMap); - } - } - - private static class RealizationCost implements Comparable<RealizationCost> { - final public int priority; - final public int cost; - - public RealizationCost(IRealization real) { - // ref Candidate.PRIORITIES - this.priority = Candidate.PRIORITIES.get(real.getType()); - - // ref CubeInstance.getCost() - int c = real.getAllDimensions().size() * CubeInstance.COST_WEIGHT_DIMENSION + real.getMeasures().size() * CubeInstance.COST_WEIGHT_MEASURE; - for (JoinTableDesc join : real.getModel().getJoinTables()) { - if (join.getJoin().isInnerJoin()) - c += CubeInstance.COST_WEIGHT_INNER_JOIN; - } - this.cost = c; - } - - @Override - public int compareTo(RealizationCost o) { - int comp = this.priority - o.priority; - if (comp != 0) - return comp; - else - return this.cost - o.cost; - } - } -} http://git-wip-us.apache.org/repos/asf/kylin/blob/ad67fd76/query/src/main/java/org/apache/kylin/query/routing/QueryRouter.java ---------------------------------------------------------------------- diff --git a/query/src/main/java/org/apache/kylin/query/routing/QueryRouter.java b/query/src/main/java/org/apache/kylin/query/routing/QueryRouter.java index 2975cf7..18db4ac 100644 --- a/query/src/main/java/org/apache/kylin/query/routing/QueryRouter.java +++ b/query/src/main/java/org/apache/kylin/query/routing/QueryRouter.java @@ -59,7 +59,7 @@ public class QueryRouter { RoutingRule.applyRules(candidates); if (candidates.size() == 0) { - throw new NoRealizationFoundException("Can't find any realization. Please confirm with providers. SQL digest: " + sqlDigest.toString()); + return null; } Candidate chosen = candidates.get(0); http://git-wip-us.apache.org/repos/asf/kylin/blob/ad67fd76/query/src/main/java/org/apache/kylin/query/routing/RealizationChooser.java ---------------------------------------------------------------------- diff --git a/query/src/main/java/org/apache/kylin/query/routing/RealizationChooser.java b/query/src/main/java/org/apache/kylin/query/routing/RealizationChooser.java new file mode 100644 index 0000000..400043b --- /dev/null +++ b/query/src/main/java/org/apache/kylin/query/routing/RealizationChooser.java @@ -0,0 +1,224 @@ +/* + * 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.query.routing; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.apache.kylin.common.KylinConfig; +import org.apache.kylin.cube.CubeInstance; +import org.apache.kylin.metadata.model.ColumnDesc; +import org.apache.kylin.metadata.model.DataModelDesc; +import org.apache.kylin.metadata.model.JoinDesc; +import org.apache.kylin.metadata.model.JoinTableDesc; +import org.apache.kylin.metadata.model.JoinsTree; +import org.apache.kylin.metadata.model.TableRef; +import org.apache.kylin.metadata.model.TblColRef; +import org.apache.kylin.metadata.project.ProjectManager; +import org.apache.kylin.metadata.realization.IRealization; +import org.apache.kylin.query.relnode.OLAPContext; +import org.apache.kylin.query.relnode.OLAPTableScan; +import org.apache.kylin.query.routing.rules.RemoveBlackoutRealizationsRule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +public class RealizationChooser { + + private static final Logger logger = LoggerFactory.getLogger(RealizationChooser.class); + + // select models for given contexts, return realization candidates for each context + public static void selectRealization(List<OLAPContext> contexts) { + // try different model for different context + for (OLAPContext ctx : contexts) { + attemptSelectRealization(ctx); + Preconditions.checkNotNull(ctx.realization); + } + } + + private static void attemptSelectRealization(OLAPContext context) { + Map<DataModelDesc, Set<IRealization>> modelMap = makeOrderedModelMap(context); + + if (modelMap.size() == 0) { + throw new NoRealizationFoundException("No model found for" + toErrorMsg(context)); + } + + for (DataModelDesc model : modelMap.keySet()) { + Map<String, String> aliasMap = matches(model, context); + if (aliasMap != null) { + fixModel(context, model, aliasMap); + + IRealization realization = QueryRouter.selectRealization(context, modelMap.get(model)); + if (realization == null) { + logger.info("Give up on model {} because no suitable realization is found"); + unfixModel(context); + continue; + } + + context.realization = realization; + return; + } + } + + throw new NoRealizationFoundException("No realization found for " + toErrorMsg(context)); + + } + + private static String toErrorMsg(OLAPContext ctx) { + StringBuilder buf = new StringBuilder(); + buf.append(ctx.firstTableScan); + for (JoinDesc join : ctx.joins) + buf.append(", ").append(join); + return buf.toString(); + } + + private static Map<String, String> matches(DataModelDesc model, OLAPContext ctx) { + Map<String, String> result = Maps.newHashMap(); + + TableRef firstTable = ctx.firstTableScan.getTableRef(); + + Map<String, String> matchUp = null; + + if (ctx.joins.isEmpty() && model.isLookupTable(firstTable.getTableIdentity())) { + // one lookup table + String modelAlias = model.findFirstTable(firstTable.getTableIdentity()).getAlias(); + matchUp = ImmutableMap.of(firstTable.getAlias(), modelAlias); + } else if (ctx.joins.size() != ctx.allTableScans.size() - 1) { + // has hanging tables + throw new IllegalStateException("Please adjust the sequence of join tables. " + toErrorMsg(ctx)); + } else { + // normal big joins + if (ctx.joinsTree == null) { + ctx.joinsTree = new JoinsTree(firstTable, ctx.joins); + } + matchUp = ctx.joinsTree.matches(model.getJoinsTree(), result); + } + + if (matchUp == null) + return null; + + result.putAll(matchUp); + + return result; + } + + private static Map<DataModelDesc, Set<IRealization>> makeOrderedModelMap(OLAPContext context) { + OLAPContext first = context; + KylinConfig kylinConfig = first.olapSchema.getConfig(); + String projectName = first.olapSchema.getProjectName(); + String factTableName = first.firstTableScan.getOlapTable().getTableName(); + Set<IRealization> realizations = ProjectManager.getInstance(kylinConfig).getRealizationsByTable(projectName, factTableName); + + final Map<DataModelDesc, Set<IRealization>> models = Maps.newHashMap(); + final Map<DataModelDesc, RealizationCost> costs = Maps.newHashMap(); + for (IRealization real : realizations) { + if (real.isReady() == false) + continue; + if (containsAll(real.getAllColumnDescs(), first.allColumns) == false) + continue; + if (RemoveBlackoutRealizationsRule.accept(real) == false) + continue; + + RealizationCost cost = new RealizationCost(real); + DataModelDesc m = real.getModel(); + Set<IRealization> set = models.get(m); + if (set == null) { + set = Sets.newHashSet(); + set.add(real); + models.put(m, set); + costs.put(m, cost); + } else { + set.add(real); + RealizationCost curCost = costs.get(m); + if (cost.compareTo(curCost) < 0) + costs.put(m, cost); + } + } + + // order model by cheapest realization cost + TreeMap<DataModelDesc, Set<IRealization>> result = Maps.newTreeMap(new Comparator<DataModelDesc>() { + @Override + public int compare(DataModelDesc o1, DataModelDesc o2) { + RealizationCost c1 = costs.get(o1); + RealizationCost c2 = costs.get(o2); + int comp = c1.compareTo(c2); + if (comp == 0) + comp = o1.getName().compareTo(o2.getName()); + return comp; + } + }); + result.putAll(models); + + return result; + } + + private static boolean containsAll(Set<ColumnDesc> allColumnDescs, Set<TblColRef> allColumns) { + for (TblColRef col : allColumns) { + if (allColumnDescs.contains(col.getColumnDesc()) == false) + return false; + } + return true; + } + + private static void fixModel(OLAPContext context, DataModelDesc model, Map<String, String> aliasMap) { + for (OLAPTableScan tableScan : context.allTableScans) { + tableScan.fixColumnRowTypeWithModel(model, aliasMap); + } + } + + private static void unfixModel(OLAPContext context) { + for (OLAPTableScan tableScan : context.allTableScans) { + tableScan.unfixColumnRowTypeWithModel(); + } + } + + private static class RealizationCost implements Comparable<RealizationCost> { + final public int priority; + final public int cost; + + public RealizationCost(IRealization real) { + // ref Candidate.PRIORITIES + this.priority = Candidate.PRIORITIES.get(real.getType()); + + // ref CubeInstance.getCost() + int c = real.getAllDimensions().size() * CubeInstance.COST_WEIGHT_DIMENSION + real.getMeasures().size() * CubeInstance.COST_WEIGHT_MEASURE; + for (JoinTableDesc join : real.getModel().getJoinTables()) { + if (join.getJoin().isInnerJoin()) + c += CubeInstance.COST_WEIGHT_INNER_JOIN; + } + this.cost = c; + } + + @Override + public int compareTo(RealizationCost o) { + int comp = this.priority - o.priority; + if (comp != 0) + return comp; + else + return this.cost - o.cost; + } + } +}