From 96d3da3816e8c32d6238612d09f84f28f8e72b81 Mon Sep 17 00:00:00 2001 From: Julian Hyde Date: Tue, 13 Nov 2007 09:37:16 +0000 Subject: [PATCH] Lots of tests for metadata classes, and implement corresponding functionality in mondrian driver. Review org.olap4j.type package, and remove/hide several methods. Add API for creating validator and validating an MDX parse tree. Test that non-unique axis names cause a validation error. git-svn-id: https://olap4j.svn.sourceforge.net/svnroot/olap4j/trunk@38 c6a108a4-781c-0410-a6c6-c2d559e19af0 --- build.xml | 2 +- doc/olap4j_fs.html | 66 ++- doc/tasks.txt | 12 +- src/mondrian/olap4j/AbstractNamedList.java | 14 +- src/mondrian/olap4j/ArrayNamedListImpl.java | 54 +++ .../olap4j/MondrianOlap4jCellSetAxis.java | 7 +- .../olap4j/MondrianOlap4jCellSetMetaData.java | 38 +- .../olap4j/MondrianOlap4jConnection.java | 264 +++++++++-- src/mondrian/olap4j/MondrianOlap4jCube.java | 139 +++++- .../olap4j/MondrianOlap4jDimension.java | 40 +- .../olap4j/MondrianOlap4jHierarchy.java | 62 ++- src/mondrian/olap4j/MondrianOlap4jLevel.java | 48 +- src/mondrian/olap4j/MondrianOlap4jMember.java | 38 +- .../olap4j/MondrianOlap4jNamedSet.java | 67 +++ .../olap4j/MondrianOlap4jProperty.java | 61 +++ src/mondrian/olap4j/NamedListImpl.java | 28 +- src/org/olap4j/CellSetAxisMetaData.java | 5 +- src/org/olap4j/CellSetMetaData.java | 7 +- .../xmla/XmlaOlap4jCellSetMetaData.java | 16 +- .../driver/xmla/XmlaOlap4jConnection.java | 7 +- src/org/olap4j/mdx/AxisNode.java | 3 +- src/org/olap4j/mdx/CallNode.java | 24 +- src/org/olap4j/mdx/CubeNode.java | 72 +++ ...ator.java => DefaultMdxValidatorImpl.java} | 29 +- src/org/olap4j/mdx/HierarchyNode.java | 4 +- src/org/olap4j/mdx/LevelNode.java | 5 +- src/org/olap4j/mdx/MemberNode.java | 6 +- src/org/olap4j/mdx/ParseTreeNode.java | 2 +- src/org/olap4j/mdx/ParseTreeVisitor.java | 7 + src/org/olap4j/mdx/SelectNode.java | 43 +- src/org/olap4j/mdx/parser/MdxParser.java | 1 - .../olap4j/mdx/parser/MdxParserFactory.java | 8 + src/org/olap4j/mdx/parser/MdxValidator.java | 57 +++ src/org/olap4j/metadata/Cube.java | 68 ++- src/org/olap4j/metadata/Dimension.java | 15 - src/org/olap4j/metadata/Hierarchy.java | 15 + src/org/olap4j/metadata/Level.java | 9 +- src/org/olap4j/metadata/Member.java | 23 +- src/org/olap4j/metadata/NamedList.java | 12 + src/org/olap4j/metadata/NamedSet.java | 9 + src/org/olap4j/metadata/Property.java | 10 +- src/org/olap4j/sample/SimpleQuerySample.java | 5 +- src/org/olap4j/type/BooleanType.java | 6 + src/org/olap4j/type/CubeType.java | 26 ++ src/org/olap4j/type/DimensionType.java | 14 +- src/org/olap4j/type/HierarchyType.java | 21 +- src/org/olap4j/type/LevelType.java | 25 +- src/org/olap4j/type/MemberType.java | 64 ++- src/org/olap4j/type/SetType.java | 5 +- src/org/olap4j/type/SymbolType.java | 22 + src/org/olap4j/type/TupleType.java | 44 +- src/org/olap4j/type/Type.java | 12 +- src/org/olap4j/type/TypeUtil.java | 44 +- testsrc/org/olap4j/ConnectionTest.java | 434 +++++++++++++++++- 54 files changed, 1743 insertions(+), 376 deletions(-) create mode 100644 src/mondrian/olap4j/ArrayNamedListImpl.java create mode 100644 src/mondrian/olap4j/MondrianOlap4jNamedSet.java create mode 100644 src/mondrian/olap4j/MondrianOlap4jProperty.java create mode 100644 src/org/olap4j/mdx/CubeNode.java rename src/org/olap4j/mdx/{MdxValidator.java => DefaultMdxValidatorImpl.java} (91%) create mode 100644 src/org/olap4j/mdx/parser/MdxValidator.java diff --git a/build.xml b/build.xml index cc3f00f..7243bab 100644 --- a/build.xml +++ b/build.xml @@ -14,7 +14,7 @@ - + diff --git a/doc/olap4j_fs.html b/doc/olap4j_fs.html index 7d58f1d..4563e96 100644 --- a/doc/olap4j_fs.html +++ b/doc/olap4j_fs.html @@ -78,10 +78,10 @@

olap4j Functional Specification

Authors: Julian Hyde, Barry Klawans
-Version: 0.89.2-dev (draft)
+Version: 0.89.3-dev (draft)
Revision: $Id$ (log)
-Last modified: September 21st, 2007.

+Last modified: November 12th, 2007.


Contents

@@ -898,7 +898,7 @@

2.4. MDX query model

