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 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 List getArgList() { return argList; } + + public CallNode deepCopy() { + return new CallNode( + this.region, + this.name, + this.syntax, + MdxUtil.deepCopyList(argList)); + } } // End CallNode.java diff --git a/src/org/olap4j/mdx/CubeNode.java b/src/org/olap4j/mdx/CubeNode.java index 9ba4c33..149bbdf 100644 --- a/src/org/olap4j/mdx/CubeNode.java +++ b/src/org/olap4j/mdx/CubeNode.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. */ @@ -67,6 +67,12 @@ public void unparse(ParseTreeWriter writer) { public String toString() { return cube.getUniqueName(); } + + public CubeNode deepCopy() { + // CubeNode is immutable + return this; + } + } // End CubeNode.java diff --git a/src/org/olap4j/mdx/DimensionNode.java b/src/org/olap4j/mdx/DimensionNode.java index f6e55bc..af18e33 100644 --- a/src/org/olap4j/mdx/DimensionNode.java +++ b/src/org/olap4j/mdx/DimensionNode.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. */ @@ -67,6 +67,11 @@ public void unparse(ParseTreeWriter writer) { public String toString() { return dimension.getUniqueName(); } + + public DimensionNode deepCopy() { + // DimensionNode is immutable + return this; + } } // End DimensionNode.java diff --git a/src/org/olap4j/mdx/HierarchyNode.java b/src/org/olap4j/mdx/HierarchyNode.java index 4bc2e5e..e86ffd4 100644 --- a/src/org/olap4j/mdx/HierarchyNode.java +++ b/src/org/olap4j/mdx/HierarchyNode.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. */ @@ -69,6 +69,11 @@ public void unparse(ParseTreeWriter writer) { public String toString() { return hierarchy.getUniqueName(); } + + public HierarchyNode deepCopy() { + // HierarchyNode is immutable + return this; + } } // End HierarchyNode.java diff --git a/src/org/olap4j/mdx/IdentifierNode.java b/src/org/olap4j/mdx/IdentifierNode.java index 369ce51..123edd8 100644 --- a/src/org/olap4j/mdx/IdentifierNode.java +++ b/src/org/olap4j/mdx/IdentifierNode.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. */ @@ -137,6 +137,11 @@ public String toString() { return buf.toString(); } + public IdentifierNode deepCopy() { + // IdentifierNode is immutable + return this; + } + /** * Parses an MDX identifier into a list of segments. * @@ -270,7 +275,7 @@ static void quoteMdxIdentifier(String id, StringBuilder buf) { * *

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 @@ * Order([Store].Members, [Measures].[Unit Sales], ASC), are * also represented as Literals. * + *

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 List deepCopyList(List list) { + // Don't make a copy of the system empty list. '==' is intentional. + if (list == Collections.EMPTY_LIST) { + return list; + } + final ArrayList listCopy = new ArrayList(list.size()); + for (E e : list) { + listCopy.add((E) e.deepCopy()); + } + return listCopy; + } } // End MdxUtil.java diff --git a/src/org/olap4j/mdx/MemberNode.java b/src/org/olap4j/mdx/MemberNode.java index ebe107c..31da096 100644 --- a/src/org/olap4j/mdx/MemberNode.java +++ b/src/org/olap4j/mdx/MemberNode.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. */ @@ -71,6 +71,11 @@ public void unparse(ParseTreeWriter writer) { public String toString() { return member.getUniqueName(); } + + public MemberNode deepCopy() { + // MemberNode is immutable + return this; + } } // End MemberNode.java diff --git a/src/org/olap4j/mdx/ParameterNode.java b/src/org/olap4j/mdx/ParameterNode.java index 4e8bad2..1f8904f 100644 --- a/src/org/olap4j/mdx/ParameterNode.java +++ b/src/org/olap4j/mdx/ParameterNode.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. */ @@ -41,6 +41,7 @@ public class ParameterNode implements ParseTreeNode { * 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. + * + *

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 getCellPropertyList() { + return cellPropertyList; + } + + public SelectNode deepCopy() { + return new SelectNode( + this.region, + MdxUtil.deepCopyList(withList), + MdxUtil.deepCopyList(axisList), + this.from != null ? this.from.deepCopy() : null, + this.filterAxis != null ? this.filterAxis.deepCopy() : null, + MdxUtil.deepCopyList(cellPropertyList)); + } } // End SelectNode.java diff --git a/src/org/olap4j/mdx/WithMemberNode.java b/src/org/olap4j/mdx/WithMemberNode.java index d5b5dca..1345db9 100644 --- a/src/org/olap4j/mdx/WithMemberNode.java +++ b/src/org/olap4j/mdx/WithMemberNode.java @@ -3,10 +3,10 @@ // 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 org.olap4j.type.Type; @@ -127,6 +127,14 @@ public Type getType() { public List getMemberPropertyList() { return memberPropertyList; } + + public WithMemberNode deepCopy() { + return new WithMemberNode( + this.region, // immutable + this.name.deepCopy(), + this.expression.deepCopy(), + MdxUtil.deepCopyList(memberPropertyList)); + } } // End WithMemberNode.java diff --git a/src/org/olap4j/mdx/WithSetNode.java b/src/org/olap4j/mdx/WithSetNode.java index 24a94b3..de2996b 100644 --- a/src/org/olap4j/mdx/WithSetNode.java +++ b/src/org/olap4j/mdx/WithSetNode.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. */ @@ -98,6 +98,12 @@ public Type getType() { throw new UnsupportedOperationException(); } + public WithSetNode deepCopy() { + return new WithSetNode( + this.region, + this.name.deepCopy(), + this.expression.deepCopy()); + } } // End WithSetNode.java diff --git a/src/org/olap4j/transform/AxisTransform.java b/src/org/olap4j/transform/AxisTransform.java new file mode 100644 index 0000000..74a6d52 --- /dev/null +++ b/src/org/olap4j/transform/AxisTransform.java @@ -0,0 +1,61 @@ +/* +// $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.Axis; +import org.olap4j.mdx.AxisNode; +import org.olap4j.mdx.ParseTreeNode; +import org.olap4j.mdx.SelectNode; + +/** + * Abstract representation of an MDX query transform acting on + * a single query axis (e.g. drill-down on member, roll-up, ...) + * + * @author etdub + * @version $Id: $ + * @since Aug 7, 2008 + * + */ +public abstract class AxisTransform implements MdxQueryTransform { + + protected final Axis axis; + + protected AxisTransform(Axis axis) { + this.axis = axis; + } + + public SelectNode apply(SelectNode sn) { + + // do a deep copy of the existing query SelectNode before + // modifying it: + SelectNode newSelectNode = sn.deepCopy(); + + for (AxisNode an : newSelectNode.getAxisList()) { + if (an.getAxis() == axis) { + // this is the axis we're drilling + + ParseTreeNode initialAxisExp = an.getExpression(); + + // apply the drill operation + ParseTreeNode newAxisExp = + processAxisExp(initialAxisExp); + + // replace the expression in the axis by the new generated one + an.setExpression(newAxisExp); + } + } + return newSelectNode; + } + + protected abstract ParseTreeNode processAxisExp(ParseTreeNode axisExp); + +} + +// End AxisTransform.java diff --git a/src/org/olap4j/transform/DrillDownOnPositionTransform.java b/src/org/olap4j/transform/DrillDownOnPositionTransform.java new file mode 100644 index 0000000..d85d594 --- /dev/null +++ b/src/org/olap4j/transform/DrillDownOnPositionTransform.java @@ -0,0 +1,191 @@ +/* +// $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.util.List; + +import org.olap4j.Axis; +import org.olap4j.CellSet; +import org.olap4j.Position; +import org.olap4j.mdx.AxisNode; +import org.olap4j.mdx.CallNode; +import org.olap4j.mdx.CubeNode; +import org.olap4j.mdx.DimensionNode; +import org.olap4j.mdx.HierarchyNode; +import org.olap4j.mdx.IdentifierNode; +import org.olap4j.mdx.LevelNode; +import org.olap4j.mdx.LiteralNode; +import org.olap4j.mdx.MemberNode; +import org.olap4j.mdx.ParameterNode; +import org.olap4j.mdx.ParseTreeNode; +import org.olap4j.mdx.ParseTreeVisitor; +import org.olap4j.mdx.PropertyValueNode; +import org.olap4j.mdx.SelectNode; +import org.olap4j.mdx.WithMemberNode; +import org.olap4j.mdx.WithSetNode; +import org.olap4j.metadata.Member; + +/** + * Drill down on position transform + * + * TODO: transform to be completed, not working for now. + * + *

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 pathToMember; + + /** + * ctor + * + * @param axis + * @param positionOrdinalInAxis + * @param memberOrdinalInPosition + * @param cellSet + */ + public DrillDownOnPositionTransform( + // ParseTreeNode query, + Axis axis, + int positionOrdinalInAxis, + int memberOrdinalInPosition, + CellSet cellSet) { + + super(axis); + // this.positionOrdinalInAxis = positionOrdinalInAxis; + // this.memberOrdinalInPosition = memberOrdinalInPosition; + // this.cellSet = cellSet; + + positionToDrill = + TransformUtil.getPositionFromCellSet(axis, positionOrdinalInAxis, + cellSet); + memberToDrill = TransformUtil.getMemberFromCellSet(axis, + positionOrdinalInAxis, memberOrdinalInPosition, cellSet); + pathToMember = TransformUtil.getPathToMember(positionToDrill, + memberOrdinalInPosition); + } + + public String getName() { + return "Drill down a member on a specific position"; + } + + public String getDescription() { + return "Expand a member on a position by adding its children"; + } + + @Override + protected ParseTreeNode processAxisExp(ParseTreeNode exp) { + // TODO: implement me! + + return null; + } + + + // visitor for a tree of expressions inside a query axis + // (not sure this should go here) + class DrillDownOnPositionVisitor implements ParseTreeVisitor { + + public ParseTreeNode visit(SelectNode selectNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(AxisNode axis) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(WithMemberNode calcMemberNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(WithSetNode calcSetNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(CallNode call) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(IdentifierNode id) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(ParameterNode parameterNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(CubeNode cubeNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(DimensionNode dimensionNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(HierarchyNode hierarchyNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(LevelNode levelNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(MemberNode memberNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(LiteralNode literalNode) { + // TODO Auto-generated method stub + return null; + } + + public ParseTreeNode visit(PropertyValueNode propertyValueNode) { + // TODO Auto-generated method stub + return null; + } + } + +} + +// End DrillDownOnPositionTransform.java diff --git a/src/org/olap4j/transform/DrillReplaceTransform.java b/src/org/olap4j/transform/DrillReplaceTransform.java new file mode 100644 index 0000000..99cb054 --- /dev/null +++ b/src/org/olap4j/transform/DrillReplaceTransform.java @@ -0,0 +1,96 @@ +/* +// $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.Axis; +import org.olap4j.CellSet; +import org.olap4j.mdx.ParseTreeNode; +import org.olap4j.metadata.Member; + +/** + * Drill replace transformation + * + *

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 pathToMember; + + /** + * ctor + * + * @param axis axis (of the resulting CellSet) the member to be drilled + * @param positionOrdinalInAxis position ordinal in axis of the member to + * be drilled + * @param memberOrdinalInPosition ordinal in position of the member to be + * drilled + * @param cellSet the CellSet resulting from execution of the query to be + * transformed + */ + public DrillReplaceTransform( + Axis axis, + int positionOrdinalInAxis, + int memberOrdinalInPosition, + CellSet cellSet) { + + super(axis); + + // this.positionOrdinalInAxis = positionOrdinalInAxis; + // this.memberOrdinalInPosition = memberOrdinalInPosition; + // this.cellSet = cellSet; + + // Position positionToDrill = + // TransformUtil.getPositionFromCellSet(axis, positionOrdinalInAxis, + // cellSet); + memberToDrill = TransformUtil.getMemberFromCellSet(axis, + positionOrdinalInAxis, memberOrdinalInPosition, cellSet); + // pathToMember = getPathToMember(positionToDrill, + // memberOrdinalInPosition); + } + + public String getName() { + return "Drill Replace On Member"; + } + + public String getDescription() { + return "Drills and replace (by its children) a member on an axis"; + } + + @Override + protected ParseTreeNode processAxisExp(ParseTreeNode exp) { + // FIXME: for now only 1 dimension on an axis is supported, + // (naive implementation only used for proof of concept) + return MdxHelper.makeSetCallNode( + MdxHelper.makeChildrenCallNode( + MdxHelper.makeMemberNode(memberToDrill))); + } + +} + +// End DrillReplaceTransform.java diff --git a/src/org/olap4j/transform/MdxHelper.java b/src/org/olap4j/transform/MdxHelper.java new file mode 100644 index 0000000..a23d6a0 --- /dev/null +++ b/src/org/olap4j/transform/MdxHelper.java @@ -0,0 +1,80 @@ +/* +// $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.util.ArrayList; +import java.util.List; + +import org.olap4j.mdx.CallNode; +import org.olap4j.mdx.MemberNode; +import org.olap4j.mdx.ParseTreeNode; +import org.olap4j.mdx.Syntax; +import org.olap4j.metadata.Member; + +/** + * Helper class for generating MDX expressions in query transforms. + * + *

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 callArgs = new ArrayList(); + callArgs.add(node); + return new CallNode(null, funcName, Syntax.Property, callArgs); + } + + public static CallNode makeChildrenCallNode(ParseTreeNode node) { + return _makePropCallNode(node, "Children"); + } + + public static CallNode makeParentCallNode(ParseTreeNode node) { + return _makePropCallNode(node, "Parent"); + } + + public static CallNode makeMembersCallNode(ParseTreeNode node) { + return _makePropCallNode(node, "Members"); + } + + public static CallNode makeLevelCallNode(ParseTreeNode node) { + return _makePropCallNode(node, "Level"); + } + + public static CallNode makeSetCallNode(List nodes) { + return new CallNode(null, "{}", Syntax.Braces, nodes); + } + + public static CallNode makeSetCallNode(ParseTreeNode... nodes) { + List nodesList = new ArrayList(); + for (ParseTreeNode n : nodes) { + nodesList.add(n); + } + return makeSetCallNode(nodesList); + } + + public static CallNode makeHierarchizeCallNode(ParseTreeNode node) { + List callArgs = new ArrayList(); + callArgs.add(node); + return new CallNode(null, "Hierarchize", Syntax.Function, callArgs); + } + +} + +// End MdxHelper.java diff --git a/src/org/olap4j/transform/MdxQueryTransform.java b/src/org/olap4j/transform/MdxQueryTransform.java new file mode 100644 index 0000000..7395d4e --- /dev/null +++ b/src/org/olap4j/transform/MdxQueryTransform.java @@ -0,0 +1,32 @@ +/* +// $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.mdx.SelectNode; + +/** + * MDX Query Transformation + * + *

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 memberTree; + + public Quax(CellSetAxis cellSetAxis) { + this.cellSetAxis = cellSetAxis; + + for (Position p : cellSetAxis.getPositions()) { + p.getMembers(); + } + + } + +} + +// End Quax.java diff --git a/src/org/olap4j/transform/RollUpLevelTransform.java b/src/org/olap4j/transform/RollUpLevelTransform.java new file mode 100644 index 0000000..eb32369 --- /dev/null +++ b/src/org/olap4j/transform/RollUpLevelTransform.java @@ -0,0 +1,97 @@ +/* +// $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.Axis; +import org.olap4j.CellSet; +import org.olap4j.mdx.ParseTreeNode; +import org.olap4j.metadata.Member; + +/** + * Roll-up level transformation + * + *

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 pathToMember; + + /** + * ctor + * + * @param axis + * @param positionOrdinalInAxis + * @param memberOrdinalInPosition + * @param cellSet + */ + public RollUpLevelTransform( + Axis axis, + int positionOrdinalInAxis, + int memberOrdinalInPosition, + CellSet cellSet) { + + super(axis); + + // this.positionOrdinalInAxis = positionOrdinalInAxis; + // this.memberOrdinalInPosition = memberOrdinalInPosition; + // this.cellSet = cellSet; + + // Position positionToDrill = + // TransformUtil.getPositionFromCellSet(axis, positionOrdinalInAxis, + // cellSet); + memberToDrill = TransformUtil.getMemberFromCellSet(axis, + positionOrdinalInAxis, memberOrdinalInPosition, cellSet); + // pathToMember = getPathToMember(positionToDrill, + // memberOrdinalInPosition); + } + + public String getName() { + return "Roll member up a level"; + } + + public String getDescription() { + return "Replaces the member expression on the axis by all members " + + "on its parent level"; + } + + @Override + protected ParseTreeNode processAxisExp(ParseTreeNode exp) { + // FIXME: for now only 1 dimension on an axis is supported, + // (naive implementation only used for proof of concept) + return MdxHelper.makeSetCallNode( + MdxHelper.makeMembersCallNode( + MdxHelper.makeLevelCallNode( + MdxHelper.makeParentCallNode( + MdxHelper.makeMemberNode(memberToDrill))))); + } + +} + +// End RollUpLevelTransform.java diff --git a/src/org/olap4j/transform/StandardTransformLibrary.java b/src/org/olap4j/transform/StandardTransformLibrary.java new file mode 100644 index 0000000..0c8c187 --- /dev/null +++ b/src/org/olap4j/transform/StandardTransformLibrary.java @@ -0,0 +1,71 @@ +/* +// $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.Axis; +import org.olap4j.CellSet; + +/** + * Standard transformations library + * + * NOTE: is this really needed since transforms' ctors have the same + * parameters as these functions? This serves only as a place to conveniently + * regroup transforms in a "library". + * + * @author etdub + * @author jhyde + * @version $Id: $ + * @since Jul 28, 2008 + */ +public class StandardTransformLibrary { + + public static MdxQueryTransform createDrillReplaceTransform( + Axis axis, + int positionOrdinalInAxis, + int memberOrdinalInPosition, + CellSet cellSet) + { + return new DrillReplaceTransform( + axis, + positionOrdinalInAxis, + memberOrdinalInPosition, + cellSet); + } + + public static MdxQueryTransform createDrillDownOnPositionTransform( + Axis axis, + int positionOrdinalInAxis, + int memberOrdinalInPosition, + CellSet cellSet) + { + return new DrillDownOnPositionTransform( + axis, + positionOrdinalInAxis, + memberOrdinalInPosition, + cellSet); + } + + public static MdxQueryTransform createRollUpLevelTransform( + Axis axis, + int positionOrdinalInAxis, + int memberOrdinalInPosition, + CellSet cellSet) + { + return new RollUpLevelTransform( + axis, + positionOrdinalInAxis, + memberOrdinalInPosition, + cellSet); + } + + // many other transforms ... +} + +// End StandardTransformLibrary.java diff --git a/src/org/olap4j/transform/TransformUtil.java b/src/org/olap4j/transform/TransformUtil.java new file mode 100644 index 0000000..fe8e519 --- /dev/null +++ b/src/org/olap4j/transform/TransformUtil.java @@ -0,0 +1,82 @@ +/* +// $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.util.ArrayList; +import java.util.List; + +import org.olap4j.Axis; +import org.olap4j.CellSet; +import org.olap4j.CellSetAxis; +import org.olap4j.Position; +import org.olap4j.metadata.Member; + +/** + * Various helper functions for MDX query transforms. + * + *

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 getPathToMember( + Position p, + int memberOrdinalInPosition) + { + List pathToMember = new ArrayList(); + for (int i = 0 ; i < memberOrdinalInPosition ; i++) { + pathToMember.add(p.getMembers().get(i)); + } + + return pathToMember; + } +} + +// End TransformUtil.java diff --git a/src/org/olap4j/transform/TreeNode.java b/src/org/olap4j/transform/TreeNode.java new file mode 100644 index 0000000..0435b2d --- /dev/null +++ b/src/org/olap4j/transform/TreeNode.java @@ -0,0 +1,198 @@ +/* +// $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.util.ArrayList; +import java.util.List; + +/** + * Generic Tree Node. + * Adapted from JPivot (class com.tonbeller.jpivot.util.TreeNode) + * + *

