diff --git a/src/org/olap4j/mdx/AxisNode.java b/src/org/olap4j/mdx/AxisNode.java index 515a884..a5fe613 100644 --- a/src/org/olap4j/mdx/AxisNode.java +++ b/src/org/olap4j/mdx/AxisNode.java @@ -3,13 +3,14 @@ // This software is subject to the terms of the Common Public License // Agreement, available at the following URL: // http://www.opensource.org/licenses/cpl.html. -// Copyright (C) 2007-2007 Julian Hyde +// Copyright (C) 2007-2008 Julian Hyde // All Rights Reserved. // You must accept the terms of that agreement to use this software. */ package org.olap4j.mdx; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; import java.util.Collections; @@ -158,6 +159,15 @@ public Type getType() { // Try AxisNode.getExpression().getType() instead. return null; } + + public AxisNode deepCopy() { + return new AxisNode( + this.region, + this.nonEmpty, + this.axis, + MdxUtil.deepCopyList(dimensionProperties), + this.expression.deepCopy()); + } } // End AxisNode.java diff --git a/src/org/olap4j/mdx/CallNode.java b/src/org/olap4j/mdx/CallNode.java index 56663db..b1d54b4 100644 --- a/src/org/olap4j/mdx/CallNode.java +++ b/src/org/olap4j/mdx/CallNode.java @@ -3,7 +3,7 @@ // This software is subject to the terms of the Common Public License // Agreement, available at the following URL: // http://www.opensource.org/licenses/cpl.html. -// Copyright (C) 2007-2007 Julian Hyde +// Copyright (C) 2007-2008 Julian Hyde // All Rights Reserved. // You must accept the terms of that agreement to use this software. */ @@ -49,7 +49,7 @@ public class CallNode implements ParseTreeNode { *
The syntax
argument determines whether this is a prefix,
* infix or postfix operator, a function call, and so forth.
*
- *
The list of arguuments args
must be specified, even if
+ *
The list of arguments args
must be specified, even if
* there are zero arguments, and each argument must be not null.
*
*
The type is initially null, but can be set using {@link #setType} @@ -99,7 +99,7 @@ public CallNode( *
The syntax
argument determines whether this is a prefix,
* infix or postfix operator, a function call, and so forth.
*
- *
The list of arguuments args
must be specified, even if
+ *
The list of arguments For example, {"Store", "USA",
* "California"} becomes "[Store].[USA].[California]".
- *
+ *
* @param ids List of segments
* @return Segments as quoted string
*/
diff --git a/src/org/olap4j/mdx/LevelNode.java b/src/org/olap4j/mdx/LevelNode.java
index 0f43009..4a87e17 100644
--- a/src/org/olap4j/mdx/LevelNode.java
+++ b/src/org/olap4j/mdx/LevelNode.java
@@ -3,7 +3,7 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2007 Julian Hyde
+// Copyright (C) 2007-2008 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -70,6 +70,12 @@ public void unparse(ParseTreeWriter writer) {
public String toString() {
return level.getUniqueName();
}
+
+ public LevelNode deepCopy() {
+ // LevelNode is immutable
+ return this;
+ }
+
}
// End LevelNode.java
diff --git a/src/org/olap4j/mdx/LiteralNode.java b/src/org/olap4j/mdx/LiteralNode.java
index 909b885..43970e1 100644
--- a/src/org/olap4j/mdx/LiteralNode.java
+++ b/src/org/olap4j/mdx/LiteralNode.java
@@ -3,7 +3,7 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2007 Julian Hyde
+// Copyright (C) 2007-2008 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -20,6 +20,8 @@
* A LiteralNode is immutable.
+ *
* @version $Id$
* @author jhyde
*/
@@ -174,6 +176,12 @@ public void unparse(ParseTreeWriter writer) {
throw new AssertionError("unexpected literal type " + type);
}
}
+
+ public LiteralNode deepCopy() {
+ // No need to copy: literal nodes are immutable.
+ return this;
+ }
+
}
// End LiteralNode.java
diff --git a/src/org/olap4j/mdx/MdxUtil.java b/src/org/olap4j/mdx/MdxUtil.java
index b718f67..eff4cdc 100644
--- a/src/org/olap4j/mdx/MdxUtil.java
+++ b/src/org/olap4j/mdx/MdxUtil.java
@@ -3,13 +3,14 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2007 Julian Hyde
+// Copyright (C) 2007-2008 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
package org.olap4j.mdx;
import java.util.regex.Pattern;
+import java.util.*;
import java.io.StringWriter;
import java.io.PrintWriter;
@@ -28,6 +29,9 @@ class MdxUtil {
/**
* Converts a string into a double-quoted string.
+ *
+ * @param val String
+ * @return String enclosed in double-quotes
*/
static String quoteForMdx(String val) {
StringBuilder buf = new StringBuilder(val.length() + 20);
@@ -48,14 +52,17 @@ static String toString(ParseTreeNode node) {
/**
* Encodes string for MDX (escapes ] as ]] inside a name).
+ *
+ * @param st String
+ * @return String escaped for inclusion in an MDX identifier
*/
static String mdxEncodeString(String st) {
StringBuilder retString = new StringBuilder(st.length() + 20);
for (int i = 0; i < st.length(); i++) {
char c = st.charAt(i);
if ((c == ']') &&
- ((i+1) < st.length()) &&
- (st.charAt(i+1) != '.')) {
+ ((i + 1) < st.length()) &&
+ (st.charAt(i + 1) != '.')) {
retString.append(']'); //escaping character
}
@@ -63,6 +70,25 @@ static String mdxEncodeString(String st) {
}
return retString.toString();
}
+
+ /**
+ * Creates a deep copy of a list.
+ *
+ * @param list List to be copied
+ * @return Copy of list, with each element deep copied
+ */
+ @SuppressWarnings({"unchecked"})
+ static Note: implementing classes can return the concrete type instead
+ * of ParseTreeNode (using Java 1.5 covariant return types)
+ *
+ * @return The deep copy of this ParseTreeNode
+ */
+ ParseTreeNode deepCopy();
+
}
// End ParseTreeNode.java
diff --git a/src/org/olap4j/mdx/PropertyValueNode.java b/src/org/olap4j/mdx/PropertyValueNode.java
index 63245b6..d98a81f 100644
--- a/src/org/olap4j/mdx/PropertyValueNode.java
+++ b/src/org/olap4j/mdx/PropertyValueNode.java
@@ -3,7 +3,7 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2007 Julian Hyde
+// Copyright (C) 2007-2008 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -81,6 +81,13 @@ public void unparse(ParseTreeWriter writer) {
writer.getPrintWriter().print(name + " = ");
expression.unparse(writer);
}
+
+ public PropertyValueNode deepCopy() {
+ return new PropertyValueNode(
+ this.region,
+ this.name,
+ this.expression.deepCopy());
+ }
}
// End PropertyValueNode.java
diff --git a/src/org/olap4j/mdx/SelectNode.java b/src/org/olap4j/mdx/SelectNode.java
index d9a302a..3a7bbe4 100644
--- a/src/org/olap4j/mdx/SelectNode.java
+++ b/src/org/olap4j/mdx/SelectNode.java
@@ -3,7 +3,7 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2007 Julian Hyde
+// Copyright (C) 2007-2008 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -202,6 +202,27 @@ public ParseTreeNode getFrom() {
public void setFrom(ParseTreeNode fromNode) {
this.from = fromNode;
}
+
+ /**
+ * Returns a list of cell properties in this SelectNode.
+ *
+ * The returned list is mutable.
+ *
+ * @return list of cell properties
+ */
+ public List Description: Adds the children of a member at a specific position on an
+ * axis. The member to drill is identified from a CellSet with the axis,
+ * positionOrdinalInAxis and memberOrdinalInPosition arguments. The drilled
+ * member will still be present on the axis, in addition to its children. It
+ * is recommended to apply a Hierarchize transform to the same axis of the
+ * resulting query, in order to have members in correct hierarchical order.
+ *
+ * Example of use: the user clicks on a member in a crosstab axis, in order
+ * to see its children in addition to the member itself.
+ *
+ * Applicability: this transform is applicable only to members in a query
+ * that are drillable, i.e. non-leaf members. The CellSet resulting from the
+ * execution of the initial MDX query must also be available.
+ *
+ * @author etdub
+ * @author jhyde
+ * @version $Id: $
+ * @since Jul 30, 2008
+ */
+public class DrillDownOnPositionTransform extends AxisTransform {
+
+ // private final int positionOrdinalInAxis;
+ // private final int memberOrdinalInPosition;
+ // private final CellSet cellSet;
+
+ private final Position positionToDrill;
+ private final Member memberToDrill;
+ private final List Description: Replaces a member at a specific position on an axis by its
+ * children. The member to drill is identified from a CellSet with the axis,
+ * positionOrdinalInAxis and memberOrdinalInPosition arguments.
+ *
+ * Example of use: the user clicks on a member in a crosstab axis, in order
+ * to see its children.
+ *
+ * Applicability: this transform is applicable only to members in a query
+ * that are drillable, i.e. non-leaf members. The CellSet resulting from the
+ * execution of the initial MDX query must also be available.
+ *
+ * @author etdub
+ * @version $Id: $
+ * @since Jul 30, 2008
+ */
+public class DrillReplaceTransform extends AxisTransform {
+
+ // private final int positionOrdinalInAxis;
+ // private final int memberOrdinalInPosition;
+ // private final CellSet cellSet;
+
+ // private final Position positionToDrill;
+ private final Member memberToDrill;
+ // private final List Intentionally package-protected; this class is not to be used outside
+ * this package.
+ *
+ * @author etdub
+ * @version $Id: $
+ * @since Aug 7, 2008
+ */
+class MdxHelper {
+
+ public static MemberNode makeMemberNode(Member m) {
+ return new MemberNode(null, m);
+ }
+
+ private static CallNode _makePropCallNode(ParseTreeNode node,
+ String funcName) {
+ List General interface for transforming an MDX query to another one,
+ * according to behavior and parameters encapsulated in implementing
+ * classes
+ *
+ * @author etdub
+ * @author jhyde
+ * @version $Id: $
+ * @since Jul 28, 2008
+ */
+public interface MdxQueryTransform {
+ String getName();
+ String getDescription();
+ SelectNode apply(SelectNode sn);
+}
+
+// End MdxQueryTransform.java
diff --git a/src/org/olap4j/transform/Quax.java b/src/org/olap4j/transform/Quax.java
new file mode 100644
index 0000000..e6ed065
--- /dev/null
+++ b/src/org/olap4j/transform/Quax.java
@@ -0,0 +1,50 @@
+/*
+// $Id: $
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2008-2008 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+*/
+package org.olap4j.transform;
+
+import org.olap4j.CellSetAxis;
+import org.olap4j.Position;
+import org.olap4j.metadata.Member;
+
+/**
+ * Representation of member expressions on a query axis, derived from
+ * CellSetAxis objects.
+ *
+ * Quaxes are used by MDX axis query transforms, to construct and use
+ * an internal tree-like representation of positions and members from the
+ * result CellSetAxis objects of a previous MDX query. This is needed
+ * for OLAP navigation operators like drill-down on a position.
+ *
+ * Inspired from the JPivot Quax class.
+ *
+ * NOTE: not exactly sure how to implement this, to be completed...
+ *
+ * @author etdub
+ * @version $Id: $
+ * @since Aug 7, 2008
+ */
+public class Quax {
+
+ private final CellSetAxis cellSetAxis;
+
+ private TreeNode Description: Replaces a member at a specific position on an axis by all
+ * the members of its parent's level. The member to roll-up is identified from
+ * a CellSet with the axis, positionOrdinalInAxis and memberOrdinalInPosition
+ * arguments.
+ *
+ * Example of use: the user clicks on a member in a crosstab axis, in order
+ * to roll up to the members of the upper level.
+ *
+ * Applicability: this transform is applicable only to members in a query
+ * that are have a parent. (Note: how would this work in parent-child
+ * hierarchies?)
+ *
+ * @author etdub
+ * @version $Id: $
+ * @since Aug 4, 2008
+ */
+public class RollUpLevelTransform extends AxisTransform {
+
+ // private final int positionOrdinalInAxis;
+ // private final int memberOrdinalInPosition;
+ // private final CellSet cellSet;
+
+ // private final Position positionToDrill;
+ private final Member memberToDrill;
+ // private final List This class is intentionally package-protected. It is NOT part of the
+ * public olap4j API.
+ *
+ * @author etdub
+ * @version $Id: $
+ * @since Aug 7, 2008
+ */
+class TransformUtil {
+
+ public static CellSetAxis getCellSetAxisFromCellSet(
+ Axis axis,
+ CellSet cellSet)
+ {
+ for (CellSetAxis a : cellSet.getAxes()) {
+ if (a.getAxisOrdinal() == axis) {
+ return a;
+ }
+ }
+
+ // axis not found
+ throw new IndexOutOfBoundsException();
+ }
+
+ public static Position getPositionFromCellSet(
+ Axis axis,
+ int positionOrdinalInAxis,
+ CellSet cellSet)
+ {
+ CellSetAxis a = getCellSetAxisFromCellSet(axis, cellSet);
+
+ return a.getPositions().get(positionOrdinalInAxis);
+ }
+
+ public static Member getMemberFromCellSet(
+ Axis axis,
+ int positionOrdinalInAxis,
+ int memberOrdinalInPosition,
+ CellSet cellSet)
+ {
+ Position p =
+ getPositionFromCellSet(
+ axis, positionOrdinalInAxis, cellSet);
+ return p.getMembers().get(memberOrdinalInPosition);
+ }
+
+ public static List REVIEW: Should this class be in the public olap4j API? (jhyde, 2008/8/14)
+ *
+ * @author etdub
+ * @version $Id: $
+ * @since Aug 7, 2008
+ */
+class TreeNode The list is mutable but shouldn't be modified by callers
+ * (use the add and remove methods instead).
+ * @return the list of children
+ */
+ public List REVIEW: Should this class be in the public olap4j API? (jhyde, 2008/8/14)
+ *
+ * @author etdub
+ * @version $Id: $
+ * @since Aug 7, 2008
+ */
+interface TreeNodeCallback NOTE: This package is experimental. Classes may be renamed or removed in a future release of olap4j.
+
+
+
diff --git a/src/org/olap4j/type/Type.java b/src/org/olap4j/type/Type.java
index 4c70edc..47b5135 100644
--- a/src/org/olap4j/type/Type.java
+++ b/src/org/olap4j/type/Type.java
@@ -16,6 +16,8 @@
/**
* Type of an MDX expression.
*
+ * All type objects are immutable.
+ *
* @author jhyde
* @since Feb 17, 2005
* @version $Id$
diff --git a/testsrc/org/olap4j/transform/TransformTest.java b/testsrc/org/olap4j/transform/TransformTest.java
new file mode 100644
index 0000000..5cec9f9
--- /dev/null
+++ b/testsrc/org/olap4j/transform/TransformTest.java
@@ -0,0 +1,212 @@
+/*
+// $Id: $
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2008-2008 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+ */
+package org.olap4j.transform;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.olap4j.Axis;
+import org.olap4j.CellSet;
+import org.olap4j.OlapConnection;
+import org.olap4j.OlapStatement;
+import org.olap4j.mdx.SelectNode;
+import org.olap4j.mdx.parser.MdxParser;
+import org.olap4j.test.TestContext;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for org.olap4j.transform package.
+ *
+ * @author etdub
+ * @author jhyde
+ * @version $Id: $
+ * @since Jul 28, 2008
+ */
+public class TransformTest extends TestCase {
+ final TestContext.Tester tester = TestContext.instance().getTester();
+ private Connection connection = null;
+
+ public TransformTest() {
+ super();
+ }
+
+ /**
+ * Simple strategy to prevent connection leaks: each test that needs a
+ * connection assigns it to this field, and {@link #tearDown()} closes it
+ * if it is not already closed.
+ */
+ protected void tearDown() throws Exception {
+ // Simple strategy to prevent connection leaks
+ if (connection != null
+ && !connection.isClosed())
+ {
+ connection.close();
+ connection = null;
+ }
+ }
+
+ protected OlapConnection getConnection() throws SQLException {
+ if (connection == null) {
+ connection = tester.createConnection();
+ }
+ return tester.getWrapper().unwrap(connection, OlapConnection.class);
+ }
+
+ protected OlapStatement getStatement() throws SQLException {
+ return getConnection().createStatement();
+ }
+
+ /**
+ * Asserts the functionality of a transformation
+ *
+ * @param initialMdx initial MDX query to be transformed
+ * @param expectedMdx expected MDX query after applying transform; will
+ * be compared against the transformed query
+ * @param trans the transformation to apply to the initial query
+ *
+ * @throws java.sql.SQLException on error
+ */
+ public void assertTransformTo(
+ String initialMdx,
+ String expectedMdx,
+ MdxQueryTransform trans) throws SQLException
+ {
+ OlapConnection olapConnection = getConnection();
+
+ MdxParser mdxParser =
+ olapConnection.getParserFactory().createMdxParser(olapConnection);
+
+ SelectNode before = mdxParser.parseSelect(initialMdx);
+ SelectNode after = trans.apply(before);
+
+ // we also parse the expected MDX in order to normalize it
+ // (eliminate any whitespace / capitalization differences)
+ // note: CallNodes are not aware of function names, and as such
+ // will be compared in a case-sensitive manner
+ // (i.e. [SomeMember].Children vs [SomeMember].children are not
+ // equal, even if they are equivalent in MDX)
+ SelectNode expected = mdxParser.parseSelect(expectedMdx);
+
+ // TODO: consider adding .equals() method to ParseTreeNode instead
+ // of comparing strings (we could ignore case when comparing
+ // function names in CallNodes ...)
+ assertEquals(expected.toString(), after.toString());
+ }
+
+ /**
+ * Unit test for DrillReplaceTransform.
+ *
+ * @throws java.sql.SQLException on error
+ */
+ public void testDrillReplaceTransform() throws SQLException {
+ final String initialMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products]} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ final String expectedMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products].Children} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ CellSet cellSet = getStatement().executeOlapQuery(initialMdx);
+
+ MdxQueryTransform transform =
+ StandardTransformLibrary.createDrillReplaceTransform(
+ Axis.ROWS,
+ 0, // position ordinal in axis
+ 0, // member ordinal in position
+ cellSet);
+
+ assertTransformTo(initialMdx, expectedMdx, transform);
+ }
+
+ /**
+ * Unit test for RollUpLevelTransform.
+ *
+ * @throws java.sql.SQLException on error
+ */
+ public void testRollUpLevelTransform() throws SQLException {
+ final String initialMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products].[Food].[Deli]} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ final String expectedMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products].[Food].[Deli].Parent.Level.Members} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ CellSet cellSet = getStatement().executeOlapQuery(initialMdx);
+
+ MdxQueryTransform transform =
+ StandardTransformLibrary.createRollUpLevelTransform(
+ Axis.ROWS,
+ 0, // position ordinal in axis
+ 0, // member ordinal in position
+ cellSet);
+
+ assertTransformTo(initialMdx, expectedMdx, transform);
+ }
+
+ /**
+ * Unit test for DrillDownOnPositionTransform.
+ *
+ * @throws java.sql.SQLException on error
+ */
+ public void _testDrillDownOnPositionTransform() throws SQLException {
+
+ // TODO: rewrite the initial and expected MDX once this transform
+ // is written.
+ // Will fail for now.
+
+ final String initialMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products]} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ final String expectedMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products].Children} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ CellSet cellSet = getStatement().executeOlapQuery(initialMdx);
+
+ MdxQueryTransform transform =
+ StandardTransformLibrary.createDrillDownOnPositionTransform(
+ Axis.ROWS,
+ 0, // position ordinal in axis
+ 0, // member ordinal in position
+ cellSet);
+
+ assertTransformTo(initialMdx, expectedMdx, transform);
+ }
+}
+
+// End TransformTest.java
diff --git a/testsrc/org/olap4j/transform/TransformTest.java.bak b/testsrc/org/olap4j/transform/TransformTest.java.bak
new file mode 100644
index 0000000..7c6868f
--- /dev/null
+++ b/testsrc/org/olap4j/transform/TransformTest.java.bak
@@ -0,0 +1,242 @@
+/*
+// $Id: $
+// This software is subject to the terms of the Common Public License
+// Agreement, available at the following URL:
+// http://www.opensource.org/licenses/cpl.html.
+// Copyright (C) 2008-2008 Julian Hyde
+// All Rights Reserved.
+// You must accept the terms of that agreement to use this software.
+ */
+package org.olap4j.transform;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.olap4j.Axis;
+import org.olap4j.CellSet;
+import org.olap4j.OlapConnection;
+import org.olap4j.OlapStatement;
+import org.olap4j.mdx.SelectNode;
+import org.olap4j.mdx.parser.MdxParser;
+import org.olap4j.test.TestContext;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for org.olap4j.transform package.
+ *
+ * @author etdub
+ * @author jhyde
+ * @version $Id: $
+ * @since Jul 28, 2008
+ */
+public class TransformTest extends TestCase {
+ final TestContext.Tester tester = TestContext.instance().getTester();
+ private Connection connection = null;
+
+ public TransformTest() {
+ super();
+ }
+
+ /**
+ * Simple strategy to prevent connection leaks: each test that needs a
+ * connection assigns it to this field, and {@link #tearDown()} closes it
+ * if it is not already closed.
+ */
+ protected void tearDown() throws Exception {
+ // Simple strategy to prevent connection leaks
+ if (connection != null
+ && !connection.isClosed())
+ {
+ connection.close();
+ connection = null;
+ }
+ }
+
+ protected OlapConnection getConnection() {
+ OlapConnection olapConnection = null;
+ try {
+ if(connection == null) {
+ connection = tester.createConnection();
+ }
+ olapConnection =
+ tester.getWrapper().unwrap(connection, OlapConnection.class);
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ return olapConnection;
+ }
+
+ protected OlapStatement getStatement() throws SQLException {
+ Statement statement = getConnection().createStatement();
+ return tester.getWrapper().unwrap(statement, OlapStatement.class);
+ }
+
+ /**
+ * Asserts the functionality of a transformation
+ *
+ * @param initialMdx initial MDX query to be transformed
+ * @param expectedMdx expected MDX query after applying transform; will
+ * be compared against the transformed query
+ * @param trans the transformation to apply to the initial query
+ */
+ public void assertTransformTo(String initialMdx, String expectedMdx,
+ MdxQueryTransform trans) {
+
+ OlapConnection connection = getConnection();
+
+ MdxParser mdxParser =
+ connection.getParserFactory().createMdxParser(connection);
+
+ SelectNode before = mdxParser.parseSelect(initialMdx);
+ SelectNode after = trans.apply(before);
+
+ // we also parse the expected MDX in order to normalize it
+ // (eliminate any whitespace / capitalization differences)
+ // note: CallNodes are not aware of function names, and as such
+ // will be compared in a case-sensitive manner
+ // (i.e. [SomeMember].Children vs [SomeMember].children are not
+ // equal, even if they are equivalent in MDX)
+ SelectNode expected = mdxParser.parseSelect(expectedMdx);
+
+ // TODO: consider adding .equals() method to ParseTreeNode instead
+ // of comparing strings (we could ignore case when comparing
+ // function names in CallNodes ...)
+ assertEquals(expected.toString(), after.toString());
+
+ try {
+ tearDown();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Unit test for DrillReplaceTransform
+ */
+ public void testDrillReplaceTransform() {
+ try{
+ final String initialMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products]} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ final String expectedMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products].Children} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ CellSet cellSet = getStatement().executeOlapQuery(initialMdx);
+
+ MdxQueryTransform transform =
+ StandardTransformLibrary.createDrillReplaceTransform(
+ Axis.ROWS,
+ 0, // position ordinal in axis
+ 0, // member ordinal in position
+ cellSet);
+
+ assertTransformTo(initialMdx, expectedMdx, transform);
+ }
+ catch(Throwable t) {
+ t.printStackTrace();
+ fail();
+ }
+
+ }
+
+ /**
+ * Unit test for RollUpLevelTransform
+ */
+ public void testRollUpLevelTransform() {
+
+ try{
+ final String initialMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products].[Food].[Deli]} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ final String expectedMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products].[Food].[Deli].Parent.Level.Members} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ CellSet cellSet = getStatement().executeOlapQuery(initialMdx);
+
+ MdxQueryTransform transform =
+ StandardTransformLibrary.createRollUpLevelTransform(
+ Axis.ROWS,
+ 0, // position ordinal in axis
+ 0, // member ordinal in position
+ cellSet);
+
+ assertTransformTo(initialMdx, expectedMdx, transform);
+ }
+ catch(Throwable t) {
+ t.printStackTrace();
+ fail();
+ }
+ }
+
+ /**
+ * Unit test for DrillDownOnPositionTransform
+ */
+ // TODO: change method name once the transform works
+ public void donttestDrillDownOnPositionTransform() {
+
+ // TODO: rewrite the initial and expected MDX once this transform
+ // is written.
+ // Will fail for now.
+
+ try{
+ final String initialMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products]} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ final String expectedMdx =
+ "SELECT {[Measures].[Unit Sales], " +
+ " [Measures].[Store Cost], " +
+ " [Measures].[Store Sales]} ON COLUMNS, " +
+ " {[Product].[All Products].Children} ON ROWS " +
+ "FROM Sales " +
+ "WHERE ([Time].[1997])";
+
+ CellSet cellSet = getStatement().executeOlapQuery(initialMdx);
+
+ MdxQueryTransform transform =
+ StandardTransformLibrary.createDrillDownOnPositionTransform(
+ Axis.ROWS,
+ 0, // position ordinal in axis
+ 0, // member ordinal in position
+ cellSet);
+
+ assertTransformTo(initialMdx, expectedMdx, transform);
+ }
+ catch(Throwable t) {
+ t.printStackTrace();
+ fail();
+ }
+
+ }
+
+}
+
+// End TransformTest.java
args
must be specified, even if
* there are zero arguments, and each argument must be not null.
*
* @param region Region of source code
@@ -177,6 +177,14 @@ public Syntax getSyntax() {
public ListOrder([Store].Members, [Measures].[Unit Sales], ASC)
, are
* also represented as Literals.
*
+ * defaultValueExpression
must be consistent with the
* type
.
*
+ * @param region Region of source code
* @param name Name of parameter
* @param type Type of parameter
* @param defaultValueExpression Expression which yields the default value
@@ -133,6 +134,13 @@ public void setDefaultValueExpression(ParseTreeNode defaultValueExpression) {
this.defaultValueExpression = defaultValueExpression;
}
+ public ParameterNode deepCopy() {
+ return new ParameterNode(
+ this.region,
+ this.name,
+ this.type, // types are immutable
+ this.defaultValueExpression.deepCopy());
+ }
}
// End ParameterNode.java
diff --git a/src/org/olap4j/mdx/ParseTreeNode.java b/src/org/olap4j/mdx/ParseTreeNode.java
index a429ad5..2c5851e 100644
--- a/src/org/olap4j/mdx/ParseTreeNode.java
+++ b/src/org/olap4j/mdx/ParseTreeNode.java
@@ -3,7 +3,7 @@
// This software is subject to the terms of the Common Public License
// Agreement, available at the following URL:
// http://www.opensource.org/licenses/cpl.html.
-// Copyright (C) 2007-2007 Julian Hyde
+// Copyright (C) 2007-2008 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
@@ -66,6 +66,17 @@ public interface ParseTreeNode {
* created by parsing
*/
ParseRegion getRegion();
+
+ /**
+ * Creates a deep copy of this ParseTreeNode object.
+ *
+ *