From a4cef5078c7c35547bddf0dbe46c1b8685d9b5a2 Mon Sep 17 00:00:00 2001 From: Luc Boudreau Date: Tue, 4 Aug 2009 16:01:05 +0000 Subject: [PATCH] Adds a sort function to a QueryAxis. Axis can now be sorted by any literal expression. git-svn-id: https://olap4j.svn.sourceforge.net/svnroot/olap4j/trunk@275 c6a108a4-781c-0410-a6c6-c2d559e19af0 --- src/org/olap4j/query/Olap4jNodeConverter.java | 23 +++- src/org/olap4j/query/QueryAxis.java | 123 ++++++++++++++++++ src/org/olap4j/query/QueryDimension.java | 23 +++- testsrc/org/olap4j/OlapTest.java | 102 +++++++++++++-- 4 files changed, 261 insertions(+), 10 deletions(-) diff --git a/src/org/olap4j/query/Olap4jNodeConverter.java b/src/org/olap4j/query/Olap4jNodeConverter.java index 8ace0d0..26478f6 100644 --- a/src/org/olap4j/query/Olap4jNodeConverter.java +++ b/src/org/olap4j/query/Olap4jNodeConverter.java @@ -149,12 +149,33 @@ private static AxisNode toOlap4j(QueryAxis axis) { callNode); } } + + // We might need to sort the whole axis. + ParseTreeNode sortedNode = null; + if (axis.getSortOrder() != null) { + LiteralNode evaluatorNode = + LiteralNode.createSymbol( + null, + axis.getSortIdentifierNodeName()); + sortedNode = + new CallNode( + null, + "Order", + Syntax.Function, + callNode, + evaluatorNode, + LiteralNode.createSymbol( + null, axis.getSortOrder().name())); + } else { + sortedNode = callNode; + } + return new AxisNode( null, axis.isNonEmpty(), axis.getLocation(), new ArrayList(), - callNode); + sortedNode); } private static List toOlap4j(QueryDimension dimension) { diff --git a/src/org/olap4j/query/QueryAxis.java b/src/org/olap4j/query/QueryAxis.java index c87a1aa..01257c9 100644 --- a/src/org/olap4j/query/QueryAxis.java +++ b/src/org/olap4j/query/QueryAxis.java @@ -10,6 +10,9 @@ package org.olap4j.query; import org.olap4j.Axis; +import org.olap4j.OlapException; +import org.olap4j.metadata.Measure; +import org.olap4j.metadata.Member; import java.util.ArrayList; import java.util.Collections; @@ -35,6 +38,8 @@ public class QueryAxis extends QueryNodeImpl { private final Query query; protected Axis location = null; private boolean nonEmpty; + private SortOrder sortOrder = null; + private String sortEvaluationLiteral = null; /** * Creates a QueryAxis. @@ -239,6 +244,124 @@ void tearDown() { this.clearListeners(); this.getDimensions().clear(); } + + /** + * Defines in what order to perform the sort. + */ + public static enum SortOrder { + /** + * Ascending sort order. Members of + * the same hierarchy are still kept together. + */ + ASC, + /** + * Descending sort order. Members of + * the same hierarchy are still kept together. + */ + DESC, + /** + * Sorts in ascending order, but does not + * maintain members of a same hierarchy + * together. This is known as a "break + * hierarchy ascending sort". + */ + BASC, + /** + * Sorts in descending order, but does not + * maintain members of a same hierarchy + * together. This is known as a "break + * hierarchy descending sort". + */ + BDESC + } + + /** + *

Sorts the axis according to the supplied order + * and member unique name. + *

