From 0a6f2dc035cbbfd78d6cc356c9ac8e2df6edaf59 Mon Sep 17 00:00:00 2001 From: Julian Hyde Date: Sun, 2 Nov 2008 07:34:39 +0000 Subject: [PATCH] Fix bug 2032449, 'User Defined Dimension Properties'. Add builtin properties MEMBER_KEY, IS_PLACEHOLDERMEMBER, IS_DATAMEMBER. Special treatment for DEPTH property and Member.getDepth() method. Add test for reading members of parent-child hierarchy. Fix test now that mondrian has more variants of the Descendants function. Introduce interface XmlaOlap4jMemberBase, to allow commonality between various implementations of Member in XMLA driver. git-svn-id: https://olap4j.svn.sourceforge.net/svnroot/olap4j/trunk@124 c6a108a4-781c-0410-a6c6-c2d559e19af0 --- .../olap4j/driver/xmla/XmlaOlap4jCellSet.java | 28 +++- .../driver/xmla/XmlaOlap4jConnection.java | 114 +++++++++++--- .../olap4j/driver/xmla/XmlaOlap4jMember.java | 95 +++++++++--- .../driver/xmla/XmlaOlap4jMemberBase.java | 51 ++++++ .../driver/xmla/XmlaOlap4jPositionMember.java | 146 +++++++++++++++++- src/org/olap4j/metadata/Property.java | 24 ++- testsrc/org/olap4j/ConnectionTest.java | 41 ++++- testsrc/org/olap4j/MetadataTest.java | 4 +- 8 files changed, 448 insertions(+), 55 deletions(-) create mode 100644 src/org/olap4j/driver/xmla/XmlaOlap4jMemberBase.java diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java index 158f3cc..0d2b401 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java @@ -233,7 +233,7 @@ void populate() throws OlapException { String hierarchyName = memberNode.getAttribute("Hierarchy"); final String uname = stringElement(memberNode, "UName"); - Member member = memberMap.get(uname); + XmlaOlap4jMemberBase member = memberMap.get(uname); if (member == null) { final String caption = stringElement(memberNode, "Caption"); @@ -242,7 +242,7 @@ void populate() throws OlapException { lookupHierarchy(metaData.cube, hierarchyName); final Level level = hierarchy.getLevels().get(lnum); member = new XmlaOlap4jSurpriseMember( - level, hierarchy, lnum, caption, uname); + this, level, hierarchy, lnum, caption, uname); } propertyValues.clear(); for (Element childNode : childElements(memberNode)) { @@ -1228,7 +1228,10 @@ public boolean isWrapperFor(Class iface) throws SQLException { * in the cube (probably because the member is a calculated member * defined in the query). */ - private static class XmlaOlap4jSurpriseMember implements Member { + private static class XmlaOlap4jSurpriseMember + implements XmlaOlap4jMemberBase + { + private final XmlaOlap4jCellSet cellSet; private final Level level; private final Hierarchy hierarchy; private final int lnum; @@ -1238,6 +1241,7 @@ private static class XmlaOlap4jSurpriseMember implements Member { /** * Creates an XmlaOlap4jSurpriseMember. * + * @param cellSet Cell set * @param level Level * @param hierarchy Hierarchy * @param lnum Level number @@ -1245,12 +1249,14 @@ private static class XmlaOlap4jSurpriseMember implements Member { * @param uname Member unique name */ XmlaOlap4jSurpriseMember( + XmlaOlap4jCellSet cellSet, Level level, Hierarchy hierarchy, int lnum, String caption, String uname) { + this.cellSet = cellSet; this.level = level; this.hierarchy = hierarchy; this.lnum = lnum; @@ -1258,6 +1264,22 @@ private static class XmlaOlap4jSurpriseMember implements Member { this.uname = uname; } + public final XmlaOlap4jCube getCube() { + return cellSet.metaData.cube; + } + + public final XmlaOlap4jConnection getConnection() { + return getCatalog().olap4jDatabaseMetaData.olap4jConnection; + } + + public final XmlaOlap4jCatalog getCatalog() { + return getCube().olap4jSchema.olap4jCatalog; + } + + public Map getPropertyValueMap() { + return Collections.emptyMap(); + } + public NamedList getChildMembers() { return Olap4jUtil.emptyNamedList(); diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java index 7b4e5e0..7bb4afb 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java @@ -2,7 +2,7 @@ // This software is subject to the terms of the Common Public License // Agreement, available at the following URL: // http://www.opensource.org/licenses/cpl.html. -// Copyright (C) 2007-2007 Julian Hyde +// Copyright (C) 2007-2008 Julian Hyde // All Rights Reserved. // You must accept the terms of that agreement to use this software. */ @@ -19,9 +19,7 @@ import org.olap4j.mdx.parser.*; import org.olap4j.mdx.parser.impl.DefaultMdxParserImpl; import org.olap4j.metadata.*; -import org.w3c.dom.DOMImplementation; -import org.w3c.dom.Document; -import org.w3c.dom.Element; +import org.w3c.dom.*; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSSerializer; import org.xml.sax.SAXException; @@ -34,10 +32,6 @@ import java.util.Map.*; import java.util.regex.*; -import javax.xml.transform.*; -import javax.xml.transform.dom.*; -import javax.xml.transform.stream.*; - /** * Implementation of {@link org.olap4j.OlapConnection} * for XML/A providers. @@ -1078,6 +1072,34 @@ public int compare( } static class MemberHandler extends HandlerImpl { + + /** + * Collection of nodes to ignore because they represent standard + * built-in properties of Members. + */ + private static final Set EXCLUDED_PROPERTY_NAMES = + new HashSet( + Arrays.asList( + Property.StandardMemberProperty.CATALOG_NAME.name(), + Property.StandardMemberProperty.CUBE_NAME.name(), + Property.StandardMemberProperty.DIMENSION_UNIQUE_NAME.name(), + Property.StandardMemberProperty.HIERARCHY_UNIQUE_NAME.name(), + Property.StandardMemberProperty.LEVEL_UNIQUE_NAME.name(), + Property.StandardMemberProperty.PARENT_LEVEL.name(), + Property.StandardMemberProperty.PARENT_COUNT.name(), + Property.StandardMemberProperty.MEMBER_KEY.name(), + Property.StandardMemberProperty.IS_PLACEHOLDERMEMBER.name(), + Property.StandardMemberProperty.IS_DATAMEMBER.name(), + Property.StandardMemberProperty.LEVEL_NUMBER.name(), + Property.StandardMemberProperty.MEMBER_ORDINAL.name(), + Property.StandardMemberProperty.MEMBER_UNIQUE_NAME.name(), + Property.StandardMemberProperty.MEMBER_NAME.name(), + Property.StandardMemberProperty.PARENT_UNIQUE_NAME.name(), + Property.StandardMemberProperty.MEMBER_TYPE.name(), + Property.StandardMemberProperty.MEMBER_CAPTION.name(), + Property.StandardMemberProperty.CHILDREN_CARDINALITY.name(), + Property.StandardMemberProperty.DEPTH.name())); + public void handle(Element row, Context context, List list) { /* Example: @@ -1103,31 +1125,87 @@ public void handle(Element row, Context context, List list) { */ - int levelNumber = integerElement(row, "LEVEL_NUMBER"); - int memberOrdinal = integerElement(row, "MEMBER_ORDINAL"); + if (false) { + int levelNumber = + integerElement( + row, + Property.StandardMemberProperty.LEVEL_NUMBER.name()); + } + int memberOrdinal = + integerElement( + row, + Property.StandardMemberProperty.MEMBER_ORDINAL.name()); String memberUniqueName = - stringElement(row, "MEMBER_UNIQUE_NAME"); + stringElement( + row, + Property.StandardMemberProperty.MEMBER_UNIQUE_NAME.name()); String memberName = - stringElement(row, "MEMBER_NAME"); + stringElement( + row, + Property.StandardMemberProperty.MEMBER_NAME.name()); String parentUniqueName = - stringElement(row, "PARENT_UNIQUE_NAME"); + stringElement( + row, + Property.StandardMemberProperty.PARENT_UNIQUE_NAME.name()); Member.Type memberType = Member.Type.values()[ - integerElement(row, "MEMBER_TYPE")]; + integerElement( + row, + Property.StandardMemberProperty.MEMBER_TYPE.name())]; String memberCaption = - stringElement(row, "MEMBER_CAPTION"); + stringElement( + row, + Property.StandardMemberProperty.MEMBER_CAPTION.name()); int childrenCardinality = - integerElement(row, "CHILDREN_CARDINALITY"); + integerElement( + row, + Property.StandardMemberProperty.CHILDREN_CARDINALITY.name()); // If this member is a measure, we want to return an object that // implements the Measure interface to all API calls. But we also // need to retrieve the properties that occur in MDSCHEMA_MEMBERS // that are not available in MDSCHEMA_MEASURES, so we create a // member for internal use. - list.add( + XmlaOlap4jMember member = new XmlaOlap4jMember( context.getLevel(row), memberUniqueName, memberName, memberCaption, "", parentUniqueName, memberType, - childrenCardinality, memberOrdinal)); + childrenCardinality, memberOrdinal); + addUserDefinedDimensionProperties(row, member); + + // Usually members have the same depth as their level. (Ragged and + // parent-child hierarchies are an exception.) Only store depth for + // the unusual ones. + final Integer depth = + integerElement( + row, + Property.StandardMemberProperty.DEPTH.name()); + if (depth != null + && depth.intValue() != member.getLevel().getDepth()) + { + member.setProperty(Property.StandardMemberProperty.DEPTH, depth); + } + list.add(member); + } + + private void addUserDefinedDimensionProperties( + Element row, + XmlaOlap4jMember member) + { + NodeList nodes = row.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (EXCLUDED_PROPERTY_NAMES.contains(node.getLocalName())) { + continue; + } + for (Property property : member.getLevel().getProperties()) { + if (property instanceof XmlaOlap4jProperty + && property.getName().equalsIgnoreCase( + node.getLocalName())) + { + member.setProperty(property, node.getTextContent()); + } + } + } } } diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java b/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java index 39d7a28..75ba16e 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java @@ -26,12 +26,12 @@ * * * @author jhyde - * @version $Id: $ + * @version $Id$ * @since Dec 5, 2007 */ class XmlaOlap4jMember extends XmlaOlap4jElement - implements Member, Named + implements XmlaOlap4jMemberBase, Member, Named { private final XmlaOlap4jLevel olap4jLevel; @@ -161,6 +161,25 @@ public boolean isCalculatedInQuery() { } public Object getPropertyValue(Property property) { + return getPropertyValue( + property, + this, + propertyValueMap); + } + + /** + * Helper method to retrieve the value of a property from a member. + * + * @param property Property + * @param member Member + * @param propertyValueMap Map of property-value pairs + * @return Property value + */ + static Object getPropertyValue( + Property property, + XmlaOlap4jMemberBase member, + Map propertyValueMap) + { // If property map contains a value for this property (even if that // value is null), that overrides. final Object value = propertyValueMap.get(property); @@ -172,47 +191,48 @@ public Object getPropertyValue(Property property) { (Property.StandardMemberProperty) property; switch (o) { case MEMBER_CAPTION: - return getCaption(getConnection().getLocale()); + return member.getCaption(member.getConnection().getLocale()); case MEMBER_NAME: - return getName(); + return member.getName(); case MEMBER_UNIQUE_NAME: - return getUniqueName(); + return member.getUniqueName(); case CATALOG_NAME: - return getCatalog().getName(); + return member.getCatalog().getName(); case CHILDREN_CARDINALITY: - return getChildMemberCount(); + return member.getChildMemberCount(); case CUBE_NAME: - return getCube().getName(); + return member.getCube().getName(); case DEPTH: - return getDepth(); + return member.getDepth(); case DESCRIPTION: - return getDescription(getConnection().getLocale()); + return member.getDescription( + member.getConnection().getLocale()); case DIMENSION_UNIQUE_NAME: - return getDimension().getUniqueName(); + return member.getDimension().getUniqueName(); case DISPLAY_INFO: // TODO: return null; case HIERARCHY_UNIQUE_NAME: - return getHierarchy().getUniqueName(); + return member.getHierarchy().getUniqueName(); case LEVEL_NUMBER: - return getLevel().getDepth(); + return member.getLevel().getDepth(); case LEVEL_UNIQUE_NAME: - return getLevel().getUniqueName(); + return member.getLevel().getUniqueName(); case MEMBER_GUID: // TODO: return null; case MEMBER_ORDINAL: - return getOrdinal(); + return member.getOrdinal(); case MEMBER_TYPE: - return getMemberType(); + return member.getMemberType(); case PARENT_COUNT: return 1; case PARENT_LEVEL: - return getParentMember().getLevel().getDepth(); + return member.getParentMember().getLevel().getDepth(); case PARENT_UNIQUE_NAME: - return getParentMember().getUniqueName(); + return member.getParentMember().getUniqueName(); case SCHEMA_NAME: - return getCube().olap4jSchema.getName(); + return member.getCube().olap4jSchema.getName(); case VALUE: // TODO: return null; @@ -222,30 +242,35 @@ public Object getPropertyValue(Property property) { } // convenience method - not part of olap4j API - private XmlaOlap4jCube getCube() { + public XmlaOlap4jCube getCube() { return olap4jLevel.olap4jHierarchy.olap4jDimension.olap4jCube; } // convenience method - not part of olap4j API - private XmlaOlap4jCatalog getCatalog() { + public XmlaOlap4jCatalog getCatalog() { return olap4jLevel.olap4jHierarchy.olap4jDimension.olap4jCube .olap4jSchema.olap4jCatalog; } // convenience method - not part of olap4j API - private XmlaOlap4jConnection getConnection() { + public XmlaOlap4jConnection getConnection() { return olap4jLevel.olap4jHierarchy.olap4jDimension.olap4jCube .olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData .olap4jConnection; } + // convenience method - not part of olap4j API + public Map getPropertyValueMap() { + return propertyValueMap; + } + public String getPropertyFormattedValue(Property property) { // FIXME: need to use a format string; but what format string; and how // to format the property on the client side? return String.valueOf(getPropertyValue(property)); } - public void setProperty(Property property, Object value) throws OlapException { + public void setProperty(Property property, Object value) { propertyValueMap.put(property, value); } @@ -262,7 +287,29 @@ public boolean isHidden() { } public int getDepth() { - return olap4jLevel.getDepth(); + // Since in regular hierarchies members have the same depth as their + // level, we store depth as a property only where it is different. + final Object depth = + propertyValueMap.get(Property.StandardMemberProperty.DEPTH); + if (depth == null) { + return olap4jLevel.getDepth(); + } else { + return toInteger(depth); + } + } + + /** + * Converts an object to an integer value. Must not be null. + * + * @param o Object + * @return Integer value + */ + static int toInteger(Object o) { + if (o instanceof Number) { + Number number = (Number) o; + return number.intValue(); + } + return Integer.valueOf(o.toString()); } public Member getDataMember() { diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jMemberBase.java b/src/org/olap4j/driver/xmla/XmlaOlap4jMemberBase.java new file mode 100644 index 0000000..3aee565 --- /dev/null +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jMemberBase.java @@ -0,0 +1,51 @@ +/* +// This software is subject to the terms of the Common Public License +// Agreement, available at the following URL: +// http://www.opensource.org/licenses/cpl.html. +// Copyright (C) 2008-2008 Julian Hyde +// All Rights Reserved. +// You must accept the terms of that agreement to use this software. +*/ +package org.olap4j.driver.xmla; + +import org.olap4j.metadata.Member; +import org.olap4j.metadata.Property; + +import java.util.Map; + +/** + * Core interface shared by all implementations of {@link Member} in the XMLA + * driver. + * + *