// Create a query model.
SelectNode query = new SelectNode();
- query.setCubeName(
+ query.setFrom(
    new IdentifierNode(
        new IdentifierNode.Segment("Sales")));
@@ -982,12 +982,36 @@

2.5. MDX parser

Package name: org.olap4j.mdx.parser

-

Provides an MDX parser.

+

Provides an MDX parser and validator.

+

Parser and validator are both allocated via a parser factory, which is +obtained from a connection:

+ +
+ OlapConnection connection;
+ MdxParserFactory parserFactory =
+    connection.getParserFactory();
+ MdxParser parser =
+    parserFactory.createMdxParser(connection);
+ SelectNode select =
+    parser.parse("SELECT FROM [Sales]");
+ MdxValidator validator =
+    parserFactory.createMdxValidator(connection);
+ select = validator.validate(select);
+ +

Parser and validator are not thread-safe (they cannot be used by more than +one thread simultaneously) but they can be re-used for multiple statements.

+

One of the chief purposes of validation is to assign a type to every +expression within the parse tree. Before validation, any node's + +ParseTreeNode.getType() method may throw an exception, but after validation +the getType() method will return a type. Nodes which are not +expressions do not have types, and will always return null.

Classes:

2.6. MDX type model

@@ -1208,13 +1232,15 @@
2.7.2.4. The Cube interface
formula.

    -
  • List<Dimension> getDimensions()
  • +
  • NamedList<Dimension> getDimensions()
  • List<Measure> getMeasures()
  • NamedList<NamedSet> getSets()
  • Schema getSchema()
  • String getName()
  • List<Locale> getSupportedLocales() (see Internationalization)
  • +
  • Member lookupMember(String... nameParts)
  • +
  • Member lookupMembers(Set<TreeOp> treeOps, String... nameParts)
2.7.2.5. The Dimension interface
@@ -1230,8 +1256,7 @@
2.7.2.5. The Dimension interface
  • String getName()
  • -
  • List<Hierarchy> getHierarchies()
  • -
  • List<Member> getRootMembers()
  • +
  • NamedList<Hierarchy> getHierarchies()
  • Dimension.Type getDimensionType()
2.7.2.6. The Hierarchy interface
@@ -1244,9 +1269,10 @@
2.7.2.6. The Hierarchy interface
  • Dimension getDimension()
  • String getName()
  • -
  • List<Level> getLevels()
  • +
  • NamedList<Level> getLevels()
  • boolean hasAll()
  • Member getDefaultMember()
  • +
  • NamedList<Member> getRootMembers()
2.7.2.7. The Level interface

A Level  @@ -1257,7 +1283,7 @@

2.7.2.7. The Level interface
  • int getDepth()
  • Hierarchy getHierarchy()
  • Level.Type getLevelType()
  • -
  • List<Property> getProperties()
  • +
  • NamedList<Property> getProperties()
  • List<Member> getMembers()
  • 2.7.2.8. The Member interface
    @@ -1266,10 +1292,11 @@
    2.7.2.8. The Member interface
    MetadataElement) is a data value in an OLAP dimension.

    • String getName()
    • -
    • List<Member> getChildMembers()
    • +
    • NamedList<Member> getChildMembers()
    • Member getParentMember()
    • Level getLevel()
    • Hierarchy getHierarchy()
    • +
    • boolean isAll()
    • boolean isChildOrEqualTo(Member member)
    • boolean isCalculated()
    • boolean isCalculatedInQuery()
    • @@ -1278,11 +1305,12 @@
      2.7.2.8. The Member interface
    • Object getPropertyValue(Property property)
    • String getPropertyFormattedValue(Property property)
    • void setProperty(Property property, Object value)
    • -
    • List<Property> getProperties()
    • +
    • NamedList<Property> getProperties()
    • int getOrdinal()
    • boolean isHidden()
    • Member getDataMember()
    +
    2.7.2.9. The Measure interface

    A Measure (extends @@ -1312,7 +1340,6 @@

    2.7.2.10. The Property interface
    • Datatype getType()
    • Scope getScope()
    • -
    • boolean isInternal()
    • enum Scope { MEMBER, CELL }
    • enum Datatype { STRING, OTHER, NUMERIC, BOOLEAN }
    • enum StandardMemberProperty implements Property { CATALOG_NAME, SCHEMA_NAME, CUBE_NAME, ... @@ -3451,8 +3478,9 @@

      Appendix D. Miscellaneous

      D.1. To be specified

      +

      [Method for richer query of members, analogous to OlapDatabaseMetaData.getMembers(). Maybe extend Cube.lookupMember. Something with a treeop.]

      [Discuss thread safety of connections, statements, result sets.]

      -

      [API for cancelling statements.]

      +

      [API for canceling statements.]

      [2006/10/20#1. Specification should include compliance levels, like the SQL specification does. In particular, we will allow providers to comply with a limited subset of MDX.]

      @@ -3476,10 +3504,12 @@

      D.1. To be specified

      efficiency as a design goal.]

      D.2. Design notes

      -

      JDK. We are targeting JDK 1.5, and running retroweaver for +

      JDK.

      +

      We are targeting JDK 1.5, and running retroweaver for backward compatibility for JDK 1.4. See forum thread: -olap4j, JDK 1.5 and generics.

      +olap4j, JDK 1.5 and generics.

      +

      We also support JDK 1.6, and with it JDBC 4.0.

      Result sets, random access, and memory usage

      Should result sets return their axes as cursors or collections? Cursors require less memory, but collections provide an easier programming model.

      @@ -3520,4 +3550,4 @@

      Appendix F. Change log


      - \ No newline at end of file + diff --git a/doc/tasks.txt b/doc/tasks.txt index 2e01b95..2565258 100644 --- a/doc/tasks.txt +++ b/doc/tasks.txt @@ -30,22 +30,19 @@ Specification Javadoc ------- -* change ant task so it doesn't generate mondrian.olap4j.* or olap4j.test.* or org.olap4j.mdx.parser.impl or package-protected classes (E.g. org.olap4j.mdx.MdxValidator) in public api; remove class OlapTest, class Todo +* remove class OlapTest * ensure that every public method has a javadoc description, including @param, @return and @throws declarations -* Review olap4j.type package. Document all public methods. Make non-essential methods (E.g. MemberType.forType) package-private. If in doubt, leave it out - + Code ---- * fix 4 TODOs in ParserTest -* document/implement Cube.lookupMember - + Testing ------- * Remove site-specifics from test code. * Ensure that there is a unit test for every method in every public class or interface. -* Write unit test for metadata classes. * Internationalization test, including Connection.getLocale(), Query.getLocale(), and all methods with Locale methods or results * Test access control. The metadata (e.g. members & hierarchies) should reflect what the current user/role can see. For example, USA.CA.SF has no children. This test should test every method which returns a metadata element or collection of metadata elements. * ConnectionTest (428, 8) // todo: test statement with no slicer @@ -55,8 +52,7 @@ Testing * Test methods in OlapResultAxisMetaData (see ConnectionTest.testOlapResultAxisMetaData) * Test cell and dimension properties (in ConnectionTest, see "todo: submit a query with cell and dimension properties, and make sure the properties appear in the result set") * Test scrolling (in ConnectionTest, see "todo: submit a query where you ask for different scrolling") -* Test axis name uniqueness (in ConnectionTest, see "test that get error if axes do not have unique names (todo)") - + Not in scope for olap4j 0.9 --------------------------- diff --git a/src/mondrian/olap4j/AbstractNamedList.java b/src/mondrian/olap4j/AbstractNamedList.java index caf8ed5..54f8326 100644 --- a/src/mondrian/olap4j/AbstractNamedList.java +++ b/src/mondrian/olap4j/AbstractNamedList.java @@ -17,21 +17,24 @@ * Partial implementation of {@link org.olap4j.metadata.NamedList}. * *

      Derived class must implement {@link #get(int)} and {@link #size()}, as - * per {@link java.util.AbstractList}. + * per {@link java.util.AbstractList}; and must implement + * {@link #getName(Object)}, to indicate how elements are named. * - * @see mondrian.olap4j.NamedListImpl + * @see ArrayNamedListImpl * * @author jhyde * @version $Id$ * @since May 25, 2007 */ -abstract class AbstractNamedList +abstract class AbstractNamedList extends AbstractList implements NamedList { + protected abstract String getName(T t); + public T get(String name) { for (T t : this) { - if (t.getName().equals(name)) { + if (getName(t).equals(name)) { return t; } } @@ -41,13 +44,12 @@ public T get(String name) { public int indexOfName(String name) { for (int i = 0; i < size(); ++i) { T t = get(i); - if (t.getName().equals(name)) { + if (getName(t).equals(name)) { return i; } } return -1; } - } // End AbstractNamedList.java diff --git a/src/mondrian/olap4j/ArrayNamedListImpl.java b/src/mondrian/olap4j/ArrayNamedListImpl.java new file mode 100644 index 0000000..e85a5f2 --- /dev/null +++ b/src/mondrian/olap4j/ArrayNamedListImpl.java @@ -0,0 +1,54 @@ +/* +// 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 +// All Rights Reserved. +// You must accept the terms of that agreement to use this software. +*/ +package mondrian.olap4j; + +import org.olap4j.metadata.NamedList; + +import java.util.ArrayList; + +/** + * Implementation of {@link org.olap4j.metadata.NamedList} which uses + * {@link java.util.ArrayList} for storage. + * + *

      Derived class must implement {@link #getName(Object)}, to indicate how + * elements are named. + * + * @see mondrian.olap4j.NamedListImpl + * + * @author jhyde + * @version $Id: $ + * @since Nov 12, 2007 + */ +public abstract class ArrayNamedListImpl + extends ArrayList + implements NamedList +{ + protected abstract String getName(T t); + + public T get(String name) { + for (T t : this) { + if (getName(t).equals(name)) { + return t; + } + } + return null; + } + + public int indexOfName(String name) { + for (int i = 0; i < size(); ++i) { + T t = get(i); + if (getName(t).equals(name)) { + return i; + } + } + return -1; + } +} + +// End ArrayNamedListImpl.java diff --git a/src/mondrian/olap4j/MondrianOlap4jCellSetAxis.java b/src/mondrian/olap4j/MondrianOlap4jCellSetAxis.java index 9d0a15e..deaa226 100644 --- a/src/mondrian/olap4j/MondrianOlap4jCellSetAxis.java +++ b/src/mondrian/olap4j/MondrianOlap4jCellSetAxis.java @@ -10,10 +10,7 @@ package mondrian.olap4j; import org.olap4j.*; -import org.olap4j.Position; -import org.olap4j.metadata.Hierarchy; -import org.olap4j.metadata.Member; -import org.olap4j.metadata.Property; +import org.olap4j.metadata.*; import java.util.*; @@ -87,7 +84,7 @@ public List getHierarchies() { throw new UnsupportedOperationException(); } - public List getProperties() { + public NamedList getProperties() { throw new UnsupportedOperationException(); } diff --git a/src/mondrian/olap4j/MondrianOlap4jCellSetMetaData.java b/src/mondrian/olap4j/MondrianOlap4jCellSetMetaData.java index 003e350..c9fdba2 100644 --- a/src/mondrian/olap4j/MondrianOlap4jCellSetMetaData.java +++ b/src/mondrian/olap4j/MondrianOlap4jCellSetMetaData.java @@ -9,22 +9,15 @@ */ package mondrian.olap4j; -import org.olap4j.CellSetMetaData; -import org.olap4j.CellSetAxisMetaData; -import org.olap4j.Axis; -import org.olap4j.metadata.Property; -import org.olap4j.metadata.Cube; -import org.olap4j.metadata.Hierarchy; - -import java.util.List; -import java.util.ArrayList; -import java.sql.SQLException; - import mondrian.olap.Query; import mondrian.olap.QueryAxis; -import mondrian.olap.type.Type; -import mondrian.olap.type.SetType; -import mondrian.olap.type.TupleType; +import mondrian.olap.type.*; +import org.olap4j.*; +import org.olap4j.metadata.*; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; /** * Implementation of {@link org.olap4j.CellSetMetaData} @@ -48,7 +41,7 @@ class MondrianOlap4jCellSetMetaData implements CellSetMetaData { // implement CellSetMetaData - public List getCellProperties() { + public NamedList getCellProperties() { throw new UnsupportedOperationException(); } @@ -56,17 +49,20 @@ public Cube getCube() { return olap4jStatement.olap4jConnection.toOlap4j(query.getCube()); } - public List getAxesMetaData() { - final List list = - new ArrayList(); + public NamedList getAxesMetaData() { + final NamedList list = + new ArrayNamedListImpl() { + protected String getName(CellSetAxisMetaData axisMetaData) { + return axisMetaData.getAxis().name(); + } + }; final MondrianOlap4jConnection olap4jConnection = olap4jStatement.olap4jConnection; for (final QueryAxis queryAxis : query.getAxes()) { list.add( new CellSetAxisMetaData() { public Axis getAxis() { - return olap4jConnection.toOlap4j( - queryAxis.getAxisOrdinal()); + return Axis.valueOf(queryAxis.getAxisOrdinal().name()); } public List getHierarchies() { @@ -89,7 +85,7 @@ public List getHierarchies() { return hierarchyList; } - public List getProperties() { + public NamedList getProperties() { throw new UnsupportedOperationException(); } } diff --git a/src/mondrian/olap4j/MondrianOlap4jConnection.java b/src/mondrian/olap4j/MondrianOlap4jConnection.java index 8e87d5e..240728f 100644 --- a/src/mondrian/olap4j/MondrianOlap4jConnection.java +++ b/src/mondrian/olap4j/MondrianOlap4jConnection.java @@ -9,31 +9,23 @@ */ package mondrian.olap4j; +import mondrian.mdx.*; import mondrian.olap.*; - -import java.sql.*; -import java.util.*; - -import org.olap4j.*; -import org.olap4j.Cell; import org.olap4j.Axis; -import org.olap4j.type.BooleanType; -import org.olap4j.type.CubeType; -import org.olap4j.type.DecimalType; -import org.olap4j.type.DimensionType; -import org.olap4j.type.SymbolType; -import org.olap4j.type.TupleType; -import org.olap4j.type.Type; -import org.olap4j.type.StringType; -import org.olap4j.type.SetType; -import org.olap4j.type.NumericType; -import org.olap4j.type.NullType; -import org.olap4j.type.MemberType; +import org.olap4j.Cell; +import org.olap4j.*; +import org.olap4j.mdx.*; +import org.olap4j.mdx.parser.*; +import org.olap4j.mdx.parser.impl.DefaultMdxParserImpl; import org.olap4j.metadata.*; import org.olap4j.metadata.Schema; -import org.olap4j.mdx.parser.MdxParserFactory; -import org.olap4j.mdx.parser.MdxParser; -import org.olap4j.mdx.parser.impl.DefaultMdxParserImpl; +import org.olap4j.type.*; +import org.olap4j.type.DimensionType; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.*; +import java.util.*; /** * Implementation of {@link org.olap4j.OlapConnection} @@ -323,6 +315,10 @@ public MdxParserFactory getParserFactory() { public MdxParser createMdxParser(OlapConnection connection) { return new DefaultMdxParserImpl(connection); } + + public MdxValidator createMdxValidator(OlapConnection connection) { + return new MondrianOlap4jMdxValidator(connection); + } }; } @@ -331,7 +327,7 @@ public Schema getSchema() throws OlapException { } MondrianOlap4jCube toOlap4j(mondrian.olap.Cube cube) { - MondrianOlap4jSchema schema = toOlap4j(cube.getSchema()); + MondrianOlap4jSchema schema = toOlap4j(cube.getSchema()); return new MondrianOlap4jCube(cube, schema); } @@ -412,7 +408,9 @@ MondrianOlap4jMember toOlap4j(mondrian.olap.Member member) { if (member == null) { return null; } - throw new UnsupportedOperationException(); + return new MondrianOlap4jMember( + toOlap4j(member.getDimension().getSchema()), + member); } MondrianOlap4jLevel toOlap4j(mondrian.olap.Level level) { @@ -441,10 +439,6 @@ Type[] toOlap4j(mondrian.olap.type.Type[] mondrianTypes) { return types; } - Axis toOlap4j(mondrian.olap.AxisOrdinal axisOrdinal) { - throw new UnsupportedOperationException(); - } - /** * Converts a Properties object to a Map with String keys and values. * @@ -459,6 +453,26 @@ public Set> entrySet() { }; } + MondrianOlap4jNamedSet toOlap4j( + mondrian.olap.Cube cube, + mondrian.olap.NamedSet namedSet) + { + if (namedSet == null) { + return null; + } + return new MondrianOlap4jNamedSet( + toOlap4j(cube), + namedSet); + } + + ParseTreeNode toOlap4j(Exp exp) { + return new MondrianToOlap4jNodeConverter(this).toOlap4j(exp); + } + + SelectNode toOlap4j(Query query) { + return new MondrianToOlap4jNodeConverter(this).toOlap4j(query); + } + // inner classes /** @@ -520,6 +534,202 @@ public OlapException toOlapException(SQLException e) { } } } + + private static class MondrianOlap4jMdxValidator implements MdxValidator { + private final OlapConnection connection; + + public MondrianOlap4jMdxValidator(OlapConnection connection) { + this.connection = connection; + } + + public SelectNode validateSelect(SelectNode selectNode) throws OlapException { + StringWriter sw = new StringWriter(); + selectNode.unparse(new ParseTreeWriter(new PrintWriter(sw))); + String mdx = sw.toString(); + final MondrianOlap4jConnection olap4jConnection = + (MondrianOlap4jConnection) connection; + Query query = + olap4jConnection.connection + .parseQuery(mdx); + query.resolve(); + return olap4jConnection.toOlap4j(query); + } + } + + private static class MondrianToOlap4jNodeConverter { + private final MondrianOlap4jConnection olap4jConnection; + + MondrianToOlap4jNodeConverter( + MondrianOlap4jConnection olap4jConnection) + { + this.olap4jConnection = olap4jConnection; + } + + public SelectNode toOlap4j(Query query) { + List list = Collections.emptyList(); + return new SelectNode( + null, + toOlap4j(query.getFormulas()), + toOlap4j(query.getAxes()), + new CubeNode( + null, + olap4jConnection.toOlap4j(query.getCube())), + query.getSlicerAxis() == null + ? null + : toOlap4j(query.getSlicerAxis()), + list); + } + + private AxisNode toOlap4j(QueryAxis axis) { + return new AxisNode( + null, + axis.isNonEmpty(), + toOlap4j(axis.getSet()), + Axis.valueOf(axis.getAxisName()), + toOlap4j(axis.getDimensionProperties())); + } + + private List toOlap4j(Id[] dimensionProperties) { + final List list = new ArrayList(); + for (Id property : dimensionProperties) { + list.add(toOlap4j(property)); + } + return list; + } + + private ParseTreeNode toOlap4j(Exp exp) { + if (exp instanceof Id) { + Id id = (Id) exp; + return toOlap4j(id); + } + if (exp instanceof ResolvedFunCall) { + ResolvedFunCall call = (ResolvedFunCall) exp; + return toOlap4j(call); + } + if (exp instanceof DimensionExpr) { + DimensionExpr dimensionExpr = (DimensionExpr) exp; + return new DimensionNode( + null, + olap4jConnection.toOlap4j(dimensionExpr.getDimension())); + } + if (exp instanceof HierarchyExpr) { + HierarchyExpr hierarchyExpr = (HierarchyExpr) exp; + return new HierarchyNode( + null, + olap4jConnection.toOlap4j(hierarchyExpr.getHierarchy())); + } + if (exp instanceof LevelExpr) { + LevelExpr levelExpr = (LevelExpr) exp; + return new LevelNode( + null, + olap4jConnection.toOlap4j(levelExpr.getLevel())); + } + if (exp instanceof MemberExpr) { + MemberExpr memberExpr = (MemberExpr) exp; + return new MemberNode( + null, + olap4jConnection.toOlap4j(memberExpr.getMember())); + } + if (exp instanceof Literal) { + Literal literal = (Literal) exp; + final Object value = literal.getValue(); + if (literal.getCategory() == Category.Symbol) { + return LiteralNode.createSymbol( + null, (String) literal.getValue()); + } else if (value instanceof Double) { + return LiteralNode.create(null, (Double) value); + } else if (value instanceof Integer) { + return LiteralNode.create(null, (Integer) value); + } else if (value instanceof String) { + return LiteralNode.createString(null, (String) value); + } else if (value == null) { + return LiteralNode.createNull(null); + } else { + throw new RuntimeException("unknown literal " + literal); + } + } + throw Util.needToImplement(exp.getClass()); + } + + private ParseTreeNode toOlap4j(ResolvedFunCall call) { + final CallNode callNode = new CallNode( + null, + call.getFunName(), + toOlap4j(call.getSyntax()), + toOlap4j(Arrays.asList(call.getArgs()))); + if (call.getType() != null) { + callNode.setType(olap4jConnection.toOlap4j(call.getType())); + } + return callNode; + } + + private List toOlap4j(List exprList) { + final List result = new ArrayList(); + for (Exp expr : exprList) { + result.add(toOlap4j(expr)); + } + return result; + } + + private org.olap4j.mdx.Syntax toOlap4j(mondrian.olap.Syntax syntax) { + return org.olap4j.mdx.Syntax.valueOf(syntax.name()); + } + + private List toOlap4j(QueryAxis[] axes) { + final ArrayList axisList = new ArrayList(); + for (QueryAxis axis : axes) { + axisList.add(toOlap4j(axis)); + } + return axisList; + } + + private List toOlap4j(Formula[] formulas) { + final List list = new ArrayList(); + for (Formula formula : formulas) { + if (formula.isMember()) { + List memberPropertyList = + new ArrayList(); + for (Object child : formula.getChildren()) { + if (child instanceof MemberProperty) { + MemberProperty memberProperty = + (MemberProperty) child; + memberPropertyList.add( + new PropertyValueNode( + null, + memberProperty.getName(), + toOlap4j(memberProperty.getExp()))); + } + } + list.add( + new WithMemberNode( + null, + toOlap4j(formula.getIdentifier()), + toOlap4j(formula.getExpression()), + memberPropertyList)); + } + } + return list; + } + + private IdentifierNode toOlap4j(Id id) { + List list = + new ArrayList(); + for (Id.Segment segment : id.getSegments()) { + list.add( + new IdentifierNode.Segment( + null, + segment.name, + toOlap4j(segment.quoting))); + } + return new IdentifierNode( + list.toArray( + new IdentifierNode.Segment[list.size()])); + } + + private IdentifierNode.Quoting toOlap4j(Id.Quoting quoting) { + return IdentifierNode.Quoting.valueOf(quoting.name()); + } + } } // End MondrianOlap4jConnection.java diff --git a/src/mondrian/olap4j/MondrianOlap4jCube.java b/src/mondrian/olap4j/MondrianOlap4jCube.java index c02aaa0..e93a424 100644 --- a/src/mondrian/olap4j/MondrianOlap4jCube.java +++ b/src/mondrian/olap4j/MondrianOlap4jCube.java @@ -9,18 +9,11 @@ */ package mondrian.olap4j; +import mondrian.olap.Id; +import mondrian.olap.SchemaReader; import org.olap4j.metadata.*; -import org.olap4j.metadata.Cube; -import org.olap4j.metadata.Dimension; -import org.olap4j.metadata.NamedSet; -import org.olap4j.metadata.Schema; -import org.olap4j.metadata.Member; -import java.util.List; -import java.util.Locale; -import java.util.Collection; - -import mondrian.olap.*; +import java.util.*; /** * Implementation of {@link Cube} @@ -32,7 +25,7 @@ */ class MondrianOlap4jCube implements Cube, Named { private final mondrian.olap.Cube cube; - private final MondrianOlap4jSchema olap4jSchema; + final MondrianOlap4jSchema olap4jSchema; MondrianOlap4jCube( mondrian.olap.Cube cube, @@ -46,6 +39,20 @@ public Schema getSchema() { return olap4jSchema; } + public int hashCode() { + return olap4jSchema.hashCode() + ^ cube.hashCode(); + } + + public boolean equals(Object obj) { + if (obj instanceof MondrianOlap4jCube) { + MondrianOlap4jCube that = (MondrianOlap4jCube) obj; + return this.olap4jSchema == that.olap4jSchema + && this.cube.equals(that.cube); + } + return false; + } + public NamedList getDimensions() { List list = new NamedListImpl(); @@ -58,11 +65,21 @@ public NamedList getDimensions() { } public List getMeasures() { - throw new UnsupportedOperationException(); + final Level measuresLevel = + getDimensions().get("Measures").getDefaultHierarchy() + .getLevels().get(0); + return (List) measuresLevel.getMembers(); } public NamedList getSets() { - throw new UnsupportedOperationException(); + final NamedListImpl list = + new NamedListImpl(); + final MondrianOlap4jConnection olap4jConnection = + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; + for (mondrian.olap.NamedSet namedSet : cube.getNamedSets()) { + list.add(olap4jConnection.toOlap4j(cube, namedSet)); + } + return (NamedList) list; } public Collection getSupportedLocales() { @@ -88,7 +105,101 @@ public String getDescription(Locale locale) { } public Member lookupMember(String... nameParts) { - throw new UnsupportedOperationException(); + final MondrianOlap4jConnection olap4jConnection = + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; + final SchemaReader schemaReader = + cube.getSchemaReader(olap4jConnection.connection.getRole()); + + final List segmentList = new ArrayList(); + for (String namePart : nameParts) { + segmentList.add(new Id.Segment(namePart, Id.Quoting.QUOTED)); + } + final mondrian.olap.Member member = + schemaReader.getMemberByUniqueName(segmentList, false); + if (member == null) { + return null; + } + return olap4jConnection.toOlap4j(member); + } + + public List lookupMembers( + Set treeOps, + String... nameParts) + { + final Member member = lookupMember(nameParts); + if (member == null) { + return Collections.emptyList(); + } + + // Add ancestors and/or the parent. Ancestors are prepended, to ensure + // hierarchical order. + final List list = new ArrayList(); + if (treeOps.contains(Member.TreeOp.ANCESTORS)) { + for (Member m = member.getParentMember(); + m != null; + m = m.getParentMember()) { + list.add(0, m); + } + } else if (treeOps.contains(Member.TreeOp.PARENT)) { + final Member parentMember = member.getParentMember(); + if (parentMember != null) { + list.add(parentMember); + } + } + + // Add siblings. Siblings which occur after the member are deferred, + // because they occur after children and descendants in the + // hierarchical ordering. + List remainingSiblingsList = null; + if (treeOps.contains(Member.TreeOp.SIBLINGS)) { + final Member parentMember = member.getParentMember(); + NamedList siblingMembers; + if (parentMember != null) { + siblingMembers = parentMember.getChildMembers(); + } else { + siblingMembers = member.getHierarchy().getRootMembers(); + } + List targetList = list; + for (Member siblingMember : siblingMembers) { + if (siblingMember.equals(member)) { + targetList = + remainingSiblingsList = + new ArrayList(); + } else { + targetList.add(siblingMember); + } + } + } + + // Add the member itself. + if (treeOps.contains(Member.TreeOp.SELF)) { + list.add(member); + } + + // Add descendants and/or children. + if (treeOps.contains(Member.TreeOp.DESCENDANTS)) { + for (Member childMember : member.getChildMembers()) { + list.add(childMember); + addDescendants(list, childMember); + } + } else if (treeOps.contains(Member.TreeOp.CHILDREN)) { + for (Member childMember : member.getChildMembers()) { + list.add(childMember); + } + } + // Lastly, add siblings which occur after the member itself. They + // occur after all of the descendants in the hierarchical ordering. + if (remainingSiblingsList != null) { + list.addAll(remainingSiblingsList); + } + return list; + } + + private static void addDescendants(List list, Member member) { + for (Member childMember : member.getChildMembers()) { + list.add(childMember); + addDescendants(list, childMember); + } } } diff --git a/src/mondrian/olap4j/MondrianOlap4jDimension.java b/src/mondrian/olap4j/MondrianOlap4jDimension.java index 256782e..8b87646 100644 --- a/src/mondrian/olap4j/MondrianOlap4jDimension.java +++ b/src/mondrian/olap4j/MondrianOlap4jDimension.java @@ -9,11 +9,10 @@ */ package mondrian.olap4j; -import org.olap4j.metadata.Dimension; -import org.olap4j.metadata.Hierarchy; -import org.olap4j.metadata.NamedList; -import org.olap4j.metadata.Member; +import mondrian.olap.DimensionType; +import mondrian.olap.Util; import org.olap4j.OlapException; +import org.olap4j.metadata.*; import java.util.Locale; @@ -47,35 +46,48 @@ public int hashCode() { } public NamedList getHierarchies() { - throw new UnsupportedOperationException(); + final NamedList list = + new NamedListImpl(); + final MondrianOlap4jConnection olap4jConnection = + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; + for (mondrian.olap.Hierarchy hierarchy : dimension.getHierarchies()) { + list.add(olap4jConnection.toOlap4j(hierarchy)); + } + return (NamedList) list; } public Hierarchy getDefaultHierarchy() { return getHierarchies().get(0); } - public NamedList getRootMembers() throws OlapException { - throw new UnsupportedOperationException(); - } - public Type getDimensionType() throws OlapException { - throw new UnsupportedOperationException(); + final DimensionType dimensionType = dimension.getDimensionType(); + switch (dimensionType) { + case StandardDimension: + return Type.OTHER; + case TimeDimension: + return Type.TIME; + default: + throw Util.unexpected(dimensionType); + } } public String getName() { - throw new UnsupportedOperationException(); + return dimension.getName(); } public String getUniqueName() { - throw new UnsupportedOperationException(); + return dimension.getUniqueName(); } public String getCaption(Locale locale) { - throw new UnsupportedOperationException(); + // TODO: locale caption + return dimension.getCaption(); } public String getDescription(Locale locale) { - throw new UnsupportedOperationException(); + // TODO: locale description + return dimension.getDescription(); } } diff --git a/src/mondrian/olap4j/MondrianOlap4jHierarchy.java b/src/mondrian/olap4j/MondrianOlap4jHierarchy.java index 4c8ed79..05d5fdc 100644 --- a/src/mondrian/olap4j/MondrianOlap4jHierarchy.java +++ b/src/mondrian/olap4j/MondrianOlap4jHierarchy.java @@ -21,7 +21,7 @@ * @version $Id$ * @since May 25, 2007 */ -class MondrianOlap4jHierarchy implements Hierarchy { +class MondrianOlap4jHierarchy implements Hierarchy, Named { private final MondrianOlap4jSchema olap4jSchema; private final mondrian.olap.Hierarchy hierarchy; @@ -48,31 +48,49 @@ public Dimension getDimension() { } public NamedList getLevels() { - if (false) { - return null; + final NamedList list = + new NamedListImpl(); + final MondrianOlap4jConnection olap4jConnection = + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; + for (mondrian.olap.Level level : hierarchy.getLevels()) { + list.add(olap4jConnection.toOlap4j(level)); } - throw new UnsupportedOperationException(); + return (NamedList) list; } public boolean hasAll() { - if (false) { - return false; - } - throw new UnsupportedOperationException(); + return hierarchy.hasAll(); } public Member getDefaultMember() { - if (false) { - return null; - } - throw new UnsupportedOperationException(); + final MondrianOlap4jConnection olap4jConnection = + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; + return olap4jConnection.toOlap4j(hierarchy.getDefaultMember()); + } + + public NamedList getRootMembers() { + final MondrianOlap4jConnection olap4jConnection = + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; + final mondrian.olap.Member[] levelMembers = + olap4jConnection.connection.getSchemaReader().getLevelMembers( + hierarchy.getLevels()[0], false); + return new AbstractNamedList() { + protected String getName(Member member) { + return member.getName(); + } + + public Member get(int index) { + return olap4jConnection.toOlap4j(levelMembers[index]); + } + + public int size() { + return levelMembers.length; + } + }; } public String getName() { - if (false) { - return null; - } - throw new UnsupportedOperationException(); + return hierarchy.getName(); } public String getUniqueName() { @@ -80,17 +98,13 @@ public String getUniqueName() { } public String getCaption(Locale locale) { - if (false) { - return null; - } - throw new UnsupportedOperationException(); + // todo: localize caption + return hierarchy.getCaption(); } public String getDescription(Locale locale) { - if (false) { - return null; - } - throw new UnsupportedOperationException(); + // todo: localize description + return hierarchy.getDescription(); } } diff --git a/src/mondrian/olap4j/MondrianOlap4jLevel.java b/src/mondrian/olap4j/MondrianOlap4jLevel.java index b40639b..9059b37 100644 --- a/src/mondrian/olap4j/MondrianOlap4jLevel.java +++ b/src/mondrian/olap4j/MondrianOlap4jLevel.java @@ -9,10 +9,15 @@ */ package mondrian.olap4j; +import mondrian.olap.*; import org.olap4j.metadata.*; +import org.olap4j.metadata.Dimension; +import org.olap4j.metadata.Hierarchy; +import org.olap4j.metadata.Level; +import org.olap4j.metadata.Member; +import org.olap4j.metadata.Property; -import java.util.Locale; -import java.util.List; +import java.util.*; /** * Implementation of {@link Level} @@ -22,7 +27,7 @@ * @version $Id$ * @since May 25, 2007 */ -class MondrianOlap4jLevel implements Level { +class MondrianOlap4jLevel implements Level, Named { private final MondrianOlap4jSchema olap4jSchema; private final mondrian.olap.Level level; @@ -60,23 +65,50 @@ public Type getLevelType() { } public NamedList getProperties() { - throw new UnsupportedOperationException(); + final NamedList list = new ArrayNamedListImpl() { + protected String getName(Property property) { + return property.getName(); + } + }; + // standard properties first + list.addAll( + Arrays.asList(Property.StandardMemberProperty.values())); + // then level-specific properties + for (mondrian.olap.Property property : level.getProperties()) { + list.add(new MondrianOlap4jProperty(property)); + } + return list; } public List getMembers() { - throw new UnsupportedOperationException(); + final MondrianOlap4jConnection olap4jConnection = + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; + final SchemaReader schemaReader = + olap4jConnection.connection.getSchemaReader(); + final mondrian.olap.Member[] levelMembers = + schemaReader.getLevelMembers(level, true); + return new AbstractList() { + public Member get(int index) { + return olap4jConnection.toOlap4j(levelMembers[index]); + } + + public int size() { + return levelMembers.length; + } + }; } public String getName() { - throw new UnsupportedOperationException(); + return level.getName(); } public String getUniqueName() { - throw new UnsupportedOperationException(); + return level.getUniqueName(); } public String getCaption(Locale locale) { - throw new UnsupportedOperationException(); + // todo: localized captions + return level.getCaption(); } public String getDescription(Locale locale) { diff --git a/src/mondrian/olap4j/MondrianOlap4jMember.java b/src/mondrian/olap4j/MondrianOlap4jMember.java index 59f328b..0ccb561 100644 --- a/src/mondrian/olap4j/MondrianOlap4jMember.java +++ b/src/mondrian/olap4j/MondrianOlap4jMember.java @@ -14,12 +14,15 @@ import org.olap4j.metadata.Hierarchy; import org.olap4j.metadata.Level; import org.olap4j.metadata.Member; +import org.olap4j.metadata.Property; import org.olap4j.OlapException; import org.olap4j.mdx.ParseTreeNode; import java.util.List; import java.util.Locale; +import mondrian.olap.*; + /** * Implementation of {@link Member} * for the Mondrian OLAP engine, @@ -38,6 +41,7 @@ class MondrianOlap4jMember implements Member, Named { MondrianOlap4jSchema olap4jSchema, mondrian.olap.Member mondrianMember) { + assert mondrianMember != null; this.olap4jSchema = olap4jSchema; this.member = mondrianMember; } @@ -56,6 +60,10 @@ public NamedList getChildMembers() { olap4jSchema.schemaReader.getMemberChildren( member); return new AbstractNamedList() { + protected String getName(MondrianOlap4jMember member) { + return member.getName(); + } + public MondrianOlap4jMember get(int index) { return new MondrianOlap4jMember(olap4jSchema, children[index]); } @@ -67,7 +75,11 @@ public int size() { } public Member getParentMember() { - return new MondrianOlap4jMember(olap4jSchema, member); + final mondrian.olap.Member parentMember = member.getParentMember(); + if (parentMember == null) { + return null; + } + return new MondrianOlap4jMember(olap4jSchema, parentMember); } public Level getLevel() { @@ -85,7 +97,11 @@ public Dimension getDimension() { } public Type getMemberType() { - throw new UnsupportedOperationException(); + return Type.valueOf(member.getMemberType().name()); + } + + public boolean isAll() { + return member.isAll(); } public boolean isChildOrEqualTo(Member member) { @@ -113,23 +129,26 @@ public boolean isCalculatedInQuery() { } public Object getPropertyValue(Property property) { - throw new UnsupportedOperationException(); + return member.getPropertyValue(property.getName()); } public String getPropertyFormattedValue(Property property) { - throw new UnsupportedOperationException(); + return member.getPropertyFormattedValue(property.getName()); } public void setProperty(Property property, Object value) throws OlapException { - throw new UnsupportedOperationException(); + member.setProperty(property.getName(), value); } - public List getProperties() { - throw new UnsupportedOperationException(); + public NamedList getProperties() { + return getLevel().getProperties(); } public int getOrdinal() { - throw new UnsupportedOperationException(); + final Number ordinal = + (Number) member.getPropertyValue( + Property.StandardMemberProperty.MEMBER_ORDINAL.getName()); + return ordinal.intValue(); } public boolean isHidden() { @@ -145,7 +164,7 @@ public Member getDataMember() { } public String getName() { - throw new UnsupportedOperationException(); + return member.getName(); } public String getUniqueName() { @@ -159,7 +178,6 @@ public String getCaption(Locale locale) { public String getDescription(Locale locale) { throw new UnsupportedOperationException(); } - } // End MondrianOlap4jMember.java diff --git a/src/mondrian/olap4j/MondrianOlap4jNamedSet.java b/src/mondrian/olap4j/MondrianOlap4jNamedSet.java new file mode 100644 index 0000000..54f118b --- /dev/null +++ b/src/mondrian/olap4j/MondrianOlap4jNamedSet.java @@ -0,0 +1,67 @@ +/* +// 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 +// All Rights Reserved. +// You must accept the terms of that agreement to use this software. +*/ +package mondrian.olap4j; + +import org.olap4j.metadata.NamedSet; +import org.olap4j.metadata.Cube; +import org.olap4j.mdx.ParseTreeNode; + +import java.util.Locale; + +/** + * Implementation of {@link org.olap4j.metadata.NamedSet} + * for the Mondrian OLAP engine. + * + * @author jhyde + * @version $Id: $ + * @since Nov 12, 2007 + */ +public class MondrianOlap4jNamedSet implements NamedSet, Named { + private final MondrianOlap4jCube olap4jCube; + private mondrian.olap.NamedSet namedSet; + + MondrianOlap4jNamedSet( + MondrianOlap4jCube olap4jCube, + mondrian.olap.NamedSet namedSet) + { + this.olap4jCube = olap4jCube; + this.namedSet = namedSet; + } + + public Cube getCube() { + return olap4jCube; + } + + public ParseTreeNode getExpression() { + final MondrianOlap4jConnection olap4jConnection = + olap4jCube.olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData + .olap4jConnection; + return olap4jConnection.toOlap4j(namedSet.getExp()); + } + + public String getName() { + return namedSet.getName(); + } + + public String getUniqueName() { + return namedSet.getUniqueName(); + } + + public String getCaption(Locale locale) { + // todo: i18n + return namedSet.getCaption(); + } + + public String getDescription(Locale locale) { + // todo: i18n + return namedSet.getDescription(); + } +} + +// End MondrianOlap4jNamedSet.java diff --git a/src/mondrian/olap4j/MondrianOlap4jProperty.java b/src/mondrian/olap4j/MondrianOlap4jProperty.java new file mode 100644 index 0000000..64b32b2 --- /dev/null +++ b/src/mondrian/olap4j/MondrianOlap4jProperty.java @@ -0,0 +1,61 @@ +/* +// 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 +// All Rights Reserved. +// You must accept the terms of that agreement to use this software. +*/ +package mondrian.olap4j; + +import org.olap4j.metadata.Property; + +import java.util.Locale; + +/** + * Implementation of {@link org.olap4j.metadata.Property} + * for the Mondrian OLAP engine, + * as a wrapper around a mondrian + * {@link mondrian.olap.Property}. + * + * @author jhyde + * @version $Id: $ + * @since Nov 12, 2007 + */ +class MondrianOlap4jProperty implements Property, Named { + private final mondrian.olap.Property property; + + MondrianOlap4jProperty(mondrian.olap.Property property) { + this.property = property; + } + + public Datatype getDatatype() { + return Datatype.valueOf(property.getType().name()); + } + + public Scope getScope() { + return property.isCellProperty() + ? Scope.CELL + : Scope.MEMBER; + } + + public String getName() { + return property.name; + } + + public String getUniqueName() { + return property.name; + } + + public String getCaption(Locale locale) { + // todo: i18n + return property.getCaption(); + } + + public String getDescription(Locale locale) { + // todo: i18n + return property.getDescription(); + } +} + +// End MondrianOlap4jProperty.java diff --git a/src/mondrian/olap4j/NamedListImpl.java b/src/mondrian/olap4j/NamedListImpl.java index 0f02f30..15e1f4d 100644 --- a/src/mondrian/olap4j/NamedListImpl.java +++ b/src/mondrian/olap4j/NamedListImpl.java @@ -9,10 +9,6 @@ */ package mondrian.olap4j; -import org.olap4j.metadata.NamedList; - -import java.util.ArrayList; - /** * Implementation of {@link org.olap4j.metadata.NamedList} which uses * {@link java.util.ArrayList} for storage and assumes that elements implement @@ -23,26 +19,10 @@ * @since May 23, 2007 */ class NamedListImpl - extends ArrayList - implements NamedList { - - public T get(String name) { - for (T t : this) { - if (t.getName().equals(name)) { - return t; - } - } - return null; - } - - public int indexOfName(String name) { - for (int i = 0; i < size(); ++i) { - T t = get(i); - if (t.getName().equals(name)) { - return i; - } - } - return -1; + extends ArrayNamedListImpl +{ + protected final String getName(T t) { + return t.getName(); } } diff --git a/src/org/olap4j/CellSetAxisMetaData.java b/src/org/olap4j/CellSetAxisMetaData.java index bd31f0c..58203fa 100755 --- a/src/org/olap4j/CellSetAxisMetaData.java +++ b/src/org/olap4j/CellSetAxisMetaData.java @@ -9,8 +9,7 @@ */ package org.olap4j; -import org.olap4j.metadata.Hierarchy; -import org.olap4j.metadata.Property; +import org.olap4j.metadata.*; import java.util.List; @@ -68,7 +67,7 @@ public interface CellSetAxisMetaData { /** * Returns the member properties which are returned on this axis. */ - List getProperties(); + NamedList getProperties(); } // End CellSetAxisMetaData.java diff --git a/src/org/olap4j/CellSetMetaData.java b/src/org/olap4j/CellSetMetaData.java index efed5db..ec5c9ed 100755 --- a/src/org/olap4j/CellSetMetaData.java +++ b/src/org/olap4j/CellSetMetaData.java @@ -9,8 +9,7 @@ */ package org.olap4j; -import org.olap4j.metadata.Property; -import org.olap4j.metadata.Cube; +import org.olap4j.metadata.*; import java.sql.ResultSetMetaData; import java.util.List; @@ -45,7 +44,7 @@ public interface CellSetMetaData extends ResultSetMetaData, OlapWrapper { * * @return list of cell properties */ - List getCellProperties(); + NamedList getCellProperties(); /** * Returns the Cube which was referenced in this statement. @@ -59,7 +58,7 @@ public interface CellSetMetaData extends ResultSetMetaData, OlapWrapper { * * @return list of metadata describing each result axis */ - List getAxesMetaData(); + NamedList getAxesMetaData(); } // End CellSetMetaData.java diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMetaData.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMetaData.java index 042e3f8..400c262 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMetaData.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSetMetaData.java @@ -8,15 +8,13 @@ */ package org.olap4j.driver.xmla; -import org.olap4j.CellSetMetaData; +import mondrian.olap.Util; import org.olap4j.CellSetAxisMetaData; -import org.olap4j.metadata.Property; -import org.olap4j.metadata.Cube; +import org.olap4j.CellSetMetaData; +import org.olap4j.metadata.*; -import java.util.List; import java.sql.SQLException; - -import mondrian.olap.Util; +import java.util.List; /** * Implementation of {@link org.olap4j.CellSetMetaData} @@ -37,15 +35,15 @@ class XmlaOlap4jCellSetMetaData implements CellSetMetaData { // implement CellSetMetaData - public List getCellProperties() { - throw new UnsupportedOperationException(); + public NamedList getCellProperties() { + throw Util.needToImplement(this); } public Cube getCube() { throw Util.needToImplement(this); } - public List getAxesMetaData() { + public NamedList getAxesMetaData() { throw Util.needToImplement(this); } diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java index 181b22f..5fa1fd0 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java @@ -9,8 +9,7 @@ package org.olap4j.driver.xmla; import org.olap4j.*; -import org.olap4j.mdx.parser.MdxParserFactory; -import org.olap4j.mdx.parser.MdxParser; +import org.olap4j.mdx.parser.*; import org.olap4j.mdx.parser.impl.DefaultMdxParserImpl; import org.olap4j.metadata.Catalog; import org.olap4j.metadata.NamedList; @@ -315,6 +314,10 @@ public MdxParserFactory getParserFactory() { public MdxParser createMdxParser(OlapConnection connection) { return new DefaultMdxParserImpl(connection); } + + public MdxValidator createMdxValidator(OlapConnection connection) { + throw Util.needToImplement(this); + } }; } diff --git a/src/org/olap4j/mdx/AxisNode.java b/src/org/olap4j/mdx/AxisNode.java index 98ef344..b34b587 100644 --- a/src/org/olap4j/mdx/AxisNode.java +++ b/src/org/olap4j/mdx/AxisNode.java @@ -140,7 +140,8 @@ public List getDimensionProperties() { } public Type getType() { - // not an expression + // An axis is not an expression, so does not have a type. + // Try AxisNode.getExpression().getType() instead. return null; } } diff --git a/src/org/olap4j/mdx/CallNode.java b/src/org/olap4j/mdx/CallNode.java index 0c93ffb..56663db 100644 --- a/src/org/olap4j/mdx/CallNode.java +++ b/src/org/olap4j/mdx/CallNode.java @@ -41,9 +41,10 @@ public class CallNode implements ParseTreeNode { private final Syntax syntax; private final List argList; private final ParseRegion region; + private Type type; /** - * Creates an CallNode. + * Creates a CallNode. * *

      The syntax argument determines whether this is a prefix, * infix or postfix operator, a function call, and so forth. @@ -51,6 +52,10 @@ public class CallNode implements ParseTreeNode { *

      The list of arguuments 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} + * after validation. + * + * @param region Region of source code * @param name Name of operator or function * @param syntax Syntax of call * @param args List of zero or more arguments @@ -97,6 +102,7 @@ public CallNode( *

      The list of arguuments args must be specified, even if * there are zero arguments, and each argument must be not null. * + * @param region Region of source code * @param name Name of operator or function * @param syntax Syntax of call * @param args List of zero or more arguments @@ -114,8 +120,22 @@ public ParseRegion getRegion() { return region; } + /** + * Sets the type of this CallNode. + * + *

      Typically, this method would be called by the validator when it has + * deduced the argument types, chosen between any overloaded functions + * or operators, and determined the result type of the function or + * operator. + * + * @param type Result type of this call + */ + public void setType(Type type) { + this.type = type; + } + public Type getType() { - throw new UnsupportedOperationException(); + return type; } public void unparse(ParseTreeWriter writer) { diff --git a/src/org/olap4j/mdx/CubeNode.java b/src/org/olap4j/mdx/CubeNode.java new file mode 100644 index 0000000..9ba4c33 --- /dev/null +++ b/src/org/olap4j/mdx/CubeNode.java @@ -0,0 +1,72 @@ +/* +// $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) 2007-2007 Julian Hyde +// All Rights Reserved. +// You must accept the terms of that agreement to use this software. +*/ +package org.olap4j.mdx; + +import org.olap4j.metadata.Cube; +import org.olap4j.type.CubeType; +import org.olap4j.type.Type; + +/** + * Usage of a {@link org.olap4j.metadata.Cube} as an expression in an MDX + * parse tree. + * + * @author jhyde + * @version $Id$ + * @since Jun 4, 2007 + */ +public class CubeNode implements ParseTreeNode { + private final ParseRegion region; + private final Cube cube; + + /** + * Creates a CubeNode. + * + * @param region Region of source code + * @param cube Cube + */ + public CubeNode( + ParseRegion region, + Cube cube) + { + this.region = region; + this.cube = cube; + } + + public ParseRegion getRegion() { + return region; + } + + /** + * Returns the Cube used in this expression. + * + * @return cube used in this expression + */ + public Cube getCube() { + return cube; + } + + public T accept(ParseTreeVisitor visitor) { + return visitor.visit(this); + } + + public Type getType() { + return new CubeType(cube); + } + + public void unparse(ParseTreeWriter writer) { + writer.getPrintWriter().print(cube.getUniqueName()); + } + + public String toString() { + return cube.getUniqueName(); + } +} + +// End CubeNode.java diff --git a/src/org/olap4j/mdx/MdxValidator.java b/src/org/olap4j/mdx/DefaultMdxValidatorImpl.java similarity index 91% rename from src/org/olap4j/mdx/MdxValidator.java rename to src/org/olap4j/mdx/DefaultMdxValidatorImpl.java index e7240c0..de701f4 100644 --- a/src/org/olap4j/mdx/MdxValidator.java +++ b/src/org/olap4j/mdx/DefaultMdxValidatorImpl.java @@ -14,6 +14,8 @@ import org.olap4j.type.TypeUtil; import org.olap4j.type.Type; +import org.olap4j.mdx.parser.MdxValidator; +import org.olap4j.OlapException; /** * Visitor which passes over a tree of MDX nodes, checks that they are valid, @@ -25,14 +27,20 @@ * @version $Id$ * @since Jun 4, 2007 */ -class MdxValidator implements ParseTreeVisitor { +class DefaultMdxValidatorImpl + implements ParseTreeVisitor, MdxValidator +{ private Stack scalarStack = new Stack(); private final SelectNode selectNode; - MdxValidator(SelectNode selectNode) { + protected DefaultMdxValidatorImpl(SelectNode selectNode) { this.selectNode = selectNode; } + public SelectNode validateSelect(SelectNode selectNode) throws OlapException { + return null; + } + public ParseTreeNode visit(SelectNode selectNode) { if (false) { return null; @@ -47,10 +55,6 @@ public ParseTreeNode visit(AxisNode axis) { throw new UnsupportedOperationException(); } - /** - * Resolves identifiers into objects. - * - */ public ParseTreeNode visit(WithMemberNode withMemberNode) { ParseTreeNode expression = acceptScalar(withMemberNode.getExpression()); withMemberNode.setExpression(expression); @@ -66,10 +70,6 @@ public ParseTreeNode visit(WithMemberNode withMemberNode) { return withMemberNode; } - /** - * Resolves identifiers into objects. - * - */ public ParseTreeNode visit(WithSetNode withSetNode) { ParseTreeNode expression = acceptScalar(withSetNode.getExpression()); withSetNode.setExpression(expression); @@ -105,6 +105,13 @@ public ParseTreeNode visit(ParameterNode parameterNode) { throw new UnsupportedOperationException(); } + public ParseTreeNode visit(CubeNode cubeNode) { + if (false) { + return null; + } + throw new UnsupportedOperationException(); + } + public ParseTreeNode visit(DimensionNode dimensionNode) { if (false) { return null; @@ -206,4 +213,4 @@ private ParseTreeNode lookup( } } -// End MdxValidator.java +// End DefaultMdxValidatorImpl.java diff --git a/src/org/olap4j/mdx/HierarchyNode.java b/src/org/olap4j/mdx/HierarchyNode.java index ef83fe5..3f24aac 100644 --- a/src/org/olap4j/mdx/HierarchyNode.java +++ b/src/org/olap4j/mdx/HierarchyNode.java @@ -51,7 +51,9 @@ public T accept(ParseTreeVisitor visitor) { } public Type getType() { - return HierarchyType.forHierarchy(hierarchy); + return new HierarchyType( + hierarchy.getDimension(), + hierarchy); } public void unparse(ParseTreeWriter writer) { diff --git a/src/org/olap4j/mdx/LevelNode.java b/src/org/olap4j/mdx/LevelNode.java index fb08cfc..01ef00f 100644 --- a/src/org/olap4j/mdx/LevelNode.java +++ b/src/org/olap4j/mdx/LevelNode.java @@ -51,7 +51,10 @@ public T accept(ParseTreeVisitor visitor) { } public Type getType() { - return LevelType.forLevel(level); + return new LevelType( + level.getDimension(), + level.getHierarchy(), + level); } public void unparse(ParseTreeWriter writer) { diff --git a/src/org/olap4j/mdx/MemberNode.java b/src/org/olap4j/mdx/MemberNode.java index d9ad0e3..ef8efda 100644 --- a/src/org/olap4j/mdx/MemberNode.java +++ b/src/org/olap4j/mdx/MemberNode.java @@ -51,7 +51,11 @@ public T accept(ParseTreeVisitor visitor) { } public Type getType() { - return MemberType.forMember(member); + return new MemberType( + member.getDimension(), + member.getHierarchy(), + member.getLevel(), + member); } public void unparse(ParseTreeWriter writer) { diff --git a/src/org/olap4j/mdx/ParseTreeNode.java b/src/org/olap4j/mdx/ParseTreeNode.java index a272473..a429ad5 100644 --- a/src/org/olap4j/mdx/ParseTreeNode.java +++ b/src/org/olap4j/mdx/ParseTreeNode.java @@ -36,7 +36,7 @@ public interface ParseTreeNode { /** * Returns the type of this expression. * - *

      Returns null if this is not an expression, for instance a + *

      Returns null if this node is not an expression, for instance a * SELECT node. * * @return type of this expression diff --git a/src/org/olap4j/mdx/ParseTreeVisitor.java b/src/org/olap4j/mdx/ParseTreeVisitor.java index bdebad1..b62647e 100644 --- a/src/org/olap4j/mdx/ParseTreeVisitor.java +++ b/src/org/olap4j/mdx/ParseTreeVisitor.java @@ -67,6 +67,13 @@ public interface ParseTreeVisitor { */ T visit(ParameterNode parameterNode); + /** + * Visits a usage of a {@link org.olap4j.metadata.Cube} in a query. + * + * @see CubeNode#accept(ParseTreeVisitor) + */ + T visit(CubeNode cubeNode); + /** * Visits a usage of a {@link org.olap4j.metadata.Dimension} in a query. * diff --git a/src/org/olap4j/mdx/SelectNode.java b/src/org/olap4j/mdx/SelectNode.java index b60726c..f88f0a5 100644 --- a/src/org/olap4j/mdx/SelectNode.java +++ b/src/org/olap4j/mdx/SelectNode.java @@ -11,9 +11,9 @@ import org.olap4j.type.Type; -import java.util.List; -import java.util.ArrayList; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; /** * Parse tree model for an MDX SELECT statement. @@ -28,29 +28,31 @@ public class SelectNode implements ParseTreeNode { private final List axisList; private final AxisNode slicerAxis; private final List cellPropertyList; - private IdentifierNode cubeName; + private ParseTreeNode from; /** * Creates a SelectNode. * + * @param region Region of source code from which this node was created * @param withList List of members and sets defined in this query using * a WITH clause * @param axisList List of axes - * @param cubeName Name of cube + * @param from Name of cube * @param slicerAxis Slicer axis + * @param cellPropertyList List of properties */ public SelectNode( ParseRegion region, List withList, List axisList, - IdentifierNode cubeName, + ParseTreeNode from, AxisNode slicerAxis, List cellPropertyList) { this.region = region; this.withList = withList; this.axisList = axisList; - this.cubeName = cubeName; + this.from = from; this.slicerAxis = slicerAxis; this.cellPropertyList = cellPropertyList; } @@ -74,7 +76,8 @@ public T accept(ParseTreeVisitor visitor) { } public Type getType() { - throw new UnsupportedOperationException(); + // not an expression, so has no type + return null; } public void unparse(ParseTreeWriter writer) { @@ -98,7 +101,7 @@ public void unparse(ParseTreeWriter writer) { } pw.println(); pw.print("FROM "); - cubeName.unparse(writer); + from.unparse(writer); if (slicerAxis != null) { pw.println(); pw.print("WHERE "); @@ -129,12 +132,28 @@ public AxisNode getSlicerAxis() { return slicerAxis; } - public IdentifierNode getCubeName() { - return cubeName; + /** + * Returns the node representing the FROM clause of this SELECT statement. + * The node is typically an {@link IdentifierNode} or a {@link CubeNode}. + * + * @return FROM clause + */ + public ParseTreeNode getFrom() { + return from; } - public void setCubeName(IdentifierNode cubeName) { - this.cubeName = cubeName; + /** + * Sets the FROM clause of this SELECT statement. + * + *

      fromNode should typically by an + * {@link org.olap4j.mdx.IdentifierNode} containing the cube name, or + * a {@link org.olap4j.mdx.CubeNode} referencing an explicit + * {@link org.olap4j.metadata.Cube} object. + * + * @param fromNode FROM clause + */ + public void setFrom(ParseTreeNode fromNode) { + this.from = fromNode; } } diff --git a/src/org/olap4j/mdx/parser/MdxParser.java b/src/org/olap4j/mdx/parser/MdxParser.java index 8d4bebc..eae1ac2 100644 --- a/src/org/olap4j/mdx/parser/MdxParser.java +++ b/src/org/olap4j/mdx/parser/MdxParser.java @@ -20,7 +20,6 @@ * from different threads. * * @see MdxParserFactory - * @see org.olap4j.mdx.parser.impl.DefaultMdxParserImpl * * @author jhyde * @version $Id$ diff --git a/src/org/olap4j/mdx/parser/MdxParserFactory.java b/src/org/olap4j/mdx/parser/MdxParserFactory.java index 74a644a..d97c5ea 100644 --- a/src/org/olap4j/mdx/parser/MdxParserFactory.java +++ b/src/org/olap4j/mdx/parser/MdxParserFactory.java @@ -26,6 +26,14 @@ public interface MdxParserFactory { * @return MDX parser */ MdxParser createMdxParser(OlapConnection connection); + + /** + * Creates an MDX validator. + * + * @param connection Connection in which to resolve identifiers + * @return MDX validator + */ + MdxValidator createMdxValidator(OlapConnection connection); } // End MdxParserFactory.java diff --git a/src/org/olap4j/mdx/parser/MdxValidator.java b/src/org/olap4j/mdx/parser/MdxValidator.java new file mode 100644 index 0000000..79746d4 --- /dev/null +++ b/src/org/olap4j/mdx/parser/MdxValidator.java @@ -0,0 +1,57 @@ +/* +// $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) 2006-2006 Julian Hyde +// All Rights Reserved. +// You must accept the terms of that agreement to use this software. +*/ +package org.olap4j.mdx.parser; + +import org.olap4j.OlapException; +import org.olap4j.mdx.SelectNode; + +/** + * Validator for the MDX query language. + * + *

      A validator is reusable but not reentrant: you can call + * {@link #validateSelect(org.olap4j.mdx.SelectNode)} several times, but not at + * the same time from different threads. + * + *

      To create a validator, use the + * {@link MdxParserFactory#createMdxValidator(org.olap4j.OlapConnection)} + * method. + * + * @see MdxParserFactory + * @see MdxParser + * + * @author jhyde + * @version $Id$ + * @since Aug 22, 2006 + */ +public interface MdxValidator { + /** + * Validates an MDX SELECT statement. + * + *

      The SelectNode representing the SELECT statement may have been + * created by an {@link MdxParser}, or it may have been built + * programmatically. + * + *

      If the parse tree is invalid, throws an {@link OlapException}. + * + *

      If it is valid, returns a parse tree. This parse tree may or may not + * be the same parse tree passed as an argument. After validation, you can + * ascertain the type of each node of the parse tree by calling its + * {@link org.olap4j.mdx.ParseTreeNode#getType()} method. + * + * @param selectNode Parse tree node representing a SELECT statement + * + * @return Validated parse tree + * + * @throws OlapException if node is invalid + */ + SelectNode validateSelect(SelectNode selectNode) throws OlapException; +} + +// End MdxValidator.java diff --git a/src/org/olap4j/metadata/Cube.java b/src/org/olap4j/metadata/Cube.java index 6b428fe..f549c87 100644 --- a/src/org/olap4j/metadata/Cube.java +++ b/src/org/olap4j/metadata/Cube.java @@ -9,9 +9,7 @@ */ package org.olap4j.metadata; -import java.util.List; -import java.util.Locale; -import java.util.Collection; +import java.util.*; /** * Central metadata object for representation of multidimensional data. @@ -87,7 +85,71 @@ public interface Cube extends MetadataElement { */ Collection getSupportedLocales(); + /** + * Finds a member in the current Cube based upon its fully-qualified name. + * Returns the member, or null if there is no member with this name. + * + *

      The fully-qualified name starts with the name of the dimension, + * followed by the name of a root member, and continues with the name of + * each successive member on the path from the root member. If a member's + * name is unique within its level, preceding member name can be omitted. + * + *

      For example, + * lookupMember("Product", "Food") + * and + * lookupMember("Product", "All Products", "Food") + * are both valid ways to locate the "Food" member of the "Product" + * dimension. + * + * @param nameParts Components of the fully-qualified member name + * @return member with the given name, or null if not found + */ Member lookupMember(String... nameParts); + + /** + * Finds a collection of members in the current Cube related to a given + * member. + * + *

      The method first looks up a member with the given fully-qualified + * name as for {@link #lookupMember(String[])}, then applies the set of + * tree-operations to find related members. + * + *

      The returned collection is sorted in hierarchical order. If no member + * is found with the given name, the collection is empty. + * + *

      For example, + * + *

      + * lookupMembers( + * EnumSet.of(TreeOp.ANCESTORS, TreeOp.CHILDREN), + * "Time", "1997", "Q2") + *
      + * + * returns + * + *
      + * [Time].[1997], [Time].[1997].[Q2].[4], + * [Time].[1997].[Q2].[5], [Time].[1997].[Q2].[6] + *
      + * + *

      The fully-qualified name starts with the name of the dimension, + * followed by the name of a root member, and continues with the name of + * each successive member on the path from the root member. If a member's + * name is unique within its level, preceding member name can be omitted. + * + *

      For example, + * lookupMember("Product", "Food") + * and + * lookupMember("Product", "All Products", "Food") + * are both valid ways to locate the "Food" member of the "Product" + * dimension. + * + * @param nameParts Components of the fully-qualified member name + * @return member with the given name, or null if not found + */ + List lookupMembers( + Set treeOps, + String... nameParts); } // End Cube.java diff --git a/src/org/olap4j/metadata/Dimension.java b/src/org/olap4j/metadata/Dimension.java index 349f7ba..4fb8257 100644 --- a/src/org/olap4j/metadata/Dimension.java +++ b/src/org/olap4j/metadata/Dimension.java @@ -42,21 +42,6 @@ public interface Dimension extends MetadataElement { */ NamedList getHierarchies(); - /** - * Returns the root member or members of this Dimension. - * - *

      If the dimension has an 'all' member, then this will be the sole - * root member. - * - *

      The caller should assume that the list is immutable; - * if the caller modifies the list, behavior is undefined.

      - * - * @return root members of this hierarchy - * - * @throws OlapException if database error occurs - */ - NamedList getRootMembers() throws OlapException; - /** * Returns the type of this Dimension. * diff --git a/src/org/olap4j/metadata/Hierarchy.java b/src/org/olap4j/metadata/Hierarchy.java index 1c9c55d..d5d2aab 100644 --- a/src/org/olap4j/metadata/Hierarchy.java +++ b/src/org/olap4j/metadata/Hierarchy.java @@ -9,6 +9,8 @@ */ package org.olap4j.metadata; +import org.olap4j.OlapException; + /** * An organization of the set of {@link Member}s in a {@link Dimension} and * their positions relative to one another. @@ -60,6 +62,19 @@ public interface Hierarchy extends MetadataElement { * @return the default member of this hierarchy */ Member getDefaultMember(); + + /** + * Returns the root member or members of this Dimension. + * + *

      If the dimension has an 'all' member, then this will be the sole + * root member. + * + *

      The caller should assume that the list is immutable; + * if the caller modifies the list, behavior is undefined.

      + * + * @return root members of this hierarchy + */ + NamedList getRootMembers(); } // End Hierarchy.java diff --git a/src/org/olap4j/metadata/Level.java b/src/org/olap4j/metadata/Level.java index 7e90f1a..da5fa5b 100644 --- a/src/org/olap4j/metadata/Level.java +++ b/src/org/olap4j/metadata/Level.java @@ -64,7 +64,7 @@ public interface Level extends MetadataElement { * * @see org.olap4j.OlapDatabaseMetaData#getProperties * - * @return properties of this level + * @return properties of this Level */ NamedList getProperties(); @@ -74,7 +74,12 @@ public interface Level extends MetadataElement { *

      Some levels have a very many members. In this case, calling this * method may be expensive in space and/or time and is not recommended. * - * @return List of members in this level + *

      The members of a level do not have unique names, so unlike + * {@link Hierarchy#getRootMembers()} and + * {@link Member#getChildMembers()} the result type + * is a {@link List} not a {@link NamedList}. + * + * @return List of members in this Level */ List getMembers(); diff --git a/src/org/olap4j/metadata/Member.java b/src/org/olap4j/metadata/Member.java index 55b460a..d1ca29b 100644 --- a/src/org/olap4j/metadata/Member.java +++ b/src/org/olap4j/metadata/Member.java @@ -75,6 +75,21 @@ public interface Member extends MetadataElement { */ Type getMemberType(); + /** + * Returns whether this Member represents the aggregation of all members + * in its Dimension. + * + *

      An 'all' member is always the root of its Hierarchy; that is, + * its parent member is the null member, and + * {@link Hierarchy#getRootMembers()} returns the 'all' + * member and no others. Some hierarchies do not have an 'all' member. + * + * @see Hierarchy#hasAll() + * + * @return whether this Member is the 'all' member of its Dimension + */ + boolean isAll(); + /** * Enumeration of types of members. * @@ -197,8 +212,14 @@ private Type(int ordinal) { /** * Returns the definitions of the properties this member may have. + * + *

      For many providers, properties are defined against a Level, so result + * of this method will be identical to + * member.getLevel().{@link Level#getProperties() getProperties}(). + * + * @return properties of this Member */ - List getProperties(); + NamedList getProperties(); /** * Returns the ordinal of the member. diff --git a/src/org/olap4j/metadata/NamedList.java b/src/org/olap4j/metadata/NamedList.java index b66715c..8040d3c 100644 --- a/src/org/olap4j/metadata/NamedList.java +++ b/src/org/olap4j/metadata/NamedList.java @@ -23,13 +23,25 @@ public interface NamedList extends List { /** * Retrieves a member by name. * + * @param name name of the element to return + * * @see #get(int) + * + * @return the element of the list with the specified name, or null if + * there is no such element */ E get(String name); /** * Returns the position where a member of a given name is found, or -1 * if the member is not present. + * + * @param name name of the element to return + * + * @return the index of element of the list with the specified name, or -1 + * if there is no such element + * + * @see #indexOf(Object) */ int indexOfName(String name); } diff --git a/src/org/olap4j/metadata/NamedSet.java b/src/org/olap4j/metadata/NamedSet.java index 176a272..581f1e6 100644 --- a/src/org/olap4j/metadata/NamedSet.java +++ b/src/org/olap4j/metadata/NamedSet.java @@ -9,6 +9,8 @@ */ package org.olap4j.metadata; +import org.olap4j.mdx.ParseTreeNode; + /** * Metadata object describing a named set defined against a {@link Cube}. * @@ -24,6 +26,13 @@ public interface NamedSet extends MetadataElement { * @return cube this named set belongs to */ Cube getCube(); + + /** + * Returns the expression which gives the value of this NamedSet. + * + * @return expression + */ + ParseTreeNode getExpression(); } // End NamedSet.java diff --git a/src/org/olap4j/metadata/Property.java b/src/org/olap4j/metadata/Property.java index 31bdef7..f896a29 100644 --- a/src/org/olap4j/metadata/Property.java +++ b/src/org/olap4j/metadata/Property.java @@ -25,7 +25,7 @@ public interface Property extends MetadataElement { * * @return datatype of this Property */ - Datatype getType(); + Datatype getDatatype(); /** * Returns the scope of this property. @@ -34,8 +34,6 @@ public interface Property extends MetadataElement { */ Scope getScope(); - boolean isInternal(); - /** * Enumeration of the scope of a Property: whether it belongs to a member * or a cell. @@ -231,7 +229,7 @@ private StandardMemberProperty( int ordinal, boolean internal, String description) { - assert ordinal == ordinal(); +// assert ordinal == ordinal(); this.internal = internal; this.type = type; this.description = description; @@ -253,7 +251,7 @@ public String getCaption(Locale locale) { return name(); } - public Datatype getType() { + public Datatype getDatatype() { return type; } @@ -356,7 +354,7 @@ private StandardCellProperty( this.description = description; } - public Datatype getType() { + public Datatype getDatatype() { return type; } diff --git a/src/org/olap4j/sample/SimpleQuerySample.java b/src/org/olap4j/sample/SimpleQuerySample.java index 783926f..a8570f5 100644 --- a/src/org/olap4j/sample/SimpleQuerySample.java +++ b/src/org/olap4j/sample/SimpleQuerySample.java @@ -144,7 +144,8 @@ void preparedStatement() throws SQLException, ClassNotFoundException { MemberType type = (MemberType) parameterMetaData.getParameterOlapType(1); Dimension dimension = type.getDimension(); assert dimension.getName().equals("Store"); - Member allStores = dimension.getRootMembers().get(0); + Member allStores = + dimension.getDefaultHierarchy().getRootMembers().get(0); Member memberUsa = allStores.getChildMembers().get("USA"); Member memberWa = memberUsa.getChildMembers().get("WA"); Member memberSeattle = memberWa.getChildMembers().get("Seattle"); @@ -236,7 +237,7 @@ private void printResult(CellSet result) { void executeSelectNode(OlapConnection connection) { // Create a query model. SelectNode query = new SelectNode(); - query.setCubeName( + query.setFrom( new IdentifierNode( new IdentifierNode.Segment("Sales"))); query.getAxisList().add( diff --git a/src/org/olap4j/type/BooleanType.java b/src/org/olap4j/type/BooleanType.java index 1d05178..f8275f6 100755 --- a/src/org/olap4j/type/BooleanType.java +++ b/src/org/olap4j/type/BooleanType.java @@ -12,6 +12,12 @@ /** * The type of a boolean expression. * + *

      An example of a boolean expression is the predicate + * + *

      + * [Measures].[Unit Sales] > 1000 + *
      + * * @author jhyde * @since Feb 17, 2005 * @version $Id$ diff --git a/src/org/olap4j/type/CubeType.java b/src/org/olap4j/type/CubeType.java index e2c6cc3..5f3fcd7 100755 --- a/src/org/olap4j/type/CubeType.java +++ b/src/org/olap4j/type/CubeType.java @@ -26,11 +26,22 @@ public class CubeType implements Type { /** * Creates a type representing a cube. + * + * @param cube Cube */ public CubeType(Cube cube) { this.cube = cube; } + /** + * Returns the cube. + * + * @return the cube + */ + public Cube getCube() { + return cube; + } + public boolean usesDimension(Dimension dimension, boolean maybe) { return false; } @@ -46,6 +57,21 @@ public Hierarchy getHierarchy() { public Level getLevel() { return null; } + + public boolean equals(Object obj) { + if (obj instanceof CubeType) { + CubeType that = (CubeType) obj; + return TypeUtil.equal(this.cube, that.cube); + } else { + return false; + } + } + + public int hashCode() { + return cube == null + ? 0 + : cube.hashCode(); + } } // End CubeType.java diff --git a/src/org/olap4j/type/DimensionType.java b/src/org/olap4j/type/DimensionType.java index 1a961bb..1ef15d1 100755 --- a/src/org/olap4j/type/DimensionType.java +++ b/src/org/olap4j/type/DimensionType.java @@ -12,7 +12,6 @@ import org.olap4j.metadata.Dimension; import org.olap4j.metadata.Hierarchy; import org.olap4j.metadata.Level; -import org.olap4j.OlapException; /** * The type of an expression which represents a Dimension. @@ -30,8 +29,8 @@ public class DimensionType implements Type { /** * Creates a type representing a dimension. * - * @param dimension Dimension that values of this type must belong to. - * Null if the dimension is unknown. + * @param dimension Dimension which values of this type must belong to, or + * null if not known */ public DimensionType(Dimension dimension) { this.dimension = dimension; @@ -52,11 +51,14 @@ public static DimensionType forType(Type type) { } public boolean usesDimension(Dimension dimension, boolean maybe) { - return this.dimension == dimension || - (maybe && this.dimension == null); + if (this.dimension == null) { + return maybe; + } else { + return this.dimension.equals(dimension); + } } - public Hierarchy getHierarchy() throws OlapException { + public Hierarchy getHierarchy() { return dimension == null ? null : dimension.getHierarchies().size() > 1 ? diff --git a/src/org/olap4j/type/HierarchyType.java b/src/org/olap4j/type/HierarchyType.java index 7ec5fea..747c6fa 100755 --- a/src/org/olap4j/type/HierarchyType.java +++ b/src/org/olap4j/type/HierarchyType.java @@ -28,8 +28,16 @@ public class HierarchyType implements Type { /** * Creates a type representing a hierarchy. + * + * @param dimension Dimension which values of this type must belong to, or + * null if not known + * + * @param hierarchy Hierarchy which values of this type must belong to, or + * null if not known */ - public HierarchyType(Dimension dimension, Hierarchy hierarchy) { + public HierarchyType( + Dimension dimension, + Hierarchy hierarchy) { this.dimension = dimension; this.hierarchy = hierarchy; StringBuilder buf = new StringBuilder("HierarchyType<"); @@ -43,17 +51,16 @@ public HierarchyType(Dimension dimension, Hierarchy hierarchy) { } - public static HierarchyType forHierarchy(Hierarchy hierarchy) { - return new HierarchyType(hierarchy.getDimension(), hierarchy); - } - public static HierarchyType forType(Type type) throws OlapException { return new HierarchyType(type.getDimension(), type.getHierarchy()); } public boolean usesDimension(Dimension dimension, boolean maybe) { - return this.dimension == dimension || - (maybe && this.dimension == null); + if (this.dimension == null) { + return maybe; + } else { + return this.dimension.equals(dimension); + } } public Dimension getDimension() { diff --git a/src/org/olap4j/type/LevelType.java b/src/org/olap4j/type/LevelType.java index fbf51eb..7cff078 100755 --- a/src/org/olap4j/type/LevelType.java +++ b/src/org/olap4j/type/LevelType.java @@ -30,12 +30,20 @@ public class LevelType implements Type { /** * Creates a type representing a level. * - * @param dimension + * @param dimension Dimension which values of this type must belong to, or + * null if not known + * * @param hierarchy Hierarchy which values of this type must belong to, or * null if not known + * * @param level Level which values of this type must belong to, or null if + * not known */ - public LevelType(Dimension dimension, Hierarchy hierarchy, Level level) { + public LevelType( + Dimension dimension, + Hierarchy hierarchy, + Level level) + { this.dimension = dimension; this.hierarchy = hierarchy; this.level = level; @@ -61,26 +69,19 @@ public LevelType(Dimension dimension, Hierarchy hierarchy, Level level) { this.digest = buf.toString(); } - public static LevelType forType(Type type) throws OlapException { + // not part of public olap4j API + private static LevelType forType(Type type) throws OlapException { return new LevelType( type.getDimension(), type.getHierarchy(), type.getLevel()); - - } - - public static LevelType forLevel(Level level) { - return new LevelType( - level.getDimension(), - level.getHierarchy(), - level); } public boolean usesDimension(Dimension dimension, boolean maybe) { if (this.dimension == null) { return maybe; } else { - return this.dimension == dimension; + return this.dimension.equals(dimension); } } diff --git a/src/org/olap4j/type/MemberType.java b/src/org/olap4j/type/MemberType.java index 28d699a..60027f0 100644 --- a/src/org/olap4j/type/MemberType.java +++ b/src/org/olap4j/type/MemberType.java @@ -29,21 +29,27 @@ public class MemberType implements Type { private final Member member; private final String digest; - public static final MemberType Unknown = new MemberType(null, null, null, null); + // not part of public olap4j public API + private static final MemberType Unknown = + new MemberType(null, null, null, null); /** * Creates a type representing a member. * - * @param dimension + * @param dimension Dimension the member belongs to, or null if not known. + * * @param hierarchy Hierarchy the member belongs to, or null if not known. + * * @param level Level the member belongs to, or null if not known + * * @param member The precise member, or null if not known */ public MemberType( - Dimension dimension, - Hierarchy hierarchy, - Level level, - Member member) { + Dimension dimension, + Hierarchy hierarchy, + Level level, + Member member) + { this.dimension = dimension; this.hierarchy = hierarchy; this.level = level; @@ -74,34 +80,6 @@ public MemberType( this.digest = buf.toString(); } - public static MemberType forDimension(Dimension dimension) { - return new MemberType(dimension, null, null, null); - } - - public static MemberType forHierarchy(Hierarchy hierarchy) { - return new MemberType( - hierarchy.getDimension(), - hierarchy, - null, - null); - } - - public static MemberType forLevel(Level level) { - return new MemberType( - level.getDimension(), - level.getHierarchy(), - level, - null); - } - - public static MemberType forMember(Member member) { - return new MemberType( - member.getDimension(), - member.getHierarchy(), - member.getLevel(), - member); - } - public String toString() { return digest; } @@ -114,16 +92,25 @@ public Level getLevel() { return level; } + /** + * Returns the member of this type, or null if not known. + * + * @return member of this type + */ + public Member getMember() { + return member; + } + public boolean usesDimension(Dimension dimension, boolean maybe) { if (this.dimension == null) { return maybe; } else { - return this.dimension == dimension || - (maybe && this.dimension == null); + return this.dimension.equals(dimension); } } - public Type getValueType() { + // not part of public olap4j API + Type getValueType() { // todo: when members have more type information (double vs. integer // vs. string), return better type if member != null. return new ScalarType(); @@ -133,7 +120,8 @@ public Dimension getDimension() { return dimension; } - public static MemberType forType(Type type) throws OlapException { + // not part of public olap4j API + static MemberType forType(Type type) throws OlapException { if (type instanceof MemberType) { return (MemberType) type; } else { diff --git a/src/org/olap4j/type/SetType.java b/src/org/olap4j/type/SetType.java index 7e80733..18a5a9c 100755 --- a/src/org/olap4j/type/SetType.java +++ b/src/org/olap4j/type/SetType.java @@ -12,7 +12,6 @@ import org.olap4j.metadata.Dimension; import org.olap4j.metadata.Hierarchy; import org.olap4j.metadata.Level; -import org.olap4j.OlapException; /** * Set type. @@ -39,6 +38,8 @@ public SetType(Type elementType) { /** * Returns the type of the elements of this set. + * + * @return element type */ public Type getElementType() { return elementType; @@ -56,7 +57,7 @@ public Dimension getDimension() { elementType.getDimension(); } - public Hierarchy getHierarchy() throws OlapException { + public Hierarchy getHierarchy() { return elementType == null ? null : elementType.getHierarchy(); } diff --git a/src/org/olap4j/type/SymbolType.java b/src/org/olap4j/type/SymbolType.java index 7dc0e59..0eddc4f 100755 --- a/src/org/olap4j/type/SymbolType.java +++ b/src/org/olap4j/type/SymbolType.java @@ -12,6 +12,28 @@ /** * The type of a symbolic expression. * + *

      Symbols are identifiers which occur in particular function calls, + * generally to indicate an option for how the function should be executed. + * They are similar to an enumerated type in other + * languages. + * + *

      For example, the optional 3rd argument to the Order function + * can be one of the symbols ASC, DESC, + * BASC, BDESC. The signature of the + * Order function is therefore + * + *

      + * Order(<Set>, <Scalar expression> [, <Symbol>]) + *
      + * + * and + * + *
      + * Order([Store].Members, [Measures].[Unit Sales], BDESC) + *
      + * + * would be a valid call to the function. + * * @author jhyde * @since Feb 17, 2005 * @version $Id$ diff --git a/src/org/olap4j/type/TupleType.java b/src/org/olap4j/type/TupleType.java index e9d1208..8155dda 100755 --- a/src/org/olap4j/type/TupleType.java +++ b/src/org/olap4j/type/TupleType.java @@ -22,14 +22,32 @@ * @version $Id$ */ public class TupleType implements Type { - public final Type[] elementTypes; + final Type[] elementTypes; + private final String digest; /** * Creates a type representing a tuple whose fields are the given types. + * + * @param elementTypes Array of field types */ public TupleType(Type[] elementTypes) { assert elementTypes != null; this.elementTypes = elementTypes.clone(); + + final StringBuilder buf = new StringBuilder("TupleType<"); + for (int i = 0; i < elementTypes.length; i++) { + Type elementType = elementTypes[i]; + if (i > 0) { + buf.append(", "); + } + buf.append(elementType.toString()); + } + buf.append(">"); + digest = buf.toString(); + } + + public String toString() { + return digest; } public boolean usesDimension(Dimension dimension, boolean maybe) { @@ -42,18 +60,19 @@ public boolean usesDimension(Dimension dimension, boolean maybe) { } public Dimension getDimension() { - throw new UnsupportedOperationException(); + return null; } public Hierarchy getHierarchy() { - throw new UnsupportedOperationException(); + return null; } public Level getLevel() { - throw new UnsupportedOperationException(); + return null; } - public Type getValueType() throws OlapException { + // not part of public olap4j API + private Type getValueType() throws OlapException { for (Type elementType : elementTypes) { if (elementType instanceof MemberType) { MemberType memberType = (MemberType) elementType; @@ -64,6 +83,21 @@ public Type getValueType() throws OlapException { } return new ScalarType(); } + + // not part of public olap4j API + boolean isUnionCompatibleWith(TupleType that) throws OlapException { + if (this.elementTypes.length != that.elementTypes.length) { + return false; + } + for (int i = 0; i < this.elementTypes.length; i++) { + if (!TypeUtil.isUnionCompatible( + this.elementTypes[i], + that.elementTypes[i])) { + return false; + } + } + return true; + } } // End TupleType.java diff --git a/src/org/olap4j/type/Type.java b/src/org/olap4j/type/Type.java index 7a66d5e..4c70edc 100644 --- a/src/org/olap4j/type/Type.java +++ b/src/org/olap4j/type/Type.java @@ -12,7 +12,6 @@ import org.olap4j.metadata.Dimension; import org.olap4j.metadata.Hierarchy; import org.olap4j.metadata.Level; -import org.olap4j.OlapException; /** * Type of an MDX expression. @@ -44,21 +43,30 @@ public interface Type { * @param dimension Dimension * @param maybe If true, returns true only if this type definitely * uses the dimension + * + * @return whether this type definitely (or if maybe is true, + * possibly) uses the given dimension */ boolean usesDimension(Dimension dimension, boolean maybe); /** * Returns the dimension of this type, or null if not known. + * + * @return dimension of this type */ Dimension getDimension(); /** * Returns the hierarchy of this type. If not applicable, throws. + * + * @return hierarchy of this type */ - Hierarchy getHierarchy() throws OlapException; + Hierarchy getHierarchy(); /** * Returns the level of this type, or null if not known. + * + * @return level of this type */ Level getLevel(); diff --git a/src/org/olap4j/type/TypeUtil.java b/src/org/olap4j/type/TypeUtil.java index ed52f9d..a9932a6 100755 --- a/src/org/olap4j/type/TypeUtil.java +++ b/src/org/olap4j/type/TypeUtil.java @@ -15,6 +15,8 @@ /** * Utility methods relating to types. * + *

      NOTE: This class is experimental. Not part of the public olap4j API. + * * @author jhyde * @since Feb 17, 2005 * @version $Id$ @@ -25,7 +27,7 @@ public class TypeUtil { * Given a set type, returns the element type. Or its element type, if it * is a set type. And so on. */ - public static Type stripSetType(Type type) { + private static Type stripSetType(Type type) { while (type instanceof SetType) { type = ((SetType) type).getElementType(); } @@ -36,7 +38,7 @@ public static Type stripSetType(Type type) { * Converts a type to a member or tuple type. * If it cannot, returns null. */ - public static Type toMemberOrTupleType(Type type) throws OlapException { + private static Type toMemberOrTupleType(Type type) throws OlapException { type = stripSetType(type); if (type instanceof TupleType) { return (TupleType) type; @@ -53,7 +55,7 @@ public static Type toMemberOrTupleType(Type type) throws OlapException { * a member type. * If it is a tuple, number, string, or boolean, returns null. */ - public static MemberType toMemberType(Type type) throws OlapException { + static MemberType toMemberType(Type type) throws OlapException { type = stripSetType(type); if (type instanceof MemberType) { return (MemberType) type; @@ -70,25 +72,21 @@ public static MemberType toMemberType(Type type) throws OlapException { * Returns whether this type is union-compatible with another. * In general, to be union-compatible, types must have the same * dimensionality. + * + * @param type1 First type + * @param type2 Second type + * @return Whether types are union-compatible + * @throws OlapException on error */ - public static boolean isUnionCompatible(Type type1, Type type2) throws OlapException { + static boolean isUnionCompatible( + Type type1, + Type type2) + throws OlapException + { if (type1 instanceof TupleType) { - TupleType tupleType1 = (TupleType) type1; - if (type2 instanceof TupleType) { - TupleType tupleType2 = (TupleType) type2; - if (tupleType1.elementTypes.length == - tupleType2.elementTypes.length) { - for (int i = 0; i < tupleType1.elementTypes.length; i++) { - if (!isUnionCompatible( - tupleType1.elementTypes[i], - tupleType2.elementTypes[i])) { - return false; - } - } - return true; - } - } - return false; + return type2 instanceof TupleType + && ((TupleType) type1).isUnionCompatibleWith( + (TupleType) type2); } else { final MemberType memberType1 = toMemberType(type1); if (memberType1 == null) { @@ -152,11 +150,15 @@ public static boolean isSet(Type type) { return type instanceof SetType; } - public static boolean couldBeMember(Type type) { + private static boolean couldBeMember(Type type) { return type instanceof MemberType || type instanceof HierarchyType || type instanceof DimensionType; } + + static boolean equal(Object o, Object p) { + return o == null ? p == null : p != null && o.equals(p); + } } // End TypeUtil.java diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java index 48552bb..21a36cb 100644 --- a/testsrc/org/olap4j/ConnectionTest.java +++ b/testsrc/org/olap4j/ConnectionTest.java @@ -9,30 +9,23 @@ */ package org.olap4j; -import junit.framework.TestCase; import junit.framework.AssertionFailedError; - -import java.sql.*; -import java.sql.Connection; -import java.sql.DriverManager; -import java.util.*; -import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; - -import org.olap4j.metadata.*; -import org.olap4j.metadata.Cube; -import org.olap4j.metadata.Dimension; -import org.olap4j.metadata.Schema; +import junit.framework.TestCase; +import mondrian.tui.XmlaSupport; +import org.olap4j.driver.xmla.XmlaOlap4jDriver; import org.olap4j.mdx.*; -import org.olap4j.mdx.parser.MdxParser; +import org.olap4j.mdx.parser.*; +import org.olap4j.metadata.*; import org.olap4j.test.TestContext; import org.olap4j.type.*; -import org.olap4j.driver.xmla.XmlaOlap4jDriver; import org.xml.sax.SAXException; -import mondrian.tui.XmlaSupport; import javax.servlet.ServletException; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.sql.*; +import java.util.*; /** * Unit test for olap4j Driver and Connection classes. @@ -649,7 +642,22 @@ public void testParsing() throws SQLException { // unparse checkUnparsedMdx(select); - // test that get error if axes do not have unique names (todo) + // test that get error if axes do not have unique names + select = + mdxParser.parseSelect( + "select {[Gender]} on columns, {[Store].Children} on columns\n" + + "from [sales]"); + MdxValidator validator = + olapConnection.getParserFactory().createMdxValidator( + olapConnection); + try { + select = validator.validateSelect(select); + fail("expected exception, got " + select); + } catch (Exception e) { + assertTrue( + getStackTrace(e).indexOf("Duplicate axis name 'COLUMNS'.") + >= 0); + } } private void checkUnparsedMdx(SelectNode select) { @@ -750,24 +758,400 @@ public void testUnparsing() { } /** - * Tests metadata browsing + * Tests the {@link Cube#lookupMember(String[])} method. + */ + public void testCubeLookupMember() throws Exception { + Class.forName(helper.getDriverClassName()); + Connection connection = helper.createConnection(); + OlapConnection olapConnection = + ((Wrapper) connection).unwrap(OlapConnection.class); + Cube cube = olapConnection.getSchema().getCubes().get("Sales"); + + Member member = + cube.lookupMember( + "Time", "1997", "Q2"); + assertEquals("[Time].[1997].[Q2]", member.getUniqueName()); + + member = + cube.lookupMember( + "Time", "1997", "Q5"); + assertNull(member); + + // arguably this should return [Customers].[All Customers]; but it + // makes a bit more sense for it to return null + member = + cube.lookupMember( + "Customers"); + assertNull(member); + + member = + cube.lookupMember( + "Customers", "All Customers"); + assertTrue(member.isAll()); + } + + /** + * Tests the {@link Cube#lookupMembers(java.util.Set, String[])} method. + */ + public void testCubeLookupMembers() throws Exception { + Class.forName(helper.getDriverClassName()); + Connection connection = helper.createConnection(); + OlapConnection olapConnection = + ((Wrapper) connection).unwrap(OlapConnection.class); + Cube cube = olapConnection.getSchema().getCubes().get("Sales"); + + List memberList = + cube.lookupMembers( + EnumSet.of(Member.TreeOp.ANCESTORS, Member.TreeOp.CHILDREN), + "Time", "1997", "Q2"); + assertEquals(TestContext.fold("[Time].[1997]\n" + + "[Time].[1997].[Q2].[4]\n" + + "[Time].[1997].[Q2].[5]\n" + + "[Time].[1997].[Q2].[6]\n"), + memberListToString(memberList)); + + // ask for non-existent member; list should be empty + memberList = + cube.lookupMembers( + EnumSet.of(Member.TreeOp.ANCESTORS, Member.TreeOp.CHILDREN), + "Time", "1997", "Q5"); + assertTrue(memberList.isEmpty()); + + // ask for parent & ancestors; should not get duplicates + memberList = + cube.lookupMembers( + EnumSet.of(Member.TreeOp.ANCESTORS, Member.TreeOp.PARENT), + "Time", "1997", "Q2"); + assertEquals( + TestContext.fold("[Time].[1997]\n"), + memberListToString(memberList)); + + // ask for parent of root member, should not get null member in list + memberList = + cube.lookupMembers( + EnumSet.of(Member.TreeOp.ANCESTORS, Member.TreeOp.PARENT), + "Product"); + assertTrue(memberList.isEmpty()); + + // ask for siblings and children, and the results should be + // hierarchically ordered (as always) + memberList = + cube.lookupMembers( + EnumSet.of(Member.TreeOp.SIBLINGS, Member.TreeOp.CHILDREN), + "Time", "1997", "Q2"); + assertEquals( + TestContext.fold("[Time].[1997].[Q1]\n" + + "[Time].[1997].[Q2].[4]\n" + + "[Time].[1997].[Q2].[5]\n" + + "[Time].[1997].[Q2].[6]\n" + + "[Time].[1997].[Q3]\n" + + "[Time].[1997].[Q4]\n"), + memberListToString(memberList)); + + // siblings of the root member - potentially tricky + memberList = + cube.lookupMembers( + EnumSet.of(Member.TreeOp.SIBLINGS), + "Time", "1997"); + assertEquals( + TestContext.fold("[Time].[1998]\n"), + memberListToString(memberList)); + + memberList = + cube.lookupMembers( + EnumSet.of(Member.TreeOp.SIBLINGS, Member.TreeOp.SELF), + "Customers", "USA", "OR"); + assertEquals( + TestContext.fold("[Customers].[All Customers].[USA].[CA]\n" + + "[Customers].[All Customers].[USA].[OR]\n" + + "[Customers].[All Customers].[USA].[WA]\n"), + memberListToString(memberList)); + } + + /** + * Tests metadata browsing. */ - public void testMetadata() { - if (true) return; - Cube cube = null; + public void testMetadata() throws Exception { + Class.forName(helper.getDriverClassName()); + Connection connection = helper.createConnection(); + OlapConnection olapConnection = + ((Wrapper) connection).unwrap(OlapConnection.class); + Cube cube = olapConnection.getSchema().getCubes().get("Sales"); + for (Dimension dimension : cube.getDimensions()) { + // Call every method of Dimension + assertNotNull(dimension.getCaption(Locale.getDefault())); + dimension.getDescription(Locale.getDefault()); + assertNotNull(dimension.getDefaultHierarchy()); + assertEquals( + dimension.getName().equals("Time") + ? Dimension.Type.TIME + : Dimension.Type.OTHER, + dimension.getDimensionType()); + assertNotNull(dimension.getName()); + assertNotNull(dimension.getUniqueName()); + for (Hierarchy hierarchy : dimension.getHierarchies()) { + // Call every method of Hierarchy + final NamedList rootMemberList = + hierarchy.getRootMembers(); + if (hierarchy.hasAll()) { + assertEquals(1, rootMemberList.size()); + } + for (Member rootMember : rootMemberList) { + assertNull(rootMember.getParentMember()); + } + assertNotNull(hierarchy.getDefaultMember()); + assertNotNull(hierarchy.getName()); + assertNotNull(hierarchy.getUniqueName()); + hierarchy.getDescription(Locale.getDefault()); + assertNotNull(hierarchy.getCaption(Locale.getDefault())); + assertEquals(dimension, hierarchy.getDimension()); + for (Level level : hierarchy.getLevels()) { + int k = 0; for (Member member : level.getMembers()) { - ; + assertNotNull(member.getName()); + if (++k > 3) { + break; + } } } } } + + cube = olapConnection.getSchema().getCubes().get("Warehouse"); + int count = 0; for (NamedSet namedSet : cube.getSets()) { - ; + ++count; + assertNotNull(namedSet.getName()); + assertNotNull(namedSet.getUniqueName()); + assertNotNull(namedSet.getCaption(Locale.getDefault())); + namedSet.getDescription(Locale.getDefault()); + assertTrue(namedSet.getExpression().getType() instanceof SetType); + } + assertTrue(count > 0); + + // ~ Member + + Member member = cube.lookupMember("Product", "Food", "Marshmallows"); + assertNull(member); // we don't sell marshmallows! + member = cube.lookupMember("Product", "Food"); + assertNotNull(member); + Member member2 = cube.lookupMember("Product", "All Products", "Food"); + assertEquals(member, member2); + assertEquals("[Product].[All Products].[Food]", + member.getUniqueName()); + assertEquals("Food", member.getName()); + assertEquals("[Product].[Product Family]", + member.getLevel().getUniqueName()); + assertEquals(Member.Type.REGULAR, member.getMemberType()); + // mondrian does not set ordinals correctly + assertEquals(-1, member.getOrdinal()); + final NamedList propertyList = member.getProperties(); + assertEquals(22, propertyList.size()); + final Property property = propertyList.get("MEMBER_CAPTION"); + assertEquals("Food", member.getPropertyFormattedValue(property)); + assertEquals("Food", member.getPropertyValue(property)); + assertFalse(member.isAll()); + + // All member + final Member allProductsMember = member.getParentMember(); + assertEquals("[Product].[All Products]", + allProductsMember.getUniqueName()); + assertEquals("(All)", allProductsMember.getLevel().getName()); + assertEquals("[Product].[(All)]", allProductsMember.getLevel().getUniqueName()); + assertEquals(1, allProductsMember.getLevel().getMembers().size()); + assertTrue(allProductsMember.isAll()); + assertNull(allProductsMember.getParentMember()); + + // ~ Property + + assertEquals("MEMBER_CAPTION", property.getName()); + assertEquals("MEMBER_CAPTION", property.getUniqueName()); + assertEquals(Property.Scope.MEMBER, property.getScope()); + assertEquals(Property.Datatype.TYPE_STRING, property.getDatatype()); + } + + /** + * Tests the type-derivation for + * {@link org.olap4j.mdx.SelectNode#getFrom()} and the {@link CubeType} + * class. + * + * @throws Throwable on error + */ + public void testCubeType() throws Throwable { + Class.forName(helper.getDriverClassName()); + Connection connection = helper.createConnection(); + OlapConnection olapConnection = + ((Wrapper) connection).unwrap(OlapConnection.class); + + final MdxParserFactory parserFactory = + olapConnection.getParserFactory(); + MdxParser mdxParser = + parserFactory.createMdxParser(olapConnection); + MdxValidator mdxValidator = + parserFactory.createMdxValidator(olapConnection); + + SelectNode select = + mdxParser.parseSelect( + "select {[Gender]} on columns from [sales]\n" + + "where [Time].[1997].[Q4]"); + + // CubeType + + // Before validation, we cannot ask for type + try { + final ParseTreeNode from = select.getFrom(); + assertTrue(from instanceof IdentifierNode); + Type type = from.getType(); + fail("expected error"); + } catch (UnsupportedOperationException e) { + // ignore + } + + select = mdxValidator.validateSelect(select); + CubeType cubeType = (CubeType) select.getFrom().getType(); + assertEquals("Sales", cubeType.getCube().getName()); + assertNull(cubeType.getDimension()); + assertNull(cubeType.getHierarchy()); + assertNull(cubeType.getLevel()); + + // Different query based on same cube should have equal CubeType + select = + mdxParser.parseSelect( + "select from [sales]"); + select = mdxValidator.validateSelect(select); + assertEquals(cubeType, select.getFrom().getType()); + + // Different query based on different cube should have different + // CubeType + select = + mdxParser.parseSelect( + "select from [warehouse and sales]"); + select = mdxValidator.validateSelect(select); + assertNotSame(cubeType, select.getFrom().getType()); + } + + /** + * Tests the type-derivation for query axes + * ({@link org.olap4j.mdx.SelectNode#getAxisList()} and + * {@link org.olap4j.mdx.SelectNode#getSlicerAxis()}), and the + * {@link org.olap4j.type.SetType}, + * {@link org.olap4j.type.TupleType}, + * {@link org.olap4j.type.MemberType} type subclasses. + * + * @throws Throwable on error + */ + public void testAxisType() throws Throwable { + Class.forName(helper.getDriverClassName()); + + // connect using properties and no username/password + Connection connection = helper.createConnection(); + OlapConnection olapConnection = connection.unwrap(OlapConnection.class); + + final MdxParserFactory parserFactory = + olapConnection.getParserFactory(); + MdxParser mdxParser = + parserFactory.createMdxParser(olapConnection); + MdxValidator mdxValidator = + parserFactory.createMdxValidator(olapConnection); + + SelectNode select = + mdxParser.parseSelect( + "select ([Gender], [Store]) on columns\n," + + "{[Customers].[City].Members} on rows\n" + + "from [sales]\n" + + "where ([Time].[1997].[Q4], [Marital Status].[S])"); + select = mdxValidator.validateSelect(select); + + // a query is not an expression, so does not have a type + assertNull(select.getType()); + + final AxisNode columnsAxis = select.getAxisList().get(0); + // an axis is not an expression, so does not have a type + assertNull(columnsAxis.getType()); + + // ~ SetType + + final SetType setType = (SetType) columnsAxis.getExpression().getType(); + assertNull(setType.getDimension()); + assertNull(setType.getHierarchy()); + assertNull(setType.getLevel()); + assertNotNull(setType.toString()); + + final Type elementType = setType.getElementType(); + + // ~ TupleType + + assertTrue(elementType instanceof TupleType); + TupleType tupleType = (TupleType) elementType; + assertNotNull(tupleType.toString()); + assertNull(tupleType.getDimension()); + assertNull(tupleType.getHierarchy()); + assertNull(tupleType.getLevel()); + final Cube cube = ((CubeType) select.getFrom().getType()).getCube(); + final Dimension storeDimension = cube.getDimensions().get("Store"); + final Dimension genderDimension = cube.getDimensions().get("Gender"); + final Dimension measuresDimension = cube.getDimensions().get("Measures"); + final Dimension customersDimension = cube.getDimensions().get("Customers"); + assertTrue(tupleType.usesDimension(storeDimension, false)); + assertTrue(tupleType.usesDimension(genderDimension, false)); + assertFalse(tupleType.usesDimension(measuresDimension, false)); + + // Other axis is a set of members + + // ~ MemberType + final AxisNode rowsAxis = select.getAxisList().get(1); + final Type rowsType = rowsAxis.getExpression().getType(); + assertTrue(rowsType instanceof SetType); + MemberType memberType = (MemberType) ((SetType) rowsType).getElementType(); + assertNotNull(memberType.toString()); + // MemberType.getMember is null because we know it belongs to the City + // level, but no particular member of that level. + assertNull("Customers", memberType.getMember()); + assertEquals("City", memberType.getLevel().getName()); + assertEquals("Customers", memberType.getHierarchy().getName()); + assertEquals("Customers", memberType.getDimension().getName()); + assertFalse(memberType.usesDimension(storeDimension, false)); + assertTrue(memberType.usesDimension(customersDimension, false)); + assertTrue(memberType.usesDimension(customersDimension, true)); + + // Slicer + + final AxisNode slicerAxis = select.getSlicerAxis(); + assertNull(slicerAxis.getType()); + final Type slicerType = slicerAxis.getExpression().getType(); + assertTrue(slicerType instanceof TupleType); + assertEquals( + "TupleType, MemberType>", + slicerType.toString()); + } + + // TODO: test for HierarchyType + // TODO: test for DimensionType + // TODO: test for LevelType + + /** + * Converts a {@link Throwable} to a stack trace. + */ + static String getStackTrace(Throwable e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + pw.flush(); + return sw.toString(); + } + + /** + * Converts a list of members to a string, one per line. + */ + static String memberListToString(List list) { + final StringBuilder buf = new StringBuilder(); + for (Member member : list) { + buf.append(member.getUniqueName()).append(TestContext.NL); } - Member member1 = cube.lookupMember("Product", "Food", "Marshmallows"); + return buf.toString(); } /**