Skip to content

Commit

Permalink
[MONDRIAN-1272] - Native Top Count cannot handle empty members
Browse files Browse the repository at this point in the history
- TODO - describe
  • Loading branch information
Andrey Khayrutdinov committed Sep 8, 2015
1 parent 3963fb3 commit 2436bfb
Show file tree
Hide file tree
Showing 15 changed files with 814 additions and 25 deletions.
19 changes: 16 additions & 3 deletions src/main/mondrian/rolap/RolapNativeSet.java
Expand Up @@ -157,6 +157,7 @@ protected class SetEvaluator implements NativeEvaluator {
private final SchemaReaderWithMemberReaderAvailable schemaReader;
private final TupleConstraint constraint;
private int maxRows = 0;
private boolean nonEmpty = false;

public SetEvaluator(
CrossJoinArg[] args,
Expand Down Expand Up @@ -185,19 +186,22 @@ public Object execute(ResultStyle desiredResultStyle) {
new HighCardSqlTupleReader(constraint));
}
// Use the regular tuple reader.
return executeList(
new SqlTupleReader(constraint));
return executeList(constraint);
}
case MUTABLE_LIST:
case LIST:
return executeList(new SqlTupleReader(constraint));
return executeList(constraint);
default:
throw ResultStyleException.generate(
ResultStyle.ITERABLE_MUTABLELIST_LIST,
Collections.singletonList(desiredResultStyle));
}
}

protected TupleList executeList(TupleConstraint constraint) {
return executeList(new SqlTupleReader(constraint));
}