This interface is private within the {@code org.olap4j.driver.xmla} + * package. The methods in this interface are NOT part of the public olap4j API. + * + * @author jhyde + * @version $Id$ + * @since Nov 1, 2008 + */ +interface XmlaOlap4jMemberBase + extends Member +{ + /** + * Returns the cube this member belongs to. + */ + XmlaOlap4jCube getCube(); + + /** + * Returns the connection that created this member. + */ + XmlaOlap4jConnection getConnection(); + + /** + * Returns the catalog that this member belongs to. + */ + XmlaOlap4jCatalog getCatalog(); + + /** + * Returns the set of property values, keyed by property. + */ + Map getPropertyValueMap(); +} + +// End XmlaOlap4jMemberBase.java diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jPositionMember.java b/src/org/olap4j/driver/xmla/XmlaOlap4jPositionMember.java index 513bb49..5d39e2c 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jPositionMember.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jPositionMember.java @@ -27,21 +27,66 @@ * properties. All other methods are delegated to the underlying member.

* * @author jhyde - * @version $Id: $ + * @version $Id$ * @since Dec 7, 2007 */ -class XmlaOlap4jPositionMember implements Member { - private final Member member; +class XmlaOlap4jPositionMember + implements XmlaOlap4jMemberBase +{ + private final XmlaOlap4jMemberBase member; private final Map propertyValues; + /** + * Creates a XmlaOlap4jPositionMember. + * + * @param member Underlying member + * @param propertyValues Property values + */ XmlaOlap4jPositionMember( - Member member, + XmlaOlap4jMemberBase member, Map propertyValues) { + assert member != null; + assert propertyValues != null; this.member = member; this.propertyValues = propertyValues; } + public boolean equals(Object obj) { + if (obj instanceof XmlaOlap4jPositionMember) { + XmlaOlap4jPositionMember that = + (XmlaOlap4jPositionMember) obj; + return this.member.equals(that.member); + } else if (obj instanceof XmlaOlap4jMember) { + XmlaOlap4jMember that = (XmlaOlap4jMember) obj; + return this.member.equals(that); + } else { + return super.equals(obj); + } + } + + public int hashCode() { + return member.hashCode(); + } + + public XmlaOlap4jCube getCube() { + return member.getCube(); + } + + public XmlaOlap4jConnection getConnection() { + return member.getConnection(); + } + + public XmlaOlap4jCatalog getCatalog() { + return member.getCatalog(); + } + + public Map getPropertyValueMap() { + return new ChainedMap( + propertyValues, + member.getPropertyValueMap()); + } + public NamedList getChildMembers() throws OlapException { return member.getChildMembers(); } @@ -75,7 +120,7 @@ public boolean isAll() { } public boolean isChildOrEqualTo(Member member) { - return member.isChildOrEqualTo(member); + return this.member.isChildOrEqualTo(member); } public boolean isCalculated() { @@ -132,7 +177,11 @@ public boolean isHidden() { } public int getDepth() { - return member.getDepth(); + return XmlaOlap4jMember.toInteger( + XmlaOlap4jMember.getPropertyValue( + Property.StandardMemberProperty.DEPTH, + member, + getPropertyValueMap())); } public Member getDataMember() { @@ -154,6 +203,91 @@ public String getCaption(Locale locale) { public String getDescription(Locale locale) { return member.getDescription(locale); } + + /** + * Read-only map that contains the union of two maps. + */ + private static class ChainedMap implements Map { + private final Map map; + private final Map next; + + /** + * Creates a ChainedMap. + * + * @param map First map in the chain + * @param next Next map in the chain + */ + ChainedMap( + Map map, + Map next) + { + this.map = map; + this.next = next; + } + + public int size() { + int n = next.size(); + for (K k : map.keySet()) { + //noinspection SuspiciousMethodCalls + if (!next.containsKey(k)) { + ++n; + } + } + return n; + } + + public boolean isEmpty() { + return map.isEmpty() + && next.isEmpty(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key) + || next.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value) + || next.containsValue(value); + } + + public V get(Object key) { + //noinspection SuspiciousMethodCalls + if (map.containsKey(key)) { + return map.get(key); + } else { + return next.get(key); + } + } + + public V put(K key, V value) { + throw new UnsupportedOperationException("read only"); + } + + public V remove(Object key) { + throw new UnsupportedOperationException("read only"); + } + + public void putAll(Map t) { + throw new UnsupportedOperationException("read only"); + } + + public void clear() { + throw new UnsupportedOperationException("read only"); + } + + public Set keySet() { + throw new UnsupportedOperationException("need to implement"); + } + + public Collection values() { + throw new UnsupportedOperationException("need to implement"); + } + + public Set> entrySet() { + throw new UnsupportedOperationException("need to implement"); + } + } } // End XmlaOlap4jPositionMember.java diff --git a/src/org/olap4j/metadata/Property.java b/src/org/olap4j/metadata/Property.java index a7fd48a..03ef084 100644 --- a/src/org/olap4j/metadata/Property.java +++ b/src/org/olap4j/metadata/Property.java @@ -3,7 +3,7 @@ // This software is subject to the terms of the Common Public License // Agreement, available at the following URL: // http://www.opensource.org/licenses/cpl.html. -// Copyright (C) 2006-2006 Julian Hyde +// Copyright (C) 2006-2008 Julian Hyde // All Rights Reserved. // You must accept the terms of that agreement to use this software. */ @@ -280,6 +280,28 @@ enum StandardMemberProperty implements Property { */ $visible(Datatype.BOOLEAN, 28, true, null), + /** + * Definition of the internal property which holds the + * value of the member key in the original data type. MEMBER_KEY is for + * backward-compatibility. MEMBER_KEY has the same value as KEY0 for + * non-composite keys, and MEMBER_KEY property is null for composite + * keys. + */ + MEMBER_KEY(Datatype.VARIANT, 29, true, "Optional. The value of the member key. Null for composite keys."), + + /** + * Definition of the boolean property that indicates whether + * a member is a placeholder member for an empty position in a + * dimension hierarchy. + */ + IS_PLACEHOLDERMEMBER(Datatype.BOOLEAN, 30, false, "Required. Whether the member is a placeholder member for an empty position in a dimension hierarchy."), + + /** + * Definition of the property that indicates whether the member is a + * data member. + */ + IS_DATAMEMBER(Datatype.BOOLEAN, 31, false, "Required. whether the member is a data member"), + /** * Definition of the property which * holds the level depth of a member. diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java index ac223f8..c50cab0 100644 --- a/testsrc/org/olap4j/ConnectionTest.java +++ b/testsrc/org/olap4j/ConnectionTest.java @@ -1491,7 +1491,7 @@ public void testMetadata() throws Exception { break; } final NamedList propertyList = member.getProperties(); - assertEquals(22, propertyList.size()); + assertEquals(25, propertyList.size()); final Property property = propertyList.get("MEMBER_CAPTION"); assertEquals("Food", member.getPropertyFormattedValue(property)); assertEquals("Food", member.getPropertyValue(property)); @@ -1553,6 +1553,45 @@ public void testMetadata() throws Exception { measureNameSet); } + /** + * Tests members from a parent-child hierarchy. + * + * @throws ClassNotFoundException + * @throws SQLException + */ + public void testParentChild() throws ClassNotFoundException, SQLException { + Class.forName(tester.getDriverClassName()); + connection = tester.createConnection(); + OlapConnection olapConnection = + tester.getWrapper().unwrap(connection, OlapConnection.class); + + final CellSet cellSet = + olapConnection.createStatement().executeOlapQuery( + "select {[Measures].[Org Salary]} on 0,\n" + + " Head([Employees].Members, 10) DIMENSION PROPERTIES DEPTH ON 1\n" + + "from [HR]"); + final CellSetAxis rowsAxis = cellSet.getAxes().get(1); + assertEquals(10, rowsAxis.getPositionCount()); + Member member0 = rowsAxis.getPositions().get(0).getMembers().get(0); + assertEquals("All Employees", member0.getName()); + assertEquals(0, member0.getDepth()); + Member member1 = rowsAxis.getPositions().get(1).getMembers().get(0); + assertEquals("[Employees].[All Employees].[Sheri Nowmer]", member1.getUniqueName()); + assertEquals(1, member1.getDepth()); + assertEquals(1, member1.getLevel().getDepth()); + assertEquals(member0.getUniqueName(), member1.getParentMember().getUniqueName()); + assertEquals(member0, member1.getParentMember()); + Member member2 = rowsAxis.getPositions().get(2).getMembers().get(0); + assertEquals("[Employees].[All Employees].[Derrick Whelply]", member2.getUniqueName()); + // TODO: should return depth=2 here but mondrian erroneously returns 1 + assertEquals(1, member2.getDepth()); + assertEquals(1, member2.getLevel().getDepth()); + // TODO: member2.parentMember should equal member1, but currently + // mondrian cannot look up a member of a parent-child hierarchy based + // on its unique name + assertNull(member2.getParentMember()); + } + /** * Tests the type-derivation for * {@link org.olap4j.mdx.SelectNode#getFrom()} and the {@link CubeType} diff --git a/testsrc/org/olap4j/MetadataTest.java b/testsrc/org/olap4j/MetadataTest.java index e9cd0d9..8cd3aeb 100644 --- a/testsrc/org/olap4j/MetadataTest.java +++ b/testsrc/org/olap4j/MetadataTest.java @@ -315,11 +315,11 @@ public void testDatabaseMetaDataGetFunctions() throws SQLException { final int functionCount = linecount(s); assertTrue(functionCount + " functions", functionCount > 360); - // Mondrian has 7 variants of the Ascendants and Descendants functions + // Mondrian has 13 variants of the Ascendants and Descendants functions s = checkResultSet( olapDatabaseMetaData.getOlapFunctions("%scendants"), FUNCTIONS_COLUMN_NAMES); - assertEquals(s, 7, linecount(s)); + assertEquals(s, 13, linecount(s)); } public void testDatabaseMetaDataGetHierarchies() throws SQLException {