Skip to content

Commit

Permalink
added selection context concept and a new union algorithm when select…
Browse files Browse the repository at this point in the history
…ing multiple dimensions on an axis to support drill down

git-svn-id: https://olap4j.svn.sourceforge.net/svnroot/olap4j/trunk@304 c6a108a4-781c-0410-a6c6-c2d559e19af0
  • Loading branch information
Will Gorman committed Mar 8, 2010
1 parent f110e66 commit a236eb0
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 85 deletions.
136 changes: 98 additions & 38 deletions src/org/olap4j/query/Olap4jNodeConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.Collections;
import java.util.List;

import mondrian.olap.Util;

import org.olap4j.Axis;
import org.olap4j.mdx.AxisNode;
import org.olap4j.mdx.CallNode;
Expand Down Expand Up @@ -82,27 +84,93 @@ private static CallNode generateListTupleCall(List<ParseTreeNode> cnodes) {
Syntax.Parentheses,
cnodes);
}

protected static CallNode getMemberSet(QueryDimension dimension) {
return
new CallNode(
null,
"{}",
Syntax.Braces,
toOlap4j(dimension));

protected static CallNode generateCrossJoin(List<Selection> selections) {
ParseTreeNode sel1 = toOlap4j(selections.remove(0));
if (sel1 instanceof MemberNode) {
sel1 = generateSetCall(sel1);
}
if (selections.size() == 1) {
ParseTreeNode sel2 = toOlap4j(selections.get(0));
if (sel2 instanceof MemberNode) {
sel2 = generateSetCall(sel2);
}

return new CallNode( null, "CrossJoin", Syntax.Function, sel1, sel2);
} else {
return new CallNode( null, "CrossJoin", Syntax.Function, sel1, generateCrossJoin(selections));
}
}

protected static CallNode generateUnion(List<List<Selection>> unions) {
if (unions.size() > 2) {
List<Selection> first = unions.remove(0);
return new CallNode( null, "Union", Syntax.Function, generateCrossJoin(first), generateUnion(unions));
} else {
return new CallNode( null, "Union", Syntax.Function, generateCrossJoin(unions.get(0)), generateCrossJoin(unions.get(1)));
}
}

protected static CallNode generateHierarchizeUnion(List<List<Selection>> unions) {
return new CallNode(null, "Hierarchize", Syntax.Function,
generateUnion(unions)
);
}

/**
*
* Algorithm:
* - generate all combinations of dimension groups
* - skip the selection if has a context
* - for all the selections with context, resolve them last
* - union all combinations
*/
private static void generateUnionsRecursively(QueryAxis axis, int dim, List<Selection> curr, List<List<Selection>> unions, List<Selection> selsWithContext, List<List<Selection>> contextUnions) {
for (Selection sel : axis.getDimensions().get(dim).getInclusions()) {

protected static CallNode crossJoin(
QueryDimension dim1,
QueryDimension dim2)
{
return
new CallNode(
null,
"CrossJoin",
Syntax.Function,
getMemberSet(dim1),
getMemberSet(dim2));
if (sel.getSelectionContext() != null && sel.getSelectionContext().size() > 0) {
// selections that have a context are treated differently than the
// rest of the MDX generation
if (!selsWithContext.contains(sel)) {
ArrayList<Selection> sels = new ArrayList<Selection>();
for (int i = 0; i < axis.getDimensions().size(); i++) {
if (dim == i) {
sels.add(sel);
} else {
// return the selections in the correct dimensional order
QueryDimension dimension = axis.getDimensions().get(i);
boolean found = false;
for (Selection selection : sel.getSelectionContext()) {
if (selection.getDimension().equals(dimension.getDimension())) {
sels.add(selection);
found = true;
}
}
if (!found) {
// add the first selection of the dimension
if (dimension.getInclusions().size() > 0) {
sels.add(dimension.getInclusions().get(0));
}
}
}
}
contextUnions.add(sels);
selsWithContext.add(sel);
}
} else {
List<Selection> ncurr = new ArrayList<Selection>();
if (curr != null) {
ncurr.addAll(curr);
}
ncurr.add(sel);
if (dim == axis.getDimensions().size() - 1) {
// last dimension
unions.add(ncurr);
} else {
generateUnionsRecursively(axis, dim + 1, ncurr, unions, selsWithContext, contextUnions);
}
}
}
}

/*
Expand Down Expand Up @@ -131,26 +199,18 @@ private static AxisNode toOlap4j(QueryAxis axis) {
QueryDimension dimension = axis.getDimensions().get(0);
List<ParseTreeNode> members = toOlap4j(dimension);
callNode = generateListSetCall(members);
} else if (numDimensions == 2) {
callNode =
crossJoin(
axis.getDimensions().get(0),
axis.getDimensions().get(1));
} else {
// need a longer crossjoin
// start from the back of the list;
List<QueryDimension> dims = axis.getDimensions();
callNode = getMemberSet(dims.get(dims.size() - 1));
for (int i = dims.size() - 2; i >= 0; i--) {
CallNode memberSet = getMemberSet(dims.get(i));
callNode =
new CallNode(
null,
"CrossJoin",
Syntax.Function,
memberSet,
callNode);
}
// generate union sets of selections in each dimension
List<List<Selection>> unions = new ArrayList<List<Selection>>();
List<Selection> selsWithContext = new ArrayList<Selection>();
List<List<Selection>> contextUnions = new ArrayList<List<Selection>>();
generateUnionsRecursively(axis, 0, null, unions, selsWithContext, contextUnions);
unions.addAll(contextUnions);
if (unions.size() > 1) {
callNode = generateHierarchizeUnion(unions);
} else {
callNode = generateCrossJoin(unions.get(0));
}
}

// We might need to sort the whole axis.
Expand Down
75 changes: 63 additions & 12 deletions src/org/olap4j/query/QueryDimension.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,14 @@ public void clearSelection() {
* @throws OlapException If no member corresponding to the supplied
* name parts could be resolved in the cube.
*/
public void include(String... nameParts) throws OlapException {
this.include(Selection.Operator.MEMBER, nameParts);
public Selection include(String... nameParts) throws OlapException {
return this.include(Selection.Operator.MEMBER, nameParts);
}

public Selection createSelection(String... nameParts) throws OlapException {
return this.createSelection(Selection.Operator.MEMBER, nameParts);
}

/**
* Selects members and includes them in the query.
* <p>This method selects and includes a member along with it's
Expand All @@ -126,7 +130,23 @@ public void include(String... nameParts) throws OlapException {
* @throws OlapException If no member corresponding to the supplied
* name parts could be resolved in the cube.
*/
public void include(
public Selection include(
Selection.Operator operator,
String... nameParts) throws OlapException
{
Member member = this.getQuery().getCube().lookupMember(nameParts);
if (member == null) {
throw new OlapException(
"Unable to find a member with name "
+ Olap4jUtil.stringArrayToString(nameParts));
} else {
return this.include(
operator,
member);
}
}

public Selection createSelection(
Selection.Operator operator,
String... nameParts) throws OlapException
{
Expand All @@ -136,7 +156,7 @@ public void include(
"Unable to find a member with name "
+ Olap4jUtil.stringArrayToString(nameParts));
} else {
this.include(
return this.createSelection(
operator,
member);
}
Expand All @@ -148,8 +168,34 @@ public void include(
* {@link Selection.Operator#MEMBER} selection operator.
* @param member The member to select and include in the query.
*/
public void include(Member member) {
include(Selection.Operator.MEMBER, member);
public Selection include(Member member) {
return include(Selection.Operator.MEMBER, member);
}

public Selection createSelection(Member member) {
return createSelection(Selection.Operator.MEMBER, member);
}

/**
* Selects members and includes them in the query.
* <p>This method selects and includes a member along with it's
* relatives, depending on the supplied {@link Selection.Operator}
* operator.
* @param operator Selection operator that defines what relatives of the
* supplied member name to include along.
* @param member Root member to select and include.
*/
public Selection createSelection(
Selection.Operator operator,
Member member)
{
if (member.getDimension().equals(this.dimension)) {
Selection selection =
query.getSelectionFactory().createMemberSelection(
member, operator);
return selection;
}
return null;
}

/**
Expand All @@ -161,7 +207,7 @@ public void include(Member member) {
* supplied member name to include along.
* @param member Root member to select and include.
*/
public void include(
public Selection include(
Selection.Operator operator,
Member member)
{
Expand All @@ -170,7 +216,9 @@ public void include(
query.getSelectionFactory().createMemberSelection(
member, operator);
this.include(selection);
return selection;
}
return null;
}

/**
Expand Down Expand Up @@ -437,10 +485,11 @@ public void clearSort() {
}

/**
* Returns the current mode of hierarchyzation, or null
* if no hierarchyzation is currently performed.
* @return Either a hierarchyzation mode value or null
* if no hierarchyzation is currently performed.
* Returns the current mode of hierarchization, or null
* if no hierarchization is currently performed.
* This capability is only available when a single dimension is selected on an axis.
* @return Either a hierarchization mode value or null
* if no hierarchization is currently performed.
*/
public HierarchizeMode getHierarchizeMode() {
return hierarchizeMode;
Expand All @@ -451,6 +500,7 @@ public HierarchizeMode getHierarchizeMode() {
* QueryDimension.
* <p>The dimension inclusions will be wrapped in an MDX Hierarchize
* function call.
* This capability is only available when a single dimension is selected on an axis.
* @param hierarchizeMode If parents should be included before or after
* their children. (Equivalent to the POST/PRE MDX literal for the
* Hierarchize() function)
Expand All @@ -461,7 +511,8 @@ public void setHierarchizeMode(HierarchizeMode hierarchizeMode) {
}

/**
* Tells the QueryDimension not to hierarchyze it's included selections.
* Tells the QueryDimension not to hierarchize it's included selections.
* This capability is only available when a single dimension is selected on an axis.
*/
public void clearHierarchizeMode() {
this.hierarchizeMode = null;
Expand Down
13 changes: 13 additions & 0 deletions src/org/olap4j/query/Selection.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
*/
package org.olap4j.query;

import java.util.List;

import org.olap4j.metadata.Dimension;
import org.olap4j.metadata.Member;

Expand Down Expand Up @@ -43,6 +45,17 @@ public interface Selection extends QueryNode {

String getHierarchyName();

/**
* The selection context includes selections from other dimensions that help determine the entire
* context of a selection, so drill down is possible.
* @return list of selections
*/
List<Selection> getSelectionContext();

void addContext(Selection selection);

void removeContext(Selection selection);

String getLevelName();

Operator getOperator();
Expand Down
19 changes: 19 additions & 0 deletions src/org/olap4j/query/SelectionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
*/
package org.olap4j.query;

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

import org.olap4j.metadata.Dimension;
import org.olap4j.metadata.Member;

Expand All @@ -28,6 +31,7 @@ class SelectionImpl extends QueryNodeImpl implements Selection {
protected String memberName;
protected Dimension dimension;
protected Operator operator = Operator.MEMBER;
protected List<Selection> selectionContext;

/**
* Creates a SelectionImpl.
Expand Down Expand Up @@ -107,6 +111,21 @@ public void setOperator(Operator operator) {

void tearDown() {
}

public List<Selection> getSelectionContext() {
return selectionContext;
}

public void addContext(Selection selection) {
if (selectionContext == null) {
selectionContext = new ArrayList<Selection>();
}
selectionContext.add(selection);
}

public void removeContext(Selection selection) {
selectionContext.remove(selection);
}
}

// End SelectionImpl.java
Loading

0 comments on commit a236eb0

Please sign in to comment.