protected TupleList executeList(final SqlTupleReader tr) {
tr.setMaxRows(maxRows);
for (CrossJoinArg arg : args) {
Expand All @@ -219,6 +223,7 @@ protected TupleList executeList(final SqlTupleReader tr) {
key.add(tr.getCacheKey());
key.addAll(Arrays.asList(args));
key.add(maxRows);
key.add(nonEmpty);

TupleList result = cache.get(key);
boolean hasEnumTargets = (tr.getEnumTargetCount() > 0);
Expand Down Expand Up @@ -389,6 +394,14 @@ int getMaxRows() {
void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}

public boolean isNonEmpty() {
return nonEmpty;
}

public void setNonEmpty(boolean nonEmpty) {
this.nonEmpty = nonEmpty;
}
}

/**
Expand Down
113 changes: 107 additions & 6 deletions src/main/mondrian/rolap/RolapNativeTopCount.java
Expand Up @@ -11,16 +11,24 @@
*/
package mondrian.rolap;

import mondrian.calc.TupleList;
import mondrian.mdx.MemberExpr;
import mondrian.olap.*;
import mondrian.rolap.aggmatcher.AggStar;
import mondrian.rolap.sql.*;
import mondrian.rolap.sql.query.*;
import mondrian.spi.Dialect;
import mondrian.spi.DialectManager;
import mondrian.util.Pair;

import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import static mondrian.rolap.sql.query.RightJoinBuilder.BASE_QUERY_ALIAS;
import static mondrian.rolap.sql.query.RightJoinBuilder.JOIN_QUERY_ALIAS;

/**
* Computes a TopCount in SQL.
*
Expand All @@ -31,7 +39,7 @@ public class RolapNativeTopCount extends RolapNativeSet {

public RolapNativeTopCount() {
super.setEnabled(
MondrianProperties.instance().EnableNativeTopCount.get());
MondrianProperties.instance().EnableNativeTopCount.get());
}

static class TopCountConstraint extends SetConstraint {
Expand All @@ -47,7 +55,7 @@ public TopCountConstraint(
super(args, evaluator, true);
this.orderByExpr = orderByExpr;
this.ascending = ascending;
this.topCount = new Integer(count);
this.topCount = count;
}

/**
Expand Down Expand Up @@ -127,11 +135,10 @@ NativeEvaluator createEvaluator(
FunDef fun,
Exp[] args)
{
boolean ascending;

if (!isEnabled()) {
return null;
}

if (!TopCountConstraint.isValidContext(
evaluator, restrictMemberTypes()))
{
Expand All @@ -140,6 +147,7 @@ evaluator, restrictMemberTypes()))

// is this "TopCount(<set>, <count>, [<numeric expr>])"
String funName = fun.getName();
final boolean ascending;
if ("TopCount".equalsIgnoreCase(funName)) {
ascending = false;
} else if ("BottomCount".equalsIgnoreCase(funName)) {
Expand Down Expand Up @@ -216,14 +224,107 @@ evaluator, restrictMemberTypes()))
TupleConstraint constraint =
new TopCountConstraint(
count, combinedArgs, evaluator, orderByExpr, ascending);
SetEvaluator sev =
new SetEvaluator(cjArgs, schemaReader, constraint);

SetEvaluator sev;
if (evaluator.isNonEmpty() && args.length == 3) {
sev = new SetEvaluator(cjArgs, schemaReader, constraint);
} else {
sev = new NativeTopCountSetEvaluator(cjArgs, schemaReader, constraint);
}
sev.setMaxRows(count);
sev.setNonEmpty(evaluator.isNonEmpty());
return sev;
} finally {
evaluator.restore(savepoint);
}
}

private class NativeTopCountSetEvaluator extends SetEvaluator {
public NativeTopCountSetEvaluator(CrossJoinArg[] args, SchemaReader schemaReader, TupleConstraint constraint) {
super(args, schemaReader, constraint);
}

@Override
protected TupleList executeList(TupleConstraint constraint) {
return executeList(new TopCountSqlTupleReader(constraint));
}
}

private static class TopCountSqlTupleReader extends SqlTupleReader {
public TopCountSqlTupleReader(TupleConstraint constraint) {
super(constraint);
}

@Override
protected Pair<String, List<SqlStatement.Type>> generateSelectForLevels(DataSource dataSource, RolapCube baseCube, WhichSelect whichSelect) {
SqlQueryBuilder subselect = new SqlQueryBuilder(DialectManager.createDialect(dataSource, null));
subselect.setAllowHints(true);
super.generateSelectForLevels(baseCube, whichSelect, subselect, constraint);

Evaluator evaluator = getEvaluator(constraint);
AggStar aggStar = chooseAggStar(constraint, evaluator, baseCube);

CrossJoinBuilder builder = CrossJoinBuilder.builder(subselect.getDialect());

for (TargetBase target : targets) {
if (target.getSrcMembers() == null) {
SqlQueryBuilder crossJoinQuery = new SqlQueryBuilder(subselect.getDialect());
addLevelMemberSql(
crossJoinQuery,
target.getLevel(),
baseCube,
whichSelect,
aggStar,
DefaultTupleConstraint.instance());
builder.append(crossJoinQuery);
}
}

SqlSelect crossJoin = builder.build();
if (crossJoin == null) {
return subselect.toSqlAndTypes();
}

SqlSelect result = RightJoinBuilder.builder(subselect.getDialect())
.query(subselect.toSelect())
.rightJoin(crossJoin)
.build(
new RightJoinBuilder.Populator() {
@Override
public void populateOutput(SqlQuery query, SqlSelect baseQuery, SqlSelect joinQuery) {
SqlUtils.addSelectFields(query, joinQuery.getOutputFields(), JOIN_QUERY_ALIAS);
int populated = joinQuery.getOutputFields().size();
int subQuerySize = baseQuery.getOutputFields().size();
if (populated < subQuerySize) {
List<SelectElement> restFromBase = baseQuery.getOutputFields().subList(populated, subQuerySize);
SqlUtils.addSelectFields(query, restFromBase, BASE_QUERY_ALIAS);
}
}
},
new RightJoinBuilder.Mapper() {
@Override
public String map(List<SelectElement> baseQueryOutput, List<SelectElement> joinQueryOutput, Dialect dialect) {
StringBuilder sb = new StringBuilder();
addEqualsCondition(sb, baseQueryOutput.get(0), joinQueryOutput.get(0), dialect);
for (int i = 1, len = joinQueryOutput.size(); i < len; i++) {
sb.append(" and ");
addEqualsCondition(sb, baseQueryOutput.get(i), joinQueryOutput.get(i), dialect);
}
return sb.toString();
}

private void addEqualsCondition(StringBuilder sb, SelectElement baseQueryOutput, SelectElement joinQueryOutput, Dialect dialect) {
sb.append('(')
.append(dialect.quoteIdentifier(BASE_QUERY_ALIAS, baseQueryOutput.getAlias()))
.append('=')
.append(dialect.quoteIdentifier(JOIN_QUERY_ALIAS, joinQueryOutput.getAlias()))
.append(')');
}
});

return Pair.of(result.getSql(), result.getOutputTypes());
}
}
}

// End RolapNativeTopCount.java
31 changes: 24 additions & 7 deletions src/main/mondrian/rolap/SqlTupleReader.java
Expand Up @@ -904,19 +904,28 @@ Pair<String, List<SqlStatement.Type>> sqlForEmptyTuple(
* @param whichSelect Position of this select statement in a union
* @return SQL statement string and types
*/
Pair<String, List<SqlStatement.Type>> generateSelectForLevels(
protected Pair<String, List<SqlStatement.Type>> generateSelectForLevels(
DataSource dataSource,
RolapCube baseCube,
WhichSelect whichSelect)
{
String s =
"while generating query to retrieve members of level(s) " + targets;
"while generating query to retrieve members of level(s) " + targets;

// Allow query to use optimization hints from the table definition
SqlQuery sqlQuery = SqlQuery.newQuery(dataSource, s);
sqlQuery.setAllowHints(true);

generateSelectForLevels(baseCube, whichSelect, sqlQuery, constraint);
return sqlQuery.toSqlAndTypes();
}

protected void generateSelectForLevels(
RolapCube baseCube,
WhichSelect whichSelect,
SqlQuery sqlQuery,
TupleConstraint constraint)
{
Evaluator evaluator = getEvaluator(constraint);
AggStar aggStar = chooseAggStar(constraint, evaluator, baseCube);

Expand All @@ -930,13 +939,11 @@ Pair<String, List<SqlStatement.Type>> generateSelectForLevels(
target.getLevel(),
baseCube,
whichSelect,
aggStar);
aggStar,
constraint);
}
}

constraint.addConstraint(sqlQuery, baseCube, aggStar);

return sqlQuery.toSqlAndTypes();
}