REVIEW: Should this class be in the public olap4j API? (jhyde, 2008/8/14) + * + * @author etdub + * @version $Id: $ + * @since Aug 7, 2008 + */ +class TreeNode { + + private TreeNode parent = null; + private final List> children; + private T reference; + + /** + * Constructor. + * + * @param data the reference to hold in the node + */ + public TreeNode(T data) { + this.reference = data; + this.children = new ArrayList>(); + } + + /** + * Removes this node from the tree + */ + public void remove() { + if (parent != null) { + parent.removeChild(this); + } + } + + /** + * Removes child node from the tree, if it exists + * @param child node to remove + */ + public void removeChild(TreeNode child) { + if (children.contains(child)) { + children.remove(child); + } + } + + /** + * Adds a child node to the tree + * @param child node to be added + */ + public void addChildNode(TreeNode child) { + child.parent = this; + if (!children.contains(child)) { + children.add(child); + } + } + + /** + * Performs a deep copy (clone) of this node + * The contained reference is not cloned but passed to the + * new node. + * @return the cloned TreeNode + */ + public TreeNode deepCopy() { + TreeNode newNode = new TreeNode(reference); + for (TreeNode child : children) { + newNode.addChildNode(child.deepCopy()); + } + return newNode; + } + + /** + * Performs a deep copy (clone) of this node, pruning + * all nodes below level specified by depth + * @param depth number of child levels to be copied + * @return the cloned TreeNode + */ + public TreeNode deepCopyPrune(int depth) { + if (depth < 0) { + throw new IllegalArgumentException("Depth is negative"); + } + TreeNode newNode = new TreeNode(reference); + if (depth == 0) { + return newNode; + } + for (TreeNode child : children) { + newNode.addChildNode(child.deepCopyPrune(depth - 1)); + } + return newNode; + } + + /** + * Returns the level of this node, i.e. distance from + * the root node + * @return level distance from root node + */ + public int getLevel() { + int level = 0; + TreeNode p = parent; + while (p != null) { + ++level; + p = p.parent; + } + return level; + } + + /** + * Gets a list of children nodes. + * + *

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> getChildren() { + return children; + } + + /** + * Gets the parent node. + * @return parent node + */ + public TreeNode getParent() { + return parent; + } + + /** + * Get the contained reference object + * @return the reference object + */ + public T getReference() { + return reference; + } + + /** + * Set the contained reference object + * @param ref the new reference object + */ + public void setReference(T ref) { + this.reference = ref; + } + + /** + * Walk through subtree of this node + * @param callbackHandler callback function called on iteration + * @return code used for navigation in the tree (@see TreeNodeCallback) + */ + public int walkTree(TreeNodeCallback callbackHandler) { + int code = callbackHandler.handleTreeNode(this); + if (code != TreeNodeCallback.CONTINUE) { + return code; + } + for (TreeNode child : children) { + code = child.walkTree(callbackHandler); + if (code >= TreeNodeCallback.CONTINUE_PARENT) { + return code; + } + } + return code; + } + + /** + * Walk through children subtrees of this node + * @param callbackHandler callback function called on iteration + * @return code used for navigation in the tree (@see TreeNodeCallback) + */ + public int walkChildren(TreeNodeCallback callbackHandler) { + int code = 0; + for (TreeNode child : children) { + code = callbackHandler.handleTreeNode(child); + if (code >= TreeNodeCallback.CONTINUE_PARENT) { + return code; + } + if (code == TreeNodeCallback.CONTINUE) { + code = child.walkChildren(callbackHandler); + if (code > TreeNodeCallback.CONTINUE_PARENT) { + return code; + } + } + } + return code; + } + +} + +// End TreeNode.java diff --git a/src/org/olap4j/transform/TreeNodeCallback.java b/src/org/olap4j/transform/TreeNodeCallback.java new file mode 100644 index 0000000..30bc1ea --- /dev/null +++ b/src/org/olap4j/transform/TreeNodeCallback.java @@ -0,0 +1,43 @@ +/* +// $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; + +/** + * Handle callback for navigating in a tree of TreeNode + * + * Adapted from JPivot (interface com.tonbeller.jpivot.util.TreeNodeCallback) + * + *

REVIEW: Should this class be in the public olap4j API? (jhyde, 2008/8/14) + * + * @author etdub + * @version $Id: $ + * @since Aug 7, 2008 + */ +interface TreeNodeCallback { + + public static final int CONTINUE = 0; + public static final int CONTINUE_SIBLING = 1; + public static final int CONTINUE_PARENT = 2; + public static final int BREAK = 3; + + /** + * Callback function. + * + * @param node the current node to handle + * @return CONTINUE (0) : continue tree walk + * CONTINUE_SIBLING (1) : continue to the sibling + * CONTINUE_PARENT (2) : continue at parent level + * BREAK (3) : break tree walk + */ + int handleTreeNode(TreeNode node); + +} + +// End TreeNodeCallback.java diff --git a/src/org/olap4j/transform/package.html b/src/org/olap4j/transform/package.html new file mode 100755 index 0000000..52ba0c5 --- /dev/null +++ b/src/org/olap4j/transform/package.html @@ -0,0 +1,8 @@ + + +Provides services to transform MDX parse trees. + +

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