Using this method will try to resolve the supplied name + * parts from the underlying cube and find the corresponding + * member. This member will then be passed as a sort evaluation + * expression. + * @param order The {@link QueryAxis.SortOrder} in which to + * sort the axis. + * @param nameParts The unique name parts of the sort + * evaluation expression. + * @throws OlapException If the supplied member cannot be resolved + * with {@link org.olap4j.metadata.Cube#lookupMember(String...)} + */ + public void sort(SortOrder order, String... nameParts) + throws OlapException + { + assert order != null; + assert nameParts != null; + Member member = query.getCube().lookupMember(nameParts); + if (member != null) { + sort(order, member); + } else { + throw new OlapException("Cannot find member."); + } + } + + /** + *

Sorts the axis according to the supplied order + * and member. + *

This method is most commonly called by passing + * it a {@link Measure}. + * @param order The {@link QueryAxis.SortOrder} in which to + * sort the axis. + * @param member The member that will be used as a sort + * evaluation expression. + */ + public void sort(SortOrder order, Member member) { + assert order != null; + assert member != null; + sort(order, member.getUniqueName()); + } + + /** + *

Sorts the axis according to the supplied order + * and evaluation expression. + *

The string value passed as the sortIdentifierNodeName + * parameter willb e used literally as a sort evaluator. + * @param order The {@link QueryAxis.SortOrder} in which to + * sort the axis. + * @param sortEvaluationLiteral The literal expression that + * will be used to sort against. + */ + public void sort(SortOrder order, String sortEvaluationLiteral) { + assert order != null; + assert sortEvaluationLiteral != null; + this.sortOrder = order; + this.sortEvaluationLiteral = sortEvaluationLiteral; + } + + /** + * Clears the sort parameters from this axis. + */ + public void clearSort() { + this.sortEvaluationLiteral = null; + this.sortOrder = null; + } + + /** + * Returns the current sort order in which this + * axis will be sorted. Might return null of none + * is currently specified. + * @return The {@link SortOrder} + */ + public SortOrder getSortOrder() { + return this.sortOrder; + } + + /** + * Returns the current sort evaluation expression, + * or null if none are currently defined. + * @return The string literal that will be used in the + * MDX Order() function. + */ + public String getSortIdentifierNodeName() { + return sortEvaluationLiteral; + } } // End QueryAxis.java diff --git a/src/org/olap4j/query/QueryDimension.java b/src/org/olap4j/query/QueryDimension.java index bcc4c05..c083fed 100644 --- a/src/org/olap4j/query/QueryDimension.java +++ b/src/org/olap4j/query/QueryDimension.java @@ -307,14 +307,31 @@ public void setDimension(Dimension dimension) { this.dimension = dimension; } + /** + * Sorts the dimension members by name in the + * order supplied as a parameter. + * @param order The {@link SortOrder} to use. + */ public void setSortOrder(SortOrder order) { this.sortOrder = order; } + /** + * Returns the current order in which the + * dimension members are sorted. + * @return A value of {@link SortOrder} + */ public SortOrder getSortOrder() { return this.sortOrder; } + /** + * Clears the current sorting settings. + */ + public void clearSort() { + this.sortOrder = null; + } + public HierarchizeMode getHierarchizeMode() { return hierarchizeMode; } @@ -322,7 +339,11 @@ public HierarchizeMode getHierarchizeMode() { /** * Triggers the hierarchization of the included members within this * QueryDimension. - * @param hierarchizeMode Whether or not to include the POST litteral + *