boolean targetIsOnBaseCube(TargetBase target, RolapCube baseCube) {
Expand Down Expand Up @@ -1039,7 +1046,17 @@ protected void addLevelMemberSql(
RolapLevel level,
RolapCube baseCube,
WhichSelect whichSelect,
AggStar aggStar)
AggStar aggStar) {
addLevelMemberSql(sqlQuery, level, baseCube, whichSelect, aggStar, constraint);
}

protected void addLevelMemberSql(
SqlQuery sqlQuery,
RolapLevel level,
RolapCube baseCube,
WhichSelect whichSelect,
AggStar aggStar,
TupleConstraint constraint)
{
RolapHierarchy hierarchy = level.getHierarchy();

Expand Down
22 changes: 13 additions & 9 deletions src/main/mondrian/rolap/sql/SqlQuery.java
Expand Up @@ -76,12 +76,12 @@ public class SqlQuery {

private boolean distinct;

private final ClauseList select;
private final FromClauseList from;
private final ClauseList where;
private final ClauseList groupBy;
private final ClauseList having;
private final ClauseList orderBy;
protected final ClauseList select;
protected final FromClauseList from;
protected final ClauseList where;
protected final ClauseList groupBy;
protected final ClauseList having;
protected final ClauseList orderBy;
private final List<ClauseList> groupingSets;
private final ClauseList groupingFunctions;

Expand All @@ -101,7 +101,7 @@ public class SqlQuery {
private final List<String> fromAliases;

/** The SQL dialect this query is to be generated in. */
private final Dialect dialect;
protected final Dialect dialect;

/** Scratch buffer. Clear it before use. */
private final StringBuilder buf;
Expand All @@ -117,7 +117,7 @@ public class SqlQuery {
mapRootToRelations =
new HashMap<MondrianDef.RelationOrJoin, List<RelInfo>>();

private final Map<String, String> columnAliases =
protected final Map<String, String> columnAliases =
new HashMap<String, String>();

private static final String INDENT = " ";
Expand Down Expand Up @@ -785,6 +785,10 @@ private void flatten(
}
}

public boolean isGenerateFormattedSql() {
return generateFormattedSql;
}

private static class JoinOnClause {
private final String condition;
private final String left;
Expand Down Expand Up @@ -881,7 +885,7 @@ void appendJoin(
}
}

static class ClauseList extends ArrayList<String> {
protected static class ClauseList extends ArrayList<String> {
protected final boolean allowDups;

ClauseList(final boolean allowDups) {
Expand Down
24 changes: 24 additions & 0 deletions src/main/mondrian/rolap/sql/query/AliasGenerator.java
@@ -0,0 +1,24 @@
package mondrian.rolap.sql.query;

/**
* @author Andrey Khayrutdinov
*/
// todo Khayrutdinov: docs
class AliasGenerator {
private final StringBuilder sb;
private final int seedLength;
private int counter;

AliasGenerator(String seed) {
this.seedLength = seed.length();
this.counter = 0;

sb = new StringBuilder(seedLength + 2);
sb.append(seed);
}

String nextAlias() {
sb.setLength(seedLength);
return sb.append(counter++).toString();
}
}

0 comments on commit 2436bfb

Please sign in to comment.