LENS-605 : Fix Candidate pruning wrt sourcecolumns required for joinchains
Project: http://git-wip-us.apache.org/repos/asf/incubator-lens/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-lens/commit/5f859118 Tree: http://git-wip-us.apache.org/repos/asf/incubator-lens/tree/5f859118 Diff: http://git-wip-us.apache.org/repos/asf/incubator-lens/diff/5f859118 Branch: refs/heads/current-release-line Commit: 5f859118c2ea49cc09dd59472d266064d36ca978 Parents: 0cf6c4e Author: Amareshwari Sriramadasu <[email protected]> Authored: Mon Jun 22 14:22:13 2015 +0530 Committer: Rajat Khandelwal <[email protected]> Committed: Mon Jun 22 14:22:13 2015 +0530 ---------------------------------------------------------------------- .../lens/cube/parse/CandidateTableResolver.java | 124 +++++++++++++++---- .../lens/cube/parse/CubeQueryContext.java | 70 ++++++++++- .../cube/parse/DenormalizationResolver.java | 6 +- .../lens/cube/parse/ExpressionResolver.java | 27 ++-- .../apache/lens/cube/parse/JoinResolver.java | 4 +- .../cube/parse/TestDenormalizationResolver.java | 32 ++++- 6 files changed, 218 insertions(+), 45 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/5f859118/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTableResolver.java ---------------------------------------------------------------------- diff --git a/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTableResolver.java b/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTableResolver.java index 79d6d43..1b5c09a 100644 --- a/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTableResolver.java +++ b/lens-cube/src/main/java/org/apache/lens/cube/parse/CandidateTableResolver.java @@ -23,6 +23,9 @@ import java.util.*; import org.apache.lens.cube.metadata.*; import org.apache.lens.cube.parse.CandidateTablePruneCause.CandidateTablePruneCode; import org.apache.lens.cube.parse.CubeQueryContext.OptionalDimCtx; +import org.apache.lens.cube.parse.CubeQueryContext.QueriedExprColumn; +import org.apache.lens.cube.parse.ExpressionResolver.ExprSpecContext; +import org.apache.lens.cube.parse.ExpressionResolver.ExpressionContext; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; @@ -32,6 +35,8 @@ import org.apache.hadoop.hive.ql.ErrorMsg; import org.apache.hadoop.hive.ql.metadata.HiveException; import org.apache.hadoop.hive.ql.parse.SemanticException; +import com.google.common.collect.Sets; + /** * This resolver prunes the candidate tables for following cases * <p/> @@ -493,39 +498,127 @@ class CandidateTableResolver implements ContextRewriter { return; } // check for source columns for denorm columns - Map<CandidateTable, Collection<String>> removedCandidates = new HashMap<CandidateTable, Collection<String>>(); + Map<Dimension, Set<CandidateTable>> removedCandidates = new HashMap<Dimension, Set<CandidateTable>>(); for (Map.Entry<Dimension, OptionalDimCtx> optdimEntry : cubeql.getOptionalDimensionMap().entrySet()) { Dimension dim = optdimEntry.getKey(); OptionalDimCtx optdim = optdimEntry.getValue(); Iterator<CandidateTable> iter = optdim.requiredForCandidates.iterator(); + // remove candidates from each optional dim if the dimension is not reachable from candidate + Set<CandidateTable> cTableSet = Sets.newHashSet(); + removedCandidates.put(dim, cTableSet); while (iter.hasNext()) { CandidateTable candidate = iter.next(); boolean remove = false; if (cubeql.getAutoJoinCtx().getJoinPathFromColumns().get(dim) == null) { - LOG.info("Removing candidate" + candidate + " from requiredForCandidates of" + dim + ", as no join paths" + LOG.info("Removing candidate " + candidate + " from requiredForCandidates of " + dim + ", as no join paths" + " exist"); remove = true; - removedCandidates.put(candidate, optdim.colQueried); } else { List<String> colSet = cubeql.getAutoJoinCtx().getJoinPathFromColumns().get(dim).get(candidate.getBaseTable()); if (!checkForColumnExists(candidate, colSet)) { - LOG.info("Removing candidate" + candidate + " from requiredForCandidates of" + dim + ", as columns:" + LOG.info("Removing candidate " + candidate + " from requiredForCandidates of " + dim + ", as columns:" + colSet + " do not exist"); remove = true; - removedCandidates.put(candidate, colSet); } } if (remove) { iter.remove(); + cTableSet.add(candidate); } } } - Set<CandidateTable> candidatesReachableThroughRefs = new HashSet<CandidateTable>(); + // For each ref column required in cube query + // remove candidates which are not reachable + // For example: + // CTable | Col1 | Col2 + // F1 | reachable through D1 | Not reachable + // F2 | Reachable through D2 | Reachable through D5 + // F3 | Not reachable | Not reachable + // F4 | Not reachable | Reachable through D6 + // F5 | Directly available | Directly available + // F6 | Directly available | Not reachable + // F3 and F4 will get pruned while iterating over col1 and F1, F6 will get pruned while iterating over col2. + for (Map.Entry<String, Set<Dimension>> dimColEntry : cubeql.getRefColToDim().entrySet()) { + Set<CandidateTable> candidatesReachableThroughRefs = new HashSet<CandidateTable>(); + String col = dimColEntry.getKey(); + Set<Dimension> dimSet = dimColEntry.getValue(); + for (Dimension dim : dimSet) { + OptionalDimCtx optdim = cubeql.getOptionalDimensionMap().get(dim); + candidatesReachableThroughRefs.addAll(optdim.requiredForCandidates); + } + for (Dimension dim : dimSet) { + for (CandidateTable candidate : removedCandidates.get(dim)) { + if (!candidatesReachableThroughRefs.contains(candidate)) { + if (candidate instanceof CandidateFact) { + if (cubeql.getCandidateFacts().contains(candidate)) { + LOG.info("Not considering fact:" + candidate + " as its required optional dims are not reachable"); + cubeql.getCandidateFacts().remove(candidate); + cubeql.addFactPruningMsgs(((CandidateFact) candidate).fact, + CandidateTablePruneCause.columnNotFound(col)); + } + } else if (cubeql.getCandidateDimTables().containsKey(((CandidateDim) candidate).getBaseTable())) { + LOG.info("Not considering dimtable:" + candidate + " as its required optional dims are not reachable"); + cubeql.getCandidateDimTables().get(((CandidateDim) candidate).getBaseTable()).remove(candidate); + cubeql.addDimPruningMsgs((Dimension) candidate.getBaseTable(), (CubeDimensionTable) candidate.getTable(), + CandidateTablePruneCause.columnNotFound(col)); + } + } + } + } + } + + // For each expression column required in cube query + // remove candidates in which expressions are not evaluable - for expression column all dimensions accessed in + // expression should be reachable from candidate. + // For example: + // CTable | Col1 | Col2 + // F1 | evaluable through D1, D3 | Not evaluable + // F2 | evaluable through D2, D4 | evaluable through D5 + // F3 | Not evaluable | Not evaluable + // F4 | Not evaluable | evaluable through D6 + // F5 | Directly available | Directly available + // F6 | Directly available | Not evaluable + for (Map.Entry<QueriedExprColumn, Set<Dimension>> exprColEntry : cubeql.getExprColToDim().entrySet()) { + QueriedExprColumn col = exprColEntry.getKey(); + Set<Dimension> dimSet = exprColEntry.getValue(); + ExpressionContext ec = cubeql.getExprCtx().getExpressionContext(col.getExprCol(), col.getAlias()); + for (Dimension dim : dimSet) { + for (CandidateTable candidate : removedCandidates.get(dim)) { + // check if evaluable expressions of this candidate are no more evaluable because dimension is not reachable + // if no evaluable expressions exist, then remove the candidate + Iterator<ExprSpecContext> escIter = ec.getEvaluableExpressions().get(candidate).iterator(); + while (escIter.hasNext()) { + ExprSpecContext esc = escIter.next(); + if (esc.getExprDims().contains(dim)) { + escIter.remove(); + } + } + if (cubeql.getExprCtx().isEvaluable(col.getExprCol(), candidate)) { + // candidate has other evaluable expressions + continue; + } + if (candidate instanceof CandidateFact) { + if (cubeql.getCandidateFacts().contains(candidate)) { + LOG.info("Not considering fact:" + candidate + " as is not reachable through any optional dim"); + cubeql.getCandidateFacts().remove(candidate); + cubeql.addFactPruningMsgs(((CandidateFact) candidate).fact, + CandidateTablePruneCause.expressionNotEvaluable(col.getExprCol())); + } + } else if (cubeql.getCandidateDimTables().containsKey(((CandidateDim) candidate).getBaseTable())) { + LOG.info("Not considering dimtable:" + candidate + " as is not reachable through any optional dim"); + cubeql.getCandidateDimTables().get(((CandidateDim) candidate).getBaseTable()).remove(candidate); + cubeql.addDimPruningMsgs((Dimension) candidate.getBaseTable(), (CubeDimensionTable) candidate.getTable(), + CandidateTablePruneCause.expressionNotEvaluable(col.getExprCol())); + } + } + } + } + + // remove optional dims which are not required any more. Set<Dimension> tobeRemoved = new HashSet<Dimension>(); for (Map.Entry<Dimension, OptionalDimCtx> optdimEntry : cubeql.getOptionalDimensionMap().entrySet()) { Dimension dim = optdimEntry.getKey(); OptionalDimCtx optdim = optdimEntry.getValue(); - candidatesReachableThroughRefs.addAll(optdim.requiredForCandidates); if ((!optdim.colQueried.isEmpty() && optdim.requiredForCandidates.isEmpty()) && !optdim.isRequiredInJoinChain) { LOG.info("Not considering optional dimension " + dim + " as all requiredForCandidates are removed"); tobeRemoved.add(dim); @@ -534,23 +627,6 @@ class CandidateTableResolver implements ContextRewriter { for (Dimension dim : tobeRemoved) { removeOptionalDim(cubeql, dim); } - for (CandidateTable candidate : removedCandidates.keySet()) { - if (!candidatesReachableThroughRefs.contains(candidate)) { - if (candidate instanceof CandidateFact) { - if (cubeql.getCandidateFacts().contains(candidate)) { - LOG.info("Not considering fact:" + candidate + " as is not reachable through any optional dim"); - cubeql.getCandidateFacts().remove(candidate); - cubeql.addFactPruningMsgs(((CandidateFact) candidate).fact, - CandidateTablePruneCause.columnNotFound(removedCandidates.get(candidate))); - } - } else if (cubeql.getCandidateDimTables().containsKey(((CandidateDim) candidate).getBaseTable())) { - LOG.info("Not considering dimtable:" + candidate + " as is not reachable through any optional dim"); - cubeql.getCandidateDimTables().get(((CandidateDim) candidate).getBaseTable()).remove(candidate); - cubeql.addDimPruningMsgs((Dimension) candidate.getBaseTable(), (CubeDimensionTable) candidate.getTable(), - CandidateTablePruneCause.columnNotFound(removedCandidates.get(candidate))); - } - } - } } private void resolveCandidateDimTables(CubeQueryContext cubeql) throws SemanticException { http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/5f859118/lens-cube/src/main/java/org/apache/lens/cube/parse/CubeQueryContext.java ---------------------------------------------------------------------- diff --git a/lens-cube/src/main/java/org/apache/lens/cube/parse/CubeQueryContext.java b/lens-cube/src/main/java/org/apache/lens/cube/parse/CubeQueryContext.java index 33b1a62..57c075c 100644 --- a/lens-cube/src/main/java/org/apache/lens/cube/parse/CubeQueryContext.java +++ b/lens-cube/src/main/java/org/apache/lens/cube/parse/CubeQueryContext.java @@ -45,7 +45,13 @@ import org.apache.hadoop.hive.ql.parse.*; import org.codehaus.jackson.map.ObjectMapper; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; + +import lombok.AllArgsConstructor; + +import lombok.Data; + import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -346,11 +352,45 @@ public class CubeQueryContext implements TrackQueriedColumns { return partsQueried; } + // map of ref column in query to set of Dimension that have the column - which are added as optional dims + @Getter + private Map<String, Set<Dimension>> refColToDim = Maps.newHashMap(); + + public void updateRefColDim(String col, Dimension dim) { + Set<Dimension> refDims = refColToDim.get(col.toLowerCase()); + if (refDims == null) { + refDims = Sets.newHashSet(); + refColToDim.put(col.toLowerCase(), refDims); + } + refDims.add(dim); + } + + @Data + @AllArgsConstructor + static class QueriedExprColumn { + private String exprCol; + private String alias; + } + // map of expression column in query to set of Dimension that are accessed in the expression column - which are added + // as optional dims + @Getter + private Map<QueriedExprColumn, Set<Dimension>> exprColToDim = Maps.newHashMap(); + + public void updateExprColDim(String tblAlias, String col, Dimension dim) { + + QueriedExprColumn qexpr = new QueriedExprColumn(col, tblAlias); + Set<Dimension> exprDims = exprColToDim.get(qexpr); + if (exprDims == null) { + exprDims = Sets.newHashSet(); + exprColToDim.put(qexpr, exprDims); + } + exprDims.add(dim); + } + // Holds the context of optional dimension // A dimension is optional if it is not queried directly by the user, but is // required by a candidate table to get a denormalized field from reference // or required in a join chain - @ToString static class OptionalDimCtx { OptionalDimCtx() { @@ -361,8 +401,22 @@ public class CubeQueryContext implements TrackQueriedColumns { boolean isRequiredInJoinChain = false; } - public void addOptionalDimTable(String alias, CandidateTable candidate, boolean isRequiredInJoin, String... cols) - throws SemanticException { + public void addOptionalJoinDimTable(String alias, boolean isRequired) throws SemanticException { + addOptionalDimTable(alias, null, isRequired, null, false, (String[])null); + } + + public void addOptionalExprDimTable(String dimAlias, String queriedExpr, String srcTableAlias, + CandidateTable candidate, String... cols) throws SemanticException { + addOptionalDimTable(dimAlias, candidate, false, queriedExpr, false, srcTableAlias, cols); + } + + public void addOptionalDimTable(String alias, CandidateTable candidate, boolean isRequiredInJoin, String cubeCol, + boolean isRef, String... cols) throws SemanticException { + addOptionalDimTable(alias, candidate, isRequiredInJoin, cubeCol, true, null, cols); + } + + private void addOptionalDimTable(String alias, CandidateTable candidate, boolean isRequiredInJoin, String cubeCol, + boolean isRef, String tableAlias, String... cols) throws SemanticException { alias = alias.toLowerCase(); try { if (!addQueriedTable(alias, true)) { @@ -380,10 +434,18 @@ public class CubeQueryContext implements TrackQueriedColumns { } optDim.requiredForCandidates.add(candidate); } + if (cubeCol != null) { + if (isRef) { + updateRefColDim(cubeCol, dim); + } else { + updateExprColDim(tableAlias, cubeCol, dim); + } + } if (!optDim.isRequiredInJoinChain) { optDim.isRequiredInJoinChain = isRequiredInJoin; } - LOG.info("Adding optional dimension:" + dim + " optDim:" + optDim); + LOG.info("Adding optional dimension:" + dim + " optDim:" + optDim + + (cubeCol == null ? "" : " for column:" + cubeCol + " isRef:" + isRef)); } catch (HiveException e) { throw new SemanticException(e); } http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/5f859118/lens-cube/src/main/java/org/apache/lens/cube/parse/DenormalizationResolver.java ---------------------------------------------------------------------- diff --git a/lens-cube/src/main/java/org/apache/lens/cube/parse/DenormalizationResolver.java b/lens-cube/src/main/java/org/apache/lens/cube/parse/DenormalizationResolver.java index 3ed94fa..075cdb4 100644 --- a/lens-cube/src/main/java/org/apache/lens/cube/parse/DenormalizationResolver.java +++ b/lens-cube/src/main/java/org/apache/lens/cube/parse/DenormalizationResolver.java @@ -152,11 +152,13 @@ public class DenormalizationResolver implements ContextRewriter { refCols.add(refer); // Add to optional tables if (refer.col.isChainedColumn()) { - cubeql.addOptionalDimTable(refer.col.getChainName(), table, false, refer.col.getRefColumn()); + cubeql.addOptionalDimTable(refer.col.getChainName(), table, false, refer.col.getName(), true, + refer.col.getRefColumn()); } else { for (TableReference reference : refer.col.getReferences()) { - cubeql.addOptionalDimTable(reference.getDestTable(), table, false, reference.getDestColumn()); + cubeql.addOptionalDimTable(reference.getDestTable(), table, false, refer.col.getName(), true, + reference.getDestColumn()); } } return true; http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/5f859118/lens-cube/src/main/java/org/apache/lens/cube/parse/ExpressionResolver.java ---------------------------------------------------------------------- diff --git a/lens-cube/src/main/java/org/apache/lens/cube/parse/ExpressionResolver.java b/lens-cube/src/main/java/org/apache/lens/cube/parse/ExpressionResolver.java index 8e199ea..b2997dd 100644 --- a/lens-cube/src/main/java/org/apache/lens/cube/parse/ExpressionResolver.java +++ b/lens-cube/src/main/java/org/apache/lens/cube/parse/ExpressionResolver.java @@ -68,6 +68,7 @@ class ExpressionResolver implements ContextRewriter { @Getter private Set<ExprSpecContext> allExprs = new LinkedHashSet<ExprSpecContext>(); private Set<CandidateTable> directlyAvailableIn = new HashSet<CandidateTable>(); + @Getter private Map<CandidateTable, Set<ExprSpecContext>> evaluableExpressions = new HashMap<CandidateTable, Set<ExprSpecContext>>(); private boolean hasMeasures = false; @@ -95,7 +96,7 @@ class ExpressionResolver implements ContextRewriter { try { if (!CubeQueryContext.DEFAULT_TABLE.equalsIgnoreCase(table) && !srcAlias.equals(table)) { cubeql.addOptionalDimTable(table, null, - false, esc.getTblAliasToColumns().get(table).toArray(new String[0])); + false, null, false, esc.getTblAliasToColumns().get(table).toArray(new String[0])); esc.exprDims.add((Dimension) cubeql.getCubeTableForAlias(table)); } } catch (HiveException e) { @@ -132,7 +133,7 @@ class ExpressionResolver implements ContextRewriter { for (String table : esc.getTblAliasToColumns().keySet()) { try { if (!CubeQueryContext.DEFAULT_TABLE.equalsIgnoreCase(table) && !srcAlias.equals(table)) { - cubeql.addOptionalDimTable(table, null, false, + cubeql.addOptionalDimTable(table, null, false, null, false, esc.getTblAliasToColumns().get(table).toArray(new String[0])); esc.exprDims.add((Dimension) cubeql.getCubeTableForAlias(table)); } @@ -171,17 +172,17 @@ class ExpressionResolver implements ContextRewriter { if (evalSet == null) { evalSet = new LinkedHashSet<ExprSpecContext>(); evaluableExpressions.put(cTable, evalSet); - // add optional dimensions involved in expressions - for (String table : esc.getTblAliasToColumns().keySet()) { - try { - if (!CubeQueryContext.DEFAULT_TABLE.equalsIgnoreCase(table) && !srcAlias.equals(table)) { - cubeql.addOptionalDimTable(table, cTable, - false, esc.getTblAliasToColumns().get(table).toArray(new String[0])); - esc.exprDims.add((Dimension) cubeql.getCubeTableForAlias(table)); - } - } catch (HiveException e) { - throw new SemanticException(e); + } + // add optional dimensions involved in expressions + for (String table : esc.getTblAliasToColumns().keySet()) { + try { + if (!CubeQueryContext.DEFAULT_TABLE.equalsIgnoreCase(table) && !srcAlias.equals(table)) { + cubeql.addOptionalExprDimTable(table, exprCol.getName(), srcAlias, cTable, + esc.getTblAliasToColumns().get(table).toArray(new String[0])); + esc.exprDims.add((Dimension) cubeql.getCubeTableForAlias(table)); } + } catch (HiveException e) { + throw new SemanticException(e); } } evalSet.add(esc); @@ -210,6 +211,7 @@ class ExpressionResolver implements ContextRewriter { private Set<ExprSpec> exprSpecs = new LinkedHashSet<ExprSpec>(); @Getter private ASTNode finalAST; + @Getter private Set<Dimension> exprDims = new HashSet<Dimension>(); // for each expression store alias to columns queried @Getter @@ -367,6 +369,7 @@ class ExpressionResolver implements ContextRewriter { } for (ExprSpecContext esc : ec.allExprs) { if (esc.getTblAliasToColumns().get(alias) == null) { + log.debug("{} = {} is evaluable in {}", expr, esc, cTable); ec.addEvaluable(cubeql, cTable, esc); } else { Set<String> columns = esc.getTblAliasToColumns().get(alias); http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/5f859118/lens-cube/src/main/java/org/apache/lens/cube/parse/JoinResolver.java ---------------------------------------------------------------------- diff --git a/lens-cube/src/main/java/org/apache/lens/cube/parse/JoinResolver.java b/lens-cube/src/main/java/org/apache/lens/cube/parse/JoinResolver.java index a760599..97c7a46 100644 --- a/lens-cube/src/main/java/org/apache/lens/cube/parse/JoinResolver.java +++ b/lens-cube/src/main/java/org/apache/lens/cube/parse/JoinResolver.java @@ -1016,7 +1016,7 @@ class JoinResolver implements ContextRewriter { for (JoinChain chain : cubeql.getJoinchains().values()) { for (String dimName : chain.getIntermediateDimensions()) { - cubeql.addOptionalDimTable(dimName, null, true, null); + cubeql.addOptionalJoinDimTable(dimName, true); } } @@ -1122,7 +1122,7 @@ class JoinResolver implements ContextRewriter { for (TableRelationship rel : joinPath.getEdges()) { // Add the joined tables to the queries table sets so that they are // resolved in candidate resolver - cubeql.addOptionalDimTable(rel.getToTable().getName(), null, required, null); + cubeql.addOptionalJoinDimTable(rel.getToTable().getName(), required); } } } http://git-wip-us.apache.org/repos/asf/incubator-lens/blob/5f859118/lens-cube/src/test/java/org/apache/lens/cube/parse/TestDenormalizationResolver.java ---------------------------------------------------------------------- diff --git a/lens-cube/src/test/java/org/apache/lens/cube/parse/TestDenormalizationResolver.java b/lens-cube/src/test/java/org/apache/lens/cube/parse/TestDenormalizationResolver.java index e615ccc..8c3c219 100644 --- a/lens-cube/src/test/java/org/apache/lens/cube/parse/TestDenormalizationResolver.java +++ b/lens-cube/src/test/java/org/apache/lens/cube/parse/TestDenormalizationResolver.java @@ -23,7 +23,9 @@ import static org.apache.lens.cube.parse.CubeTestSetup.*; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.lens.cube.parse.CandidateTablePruneCause.CandidateTablePruneCode; import org.apache.lens.server.api.error.LensException; @@ -164,7 +166,7 @@ public class TestDenormalizationResolver extends TestQueryRewrite { })) ); put("summary1,cheapfact,testfactmonthly,testfact2,testfact", - Arrays.asList(CandidateTablePruneCause.columnNotFound("dim2big1", "dim2"))); + Arrays.asList(CandidateTablePruneCause.columnNotFound("dim2big2"))); } } )); @@ -225,6 +227,34 @@ public class TestDenormalizationResolver extends TestQueryRewrite { } @Test + public void testCubeQueryWithTwoRefCols() throws Exception { + Configuration tConf = new Configuration(conf); + tConf.set(CubeQueryConfUtil.DRIVER_SUPPORTED_STORAGES, ""); + CubeQueryContext cubeql = rewriteCtx("select dim2, test_time_dim2 from testcube where " + TWO_DAYS_RANGE, tConf); + Set<String> candidateFacts = new HashSet<String>(); + for (CandidateFact cfact : cubeql.getCandidateFacts()) { + candidateFacts.add(cfact.getName().toLowerCase()); + } + // testfact contains test_time_dim_hour_id, but not dim2 - it should have been removed. + Assert.assertFalse(candidateFacts.contains("testfact")); + // summary2 contains dim2, but not test_time_dim2 - it should have been removed. + Assert.assertFalse(candidateFacts.contains("summary2")); + } + + @Test + public void testDimensionQueryWithTwoRefCols() throws Exception { + Configuration tConf = new Configuration(conf); + tConf.set(CubeQueryConfUtil.DRIVER_SUPPORTED_STORAGES, ""); + CubeQueryContext cubeql = rewriteCtx("select citydim.zipcode, citydim.statename from" + " citydim", tConf); + Set<String> candidateDims = new HashSet<String>(); + for (CandidateDim cdim : cubeql.getCandidateDims().get(cubeql.getMetastoreClient().getDimension("citydim"))) { + candidateDims.add(cdim.getName()); + } + // city_table2 contains stateid, but not zipcode - it should have been removed. + Assert.assertFalse(candidateDims.contains("city_table2")); + } + + @Test public void testDimensionQueryWithExpressionHavingDenormColumn() throws Exception { String hqlQuery = rewrite("select citydim.name, citydim.citystate from" + " citydim", conf); String joinExpr =