The dimension inclusions will be wrapped in an MDX Hierarchize + * function call. + * @param hierarchizeMode If parents should be included before or after + * their children. (Equivalent to the POST/PRE MDX literal for the + * Hierarchize() function) * inside the Hierarchize() MDX function call. */ public void setHierarchizeMode(HierarchizeMode hierarchizeMode) { diff --git a/testsrc/org/olap4j/OlapTest.java b/testsrc/org/olap4j/OlapTest.java index cebf65c..1cbac50 100644 --- a/testsrc/org/olap4j/OlapTest.java +++ b/testsrc/org/olap4j/OlapTest.java @@ -472,8 +472,12 @@ public void testSortDimension() { QueryDimension measuresDimension = query.getDimension("Measures"); measuresDimension.include("Measures", "Store Sales"); + QueryDimension timeDimension = query.getDimension("Time"); + timeDimension.include("Time", "Year", "1997", "Q3", "7"); + query.getAxis(Axis.ROWS).addDimension(productDimension); query.getAxis(Axis.COLUMNS).addDimension(measuresDimension); + query.getAxis(Axis.FILTER).addDimension(timeDimension); query.validate(); @@ -490,7 +494,8 @@ public void testSortDimension() { "SELECT\n" + "{[Measures].[Store Sales]} ON COLUMNS,\n" + "{{[Product].[All Products].[Drink], [Product].[All Products].[Drink].Children}} ON ROWS\n" - + "FROM [Sales]", + + "FROM [Sales]\n" + + "WHERE ([Time].[1997].[Q3].[7])", mdxString); // Sort the products in ascending order. @@ -502,14 +507,15 @@ public void testSortDimension() { "SELECT\n" + "{[Measures].[Store Sales]} ON COLUMNS,\n" + "{Order({{[Product].[All Products].[Drink], [Product].[All Products].[Drink].Children}}, [Product].CurrentMember.Name, DESC)} ON ROWS\n" - + "FROM [Sales]", + + "FROM [Sales]\n" + + "WHERE ([Time].[1997].[Q3].[7])", sortedMdxString); CellSet results = query.execute(); String s = TestContext.toString(results); TestContext.assertEqualsVerbose( "Axis #0:\n" - + "{[Store].[All Stores], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Time].[1997], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n" + + "{[Store].[All Stores], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Time].[1997].[Q3].[7], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" @@ -517,10 +523,10 @@ public void testSortDimension() { + "{[Product].[All Products].[Drink].[Dairy]}\n" + "{[Product].[All Products].[Drink].[Beverages]}\n" + "{[Product].[All Products].[Drink].[Alcoholic Beverages]}\n" - + "Row #0: 48,836.21\n" - + "Row #1: 7,058.60\n" - + "Row #2: 27,748.53\n" - + "Row #3: 14,029.08\n", + + "Row #0: 4,409.58\n" + + "Row #1: 629.69\n" + + "Row #2: 2,477.02\n" + + "Row #3: 1,302.87\n", s); } catch (Exception e) { e.printStackTrace(); @@ -528,6 +534,87 @@ public void testSortDimension() { } } + public void testSortAxis() { + try { + Cube cube = getFoodmartCube("Sales"); + if (cube == null) { + fail("Could not find Sales cube"); + } + Query query = new Query("my query", cube); + + // create selections + + QueryDimension productDimension = query.getDimension("Product"); + productDimension.include( + Selection.Operator.INCLUDE_CHILDREN, "Product", "Drink"); + + QueryDimension measuresDimension = query.getDimension("Measures"); + measuresDimension.include("Measures", "Store Sales"); + + QueryDimension timeDimension = query.getDimension("Time"); + timeDimension.include("Time", "Year", "1997", "Q3", "7"); + + query.getAxis(Axis.ROWS).addDimension(productDimension); + query.getAxis(Axis.COLUMNS).addDimension(measuresDimension); + query.getAxis(Axis.FILTER).addDimension(timeDimension); + + query.validate(); + + assertEquals( + Axis.ROWS, + productDimension.getAxis().getLocation()); + assertEquals( + Axis.COLUMNS, + measuresDimension.getAxis().getLocation()); + + SelectNode mdx = query.getSelect(); + String mdxString = mdx.toString(); + TestContext.assertEqualsVerbose( + "SELECT\n" + + "{[Measures].[Store Sales]} ON COLUMNS,\n" + + "{{[Product].[All Products].[Drink], [Product].[All Products].[Drink].Children}} ON ROWS\n" + + "FROM [Sales]\n" + + "WHERE ([Time].[1997].[Q3].[7])", + mdxString); + + // Sort the rows in ascending order. + query.getAxis(Axis.ROWS).sort( + org.olap4j.query.QueryAxis.SortOrder.BASC, + "Measures", + "Store Sales"); + + SelectNode sortedMdx = query.getSelect(); + String sortedMdxString = sortedMdx.toString(); + TestContext.assertEqualsVerbose( + "SELECT\n" + + "{[Measures].[Store Sales]} ON COLUMNS,\n" + + "Order({{[Product].[All Products].[Drink], [Product].[All Products].[Drink].Children}}, [Measures].[Store Sales], BASC) ON ROWS\n" + + "FROM [Sales]\n" + + "WHERE ([Time].[1997].[Q3].[7])", + sortedMdxString); + + CellSet results = query.execute(); + String s = TestContext.toString(results); + TestContext.assertEqualsVerbose( + "Axis #0:\n" + + "{[Store].[All Stores], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Time].[1997].[Q3].[7], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n" + + "Axis #1:\n" + + "{[Measures].[Store Sales]}\n" + + "Axis #2:\n" + + "{[Product].[All Products].[Drink].[Dairy]}\n" + + "{[Product].[All Products].[Drink].[Alcoholic Beverages]}\n" + + "{[Product].[All Products].[Drink].[Beverages]}\n" + + "{[Product].[All Products].[Drink]}\n" + + "Row #0: 629.69\n" + + "Row #1: 1,302.87\n" + + "Row #2: 2,477.02\n" + + "Row #3: 4,409.58\n", + s); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } public void testDimensionsOrder() { try { @@ -548,7 +635,6 @@ public void testDimensionsOrder() { Selection.Operator.INCLUDE_CHILDREN, "Store", "USA"); QueryDimension timeDimension = query.getDimension("Time"); - timeDimension.include(Selection.Operator.CHILDREN, "Time", "1997"); QueryDimension measuresDimension = query.getDimension("Measures");