diff --git a/src/org/olap4j/Axis.java b/src/org/olap4j/Axis.java index a0e31e7..5de2906 100644 --- a/src/org/olap4j/Axis.java +++ b/src/org/olap4j/Axis.java @@ -117,7 +117,10 @@ public interface Axis { * Enumeration of standard, named axes descriptors. */ public enum Standard implements Axis { - /** Filter axis, also known as the slicer axis. */ + /** + * Filter axis, also known as the slicer axis, and represented by the + * WHERE clause of an MDX query. + */ FILTER, /** COLUMNS axis, also known as X axis and AXIS(0). */ diff --git a/src/org/olap4j/mdx/AxisNode.java b/src/org/olap4j/mdx/AxisNode.java index c01a07e..4d0623d 100644 --- a/src/org/olap4j/mdx/AxisNode.java +++ b/src/org/olap4j/mdx/AxisNode.java @@ -10,7 +10,6 @@ package org.olap4j.mdx; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.List; import java.util.Collections; @@ -38,7 +37,7 @@ public class AxisNode implements ParseTreeNode { * @param region Region of source code * @param nonEmpty Whether to filter out members of this axis whose cells * are all empty - * @param axisDef Which axis (ROWS, COLUMNS, etc.) + * @param axis Which axis (ROWS, COLUMNS, etc.) * @param dimensionProperties List of dimension properties; if null, * empty list is assumed * @param expression Expression to populate the axis @@ -46,14 +45,17 @@ public class AxisNode implements ParseTreeNode { public AxisNode( ParseRegion region, boolean nonEmpty, - Axis axisDef, + Axis axis, List dimensionProperties, ParseTreeNode expression) { this.region = region; this.nonEmpty = nonEmpty; this.expression = expression; - this.axis = axisDef; + this.axis = axis; + if (axis == null) { + throw new IllegalArgumentException("Axis type must not be null"); + } if (dimensionProperties == null) { dimensionProperties = Collections.emptyList(); } diff --git a/src/org/olap4j/mdx/SelectNode.java b/src/org/olap4j/mdx/SelectNode.java index 49296eb..6515abc 100644 --- a/src/org/olap4j/mdx/SelectNode.java +++ b/src/org/olap4j/mdx/SelectNode.java @@ -10,11 +10,11 @@ package org.olap4j.mdx; import org.olap4j.type.Type; +import org.olap4j.Axis; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * Parse tree model for an MDX SELECT statement. @@ -54,6 +54,19 @@ public SelectNode( this.withList = withList; this.axisList = axisList; this.from = from; + if (filterAxis == null) { + filterAxis = + new AxisNode( + null, + false, + Axis.FILTER, + Collections.emptyList(), + null); + } + if (filterAxis.getAxis() != Axis.FILTER) { + throw new IllegalArgumentException( + "Filter axis must have type FILTER"); + } this.filterAxis = filterAxis; this.cellPropertyList = cellPropertyList; } @@ -117,7 +130,7 @@ public void unparse(ParseTreeWriter writer) { pw.println(); pw.print("FROM "); from.unparse(writer); - if (filterAxis != null) { + if (filterAxis.getExpression() != null) { pw.println(); pw.print("WHERE "); filterAxis.unparse(writer); @@ -170,8 +183,15 @@ public List getAxisList() { } /** - * Returns the filter axis defined by the WHERE clause of this SelectNode, - * or null if there is no filter axis. + * Returns the filter axis defined by the WHERE clause of this SelectNode. + * + *

Never returns {@code null}. If there is no WHERE clause, returns an + * AxisNode for which {@link org.olap4j.mdx.AxisNode#getExpression()} + * returns null. + * + *

You can modify the filter expression by calling + * {@link org.olap4j.mdx.AxisNode#getExpression()} on the filter AxisNode; + * {@code null} means that there is no filter axis. * * @return filter axis */ @@ -220,7 +240,7 @@ public SelectNode deepCopy() { MdxUtil.deepCopyList(withList), MdxUtil.deepCopyList(axisList), this.from != null ? this.from.deepCopy() : null, - this.filterAxis != null ? this.filterAxis.deepCopy() : null, + this.filterAxis.deepCopy(), MdxUtil.deepCopyList(cellPropertyList)); } } diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java index deeee65..5779621 100644 --- a/testsrc/org/olap4j/ConnectionTest.java +++ b/testsrc/org/olap4j/ConnectionTest.java @@ -1100,12 +1100,27 @@ public void testParsing() throws SQLException { fail("expected exception, got " + select); } catch (Exception e) { assertTrue( - TestContext.getStackTrace(e).indexOf("Duplicate axis name 'COLUMNS'.") - >= 0); + TestContext.getStackTrace(e) + .indexOf("Duplicate axis name 'COLUMNS'.") >= 0); } } private void checkUnparsedMdx(SelectNode select) { + checkUnparsedMdx( + select, + "WITH\n" + + "MEMBER [Measures].[Foo] AS '[Measures].[Bar]', FORMAT_STRING = \"xxx\"\n" + + "SELECT\n" + + "{[Gender]} ON COLUMNS,\n" + + "{[Store].Children} ON ROWS\n" + + "FROM [sales]\n" + + "WHERE [Time].[1997].[Q4]"); + } + + private void checkUnparsedMdx( + SelectNode select, + String expectedMdx) + { StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw); ParseTreeWriter parseTreeWriter = new ParseTreeWriter(pw); @@ -1113,14 +1128,7 @@ private void checkUnparsedMdx(SelectNode select) { pw.flush(); String mdx = sw.toString(); TestContext.assertEqualsVerbose( - TestContext.fold("WITH\n" + - "MEMBER [Measures].[Foo] AS '[Measures].[Bar]', FORMAT_STRING = \"xxx\"\n" + - "SELECT\n" + - "{[Gender]} ON COLUMNS,\n" + - "{[Store].Children} ON ROWS\n" + - "FROM [sales]\n" + - "WHERE [Time].[1997].[Q4]"), - mdx); + TestContext.fold(expectedMdx), mdx); } /** @@ -1197,6 +1205,78 @@ public void testUnparsing() { checkUnparsedMdx(select); } + public void testBuildParseTree() { + // It is an error to create a select node with a filter axis whose type + // is not filter + try { + SelectNode select = new SelectNode( + null, + new ArrayList(), + new ArrayList(), + new IdentifierNode(new IdentifierNode.NameSegment("sales")), + new AxisNode( + null, + false, + Axis.COLUMNS, + new ArrayList(), + null), + new ArrayList()); + fail("expected error, got " + select); + } catch (IllegalArgumentException e) { + TestContext.checkThrowable(e, "Filter axis must have type FILTER"); + } + + // Create a select node with empty filter axis. It is populated with + // a filter axis whose expression is null. + SelectNode select = new SelectNode( + null, + new ArrayList(), + new ArrayList(), + new IdentifierNode(new IdentifierNode.NameSegment("sales")), + new AxisNode( + null, + false, + Axis.FILTER, + new ArrayList(), + null), + new ArrayList()); + final AxisNode filterAxis = select.getFilterAxis(); + assertNotNull(filterAxis); + assertNull(filterAxis.getExpression()); + assertEquals(Axis.FILTER, filterAxis.getAxis()); + + // Parses to an expression with no WHERE clause + checkUnparsedMdx( + select, + "SELECT\n" + + "FROM [sales]"); + + // Set the filter, see if it takes. + select.getFilterAxis().setExpression( + new CallNode( + null, + "()", + Syntax.Parentheses, + new IdentifierNode( + new IdentifierNode.NameSegment("Measures"), + new IdentifierNode.NameSegment("Store Sales")), + new IdentifierNode( + new IdentifierNode.NameSegment("Gender"), + new IdentifierNode.NameSegment("M")))); + checkUnparsedMdx( + select, + "SELECT\n" + + "FROM [sales]\n" + + "WHERE ([Measures].[Store Sales], [Gender].[M])"); + + // Set it back to null + select.getFilterAxis().setExpression(null); + checkUnparsedMdx( + select, + "SELECT\n" + + "FROM [sales]"); + } + /** * Tests the {@link Cube#lookupMember(String[])} method. */ diff --git a/testsrc/org/olap4j/test/TestContext.java b/testsrc/org/olap4j/test/TestContext.java index f0cde14..330d7f7 100644 --- a/testsrc/org/olap4j/test/TestContext.java +++ b/testsrc/org/olap4j/test/TestContext.java @@ -22,6 +22,7 @@ import org.apache.commons.dbcp.*; import junit.framework.ComparisonFailure; +import junit.framework.Assert; /** * Context for olap4j tests. @@ -398,6 +399,25 @@ public static String getStackTrace(Throwable e) { return sw.toString(); } + /** + * Checks that an exception is not null and the stack trace contains a + * given string. Fails otherwise. + * + * @param throwable Stack trace + * @param pattern Seek string + */ + public static void checkThrowable(Throwable throwable, String pattern) { + if (throwable == null) { + Assert.fail("query did not yield an exception"); + } + String stackTrace = getStackTrace(throwable); + if (stackTrace.indexOf(pattern) < 0) { + Assert.fail( + "error does not match pattern '" + pattern + + "'; error is [" + stackTrace + "]"); + } + } + /** * Returns this context's tester. *