diff --git a/src/org/olap4j/driver/xmla/MetadataReader.java b/src/org/olap4j/driver/xmla/MetadataReader.java new file mode 100644 index 0000000..b53fee6 --- /dev/null +++ b/src/org/olap4j/driver/xmla/MetadataReader.java @@ -0,0 +1,88 @@ +/* +// 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.OlapException; +import org.olap4j.metadata.Member; + +import java.util.*; + +/** + * Can read metadata, in particular members. + * + * @author jhyde + * @version $Id: $ + * @since Jan 14, 2008 + */ +interface MetadataReader { + /** + * Looks up a member by its unique name. + * + *

Not part of public olap4j API. + * + * @param memberUniqueName Unique name of member + * @return Member, or null if not found + * @throws org.olap4j.OlapException if error occurs + */ + XmlaOlap4jMember lookupMemberByUniqueName( + String memberUniqueName) + throws OlapException; + + /** + * Looks up a list of members by their unique name and writes the results + * into a map. + * + *

Not part of public olap4j API. + * + * @param memberUniqueNames List of unique names of member + * + * @param memberMap Map to populate with members + * + * @throws org.olap4j.OlapException if error occurs + */ + void lookupMembersByUniqueName( + List memberUniqueNames, + Map memberMap) + throws OlapException; + + /** + * Looks a member by its unique name and returns members related by + * the specified tree-operations. + * + *

Not part of public olap4j API. + * + * @param memberUniqueName Unique name of member + * + * @param treeOps Collection of tree operations to travel relative to + * given member in order to create list of members + * + * @param list list to be populated with members related to the given + * member, or empty set if the member is not found + * + * @throws org.olap4j.OlapException if error occurs + */ + void lookupMemberRelatives( + Set treeOps, + String memberUniqueName, + List list) throws OlapException; + + /** + * Looks up members of a given level. + * + * @param level Level + * + * @throws org.olap4j.OlapException if error occurs + * + * @return list of members at in the level + */ + List getLevelMembers(XmlaOlap4jLevel level) + throws OlapException; +} + +// End MetadataReader.java diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java index 9732690..4cae9b4 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java @@ -182,6 +182,32 @@ void populate() throws OlapException { int ordinal = 0; final Map propertyValues = new HashMap(); + + // First pass, gather up a list of member unique names to fetch + // all at once. + // + // NOTE: This approach allows the driver to fetch a large number + // of members in one round trip, which is much more efficient. + // However, if the axis has a very large number of members, the map + // may use too much memory. This is an unresolved issue. + final MetadataReader metadataReader = + metaData.cube.getMetadataReader(); + final Map memberMap = + new HashMap(); + List uniqueNames = new ArrayList(); + for (Element tupleNode + : findChildren(tuplesNode, MDDATASET_NS, "Tuple")) + { + for (Element memberNode + : findChildren(tupleNode, MDDATASET_NS, "Member")) + { + final String uname = stringElement(memberNode, "UName"); + uniqueNames.add(uname); + } + } + metadataReader.lookupMembersByUniqueName(uniqueNames, memberMap); + + // Second pass, populate the axis. for (Element tupleNode : findChildren(tuplesNode, MDDATASET_NS, "Tuple")) { @@ -192,8 +218,7 @@ void populate() throws OlapException { String hierarchyName = memberNode.getAttribute("Hierarchy"); final String uname = stringElement(memberNode, "UName"); - Member member = - metaData.cube.lookupMemberByUniqueName(uname); + Member member = memberMap.get(uname); if (member == null) { final String caption = stringElement(memberNode, "Caption"); diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java index 8d0d673..752a409 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java @@ -396,7 +396,7 @@ void populateList( Context context, MetadataRequest metadataRequest, Handler handler, - String... restrictions) throws OlapException + Object[] restrictions) throws OlapException { String request = generateRequest(context, metadataRequest, restrictions); @@ -474,10 +474,24 @@ Element xxx(String request) throws OlapException { return findChild(returnElement, ROWSET_NS, "root"); } + /** + * Generates a metadata request. + * + *

The list of restrictions must have even length. Even elements must + * be a string (the name of the restriction); odd elements must be either + * a string (the value of the restriction) or a list of strings (multiple + * values of the restriction) + * + * + * @param context Context + * @param metadataRequest Metadata request + * @param restrictions List of restrictions + * @return XMLA request + */ public String generateRequest( Context context, MetadataRequest metadataRequest, - String... restrictions) + Object[] restrictions) { final String dataSourceInfo = context.olap4jConnection.getDataSourceInfo(); @@ -499,11 +513,23 @@ public String generateRequest( throw new IllegalArgumentException(); } for (int i = 0; i < restrictions.length; i += 2) { - final String restriction = restrictions[i]; - final String value = restrictions[i + 1]; - buf.append("<").append(restriction).append(">"); - buf.append(xmlEncode(value)); - buf.append(""); + final String restriction = (String) restrictions[i]; + final Object o = restrictions[i + 1]; + if (o instanceof String) { + buf.append("<").append(restriction).append(">"); + final String value = (String) o; + buf.append(xmlEncode(value)); + buf.append(""); + + } else { + //noinspection unchecked + List valueList = (List) o; + for (String value : valueList) { + buf.append("<").append(restriction).append(">"); + buf.append(xmlEncode(value)); + buf.append(""); + } + } } } buf.append(" \n" diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java index 515f224..57f7afd 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java @@ -14,6 +14,7 @@ import org.olap4j.metadata.*; import java.util.*; +import java.lang.ref.SoftReference; /** * Implementation of {@link Cube} @@ -43,6 +44,7 @@ class XmlaOlap4jCube implements Cube, Named new NamedListImpl(); private final NamedList namedSets = new NamedListImpl(); + private final MetadataReader metadataReader; XmlaOlap4jCube( XmlaOlap4jSchema olap4jSchema, @@ -55,6 +57,11 @@ class XmlaOlap4jCube implements Cube, Named this.olap4jSchema = olap4jSchema; this.name = name; this.description = description; + this.metadataReader = + new CachingMetadataReader( + new RawMetadataReader() + ) + ; final XmlaOlap4jConnection olap4jConnection = olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; @@ -168,117 +175,26 @@ public Member lookupMember(String... nameParts) throws OlapException { private Member lookupMember( List segmentList) throws OlapException { - if (true) { - StringBuilder buf = new StringBuilder(); - for (IdentifierNode.Segment segment : segmentList) { - if (buf.length() > 0) { - buf.append('.'); - } - buf.append(segment.toString()); - } - final String uniqueName = buf.toString(); - return lookupMemberByUniqueName(uniqueName); - } else { - final Hierarchy hierarchy = - getHierarchies().get(segmentList.get(0).getName()); - final NamedList rootMembers = hierarchy.getRootMembers(); - Member member = rootMembers.get(segmentList.get(1).getName()); - int k = 1; - if (member == null) { - if (rootMembers.size() == 1 - && rootMembers.get(0).isAll()) { - member = rootMembers.get(0); - ++k; - } else { - return null; - } - } - while (k < segmentList.size()) { - + StringBuilder buf = new StringBuilder(); + for (IdentifierNode.Segment segment : segmentList) { + if (buf.length() > 0) { + buf.append('.'); } - return member; - } - } - - /** - * Looks up a member by its unique name. - * - *

Not part of public olap4j API. - * - * @param memberUniqueName Unique name of member - * @return Member, or null if not found - * @throws OlapException if error occurs - */ - XmlaOlap4jMember lookupMemberByUniqueName( - String memberUniqueName) - throws OlapException - { - NamedList list = - new NamedListImpl(); - lookupMembersByUniqueName( - EnumSet.of(Member.TreeOp.SELF), memberUniqueName, list); - switch (list.size()) { - case 0: - return null; - case 1: - return list.get(0); - default: - throw new IllegalArgumentException( - "more than one member with unique name '" - + memberUniqueName - + "'"); + buf.append(segment.toString()); } + final String uniqueName = buf.toString(); + return getMetadataReader().lookupMemberByUniqueName(uniqueName); } /** - * Looks a member by its unique name and returns members related by - * the specified tree-operations. + * Returns this cube's metadata reader. * *

Not part of public olap4j API. * - * @param memberUniqueName Unique name of member - * - * @param treeOps Collection of tree operations to travel relative to - * given member in order to create list of members - * - * @param list list to be populated with members related to the given - * member, or empty set if the member is not found - * - * @throws OlapException if error occurs - */ - void lookupMembersByUniqueName( - Set treeOps, - String memberUniqueName, - List list) throws OlapException - { - final XmlaOlap4jConnection.Context context = - new XmlaOlap4jConnection.Context(this, null, null, null); - int treeOpMask = 0; - for (Member.TreeOp treeOp : treeOps) { - treeOpMask |= treeOp.xmlaOrdinal(); - } - olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection - .populateList( - list, - context, - XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_MEMBERS, - new XmlaOlap4jConnection.MemberHandler(), - "CATALOG_NAME", olap4jSchema.olap4jCatalog.getName(), - "SCHEMA_NAME", olap4jSchema.getName(), - "CUBE_NAME", getName(), - "MEMBER_UNIQUE_NAME", memberUniqueName, - "TREE_OP", String.valueOf(treeOpMask)); - } - - /** - * Returns true if two objects are equal, or are both null. - * - * @param s First object - * @param t Second object - * @return whether objects are equal + * @return metadata reader */ - static boolean equal(T s, T t) { - return (s == null) ? (t == null) : s.equals(t); + MetadataReader getMetadataReader() { + return metadataReader; } public List lookupMembers( @@ -295,80 +211,248 @@ public List lookupMembers( final String uniqueName = buf.toString(); final List list = new ArrayList(); - lookupMembersByUniqueName(treeOps, uniqueName, list); -// Collections.sort(list, new MemberComparator()); + getMetadataReader().lookupMemberRelatives( + treeOps, uniqueName, list); return Olap4jUtil.cast(list); } /** - * Looks a member by its unique name and returns members related by - * the specified tree-operations. - * - *

Not part of public olap4j API. - * - * @param level Level - * - * @param list list to be populated with members related to the given level - * - * @throws OlapException if error occurs + * Abstract implementation of MemberReader that delegates all operations + * to an underlying MemberReader. */ - void lookupLevelMembers( - XmlaOlap4jLevel level, - List list) throws OlapException + private static abstract class DelegatingMetadataReader + implements MetadataReader { - assert level.olap4jHierarchy.olap4jDimension.olap4jCube == this; - final XmlaOlap4jConnection.Context context = - new XmlaOlap4jConnection.Context(level); - olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection - .populateList( - list, - context, - XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_MEMBERS, - new XmlaOlap4jConnection.MemberHandler(), - "CATALOG_NAME", olap4jSchema.olap4jCatalog.getName(), - "SCHEMA_NAME", olap4jSchema.getName(), - "CUBE_NAME", getName(), - "DIMENSION_UNIQUE_NAME", - level.olap4jHierarchy.olap4jDimension.getUniqueName(), - "HIERARCHY_UNIQUE_NAME", - level.olap4jHierarchy.getUniqueName(), - "LEVEL_UNIQUE_NAME", level.getUniqueName()); + private final MetadataReader metadataReader; + + DelegatingMetadataReader(MetadataReader metadataReader) { + this.metadataReader = metadataReader; + } + + public XmlaOlap4jMember lookupMemberByUniqueName( + String memberUniqueName) throws OlapException + { + return metadataReader.lookupMemberByUniqueName(memberUniqueName); + } + + public void lookupMembersByUniqueName( + List memberUniqueNames, + Map memberMap) throws OlapException + { + metadataReader.lookupMembersByUniqueName( + memberUniqueNames, memberMap); + } + + public void lookupMemberRelatives( + Set treeOps, + String memberUniqueName, + List list) throws OlapException + { + metadataReader.lookupMemberRelatives( + treeOps, memberUniqueName, list); + } + + public List getLevelMembers( + XmlaOlap4jLevel level) + throws OlapException + { + return metadataReader.getLevelMembers(level); + } } - // NOT USED - private static class MemberComparator - implements Comparator + /** + * Implementation of MemberReader that reads from an underlying member + * reader and caches the results. + * + *

Caches are {@link Map}s containing + * {@link java.lang.ref.SoftReference}s to cached objects, so can be + * cleared when memory is in short supply. + */ + private static class CachingMetadataReader + extends DelegatingMetadataReader { - public int compare(XmlaOlap4jMember m1, XmlaOlap4jMember m2) { - if (equal(m1, m2)) { - return 0; + private final Map> memberMap = + new HashMap>(); + + private final Map>> + levelMemberListMap = + new HashMap>>(); + + CachingMetadataReader(MetadataReader metadataReader) { + super(metadataReader); + } + + public XmlaOlap4jMember lookupMemberByUniqueName( + String memberUniqueName) throws OlapException + { + final SoftReference memberRef = + memberMap.get(memberUniqueName); + if (memberRef != null) { + final XmlaOlap4jMember member = memberRef.get(); + if (member != null) { + return member; + } } - while (true) { - int depth1 = m1.getDepth(), - depth2 = m2.getDepth(); - if (depth1 < depth2) { - m2 = m2.getParentMember(); - if (Olap4jUtil.equal(m1, m2)) { - return -1; - } - } else if (depth1 > depth2) { - m1 = m1.getParentMember(); - if (equal(m1, m2)) { - return 1; - } + final XmlaOlap4jMember member = + super.lookupMemberByUniqueName(memberUniqueName); + memberMap.put( + memberUniqueName, + new SoftReference(member)); + return member; + } + + public void lookupMembersByUniqueName( + List memberUniqueNames, + Map memberMap) throws OlapException + { + final ArrayList remainingMemberUniqueNames = + new ArrayList(); + for (String memberUniqueName : memberUniqueNames) { + final SoftReference memberRef = + this.memberMap.get(memberUniqueName); + final XmlaOlap4jMember member; + if (memberRef != null && + (member = memberRef.get()) != null) { + memberMap.put(memberUniqueName, member); } else { - m1 = m1.getParentMember(); - m2 = m2.getParentMember(); - if (equal(m1, m2)) { - // The previous values of m1 and m2 are siblings. - // We do not have access to the ordering key, if - // we assume that (a) the siblings were returned in - // the correct order, and (b) the sort is stable, - // then the first member is the earlier one. - return -1; - } + remainingMemberUniqueNames.add(memberUniqueName); } } + // If any of the member names were not in the cache, look them up + // by delegating. + if (!remainingMemberUniqueNames.isEmpty()) { + super.lookupMembersByUniqueName( + memberUniqueNames, + memberMap); + } + } + + public List getLevelMembers( + XmlaOlap4jLevel level) + throws OlapException + { + final SoftReference> memberListRef = + levelMemberListMap.get(level); + if (memberListRef != null) { + final List memberList = memberListRef.get(); + if (memberList != null) { + return memberList; + } + } + final List memberList = + super.getLevelMembers(level); + levelMemberListMap.put( + level, + new SoftReference>(memberList)); + return memberList; + } + } + + /** + * Implementation of MetadataReader that reads from the XMLA provider, + * without caching. + */ + private class RawMetadataReader implements MetadataReader { + public XmlaOlap4jMember lookupMemberByUniqueName( + String memberUniqueName) + throws OlapException + { + NamedList list = + new NamedListImpl(); + lookupMemberRelatives( + EnumSet.of(Member.TreeOp.SELF), memberUniqueName, list); + switch (list.size()) { + case 0: + return null; + case 1: + return list.get(0); + default: + throw new IllegalArgumentException( + "more than one member with unique name '" + + memberUniqueName + + "'"); + } + } + + public void lookupMembersByUniqueName( + List memberUniqueNames, + Map memberMap) throws OlapException + { + final XmlaOlap4jConnection.Context context = + new XmlaOlap4jConnection.Context( + XmlaOlap4jCube.this, null, null, null); + List memberList = + new ArrayList(); + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection + .populateList( + memberList, + context, + XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_MEMBERS, + new XmlaOlap4jConnection.MemberHandler(), + new Object[] { + "CATALOG_NAME", olap4jSchema.olap4jCatalog.getName(), + "SCHEMA_NAME", olap4jSchema.getName(), + "CUBE_NAME", getName(), + "MEMBER_UNIQUE_NAME", memberUniqueNames + }); + for (XmlaOlap4jMember member : memberList) { + memberMap.put(member.getUniqueName(), member); + } + } + + public void lookupMemberRelatives( + Set treeOps, + String memberUniqueName, + List list) throws OlapException + { + final XmlaOlap4jConnection.Context context = + new XmlaOlap4jConnection.Context( + XmlaOlap4jCube.this, null, null, null); + int treeOpMask = 0; + for (Member.TreeOp treeOp : treeOps) { + treeOpMask |= treeOp.xmlaOrdinal(); + } + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection + .populateList( + list, + context, + XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_MEMBERS, + new XmlaOlap4jConnection.MemberHandler(), + new Object[] { + "CATALOG_NAME", olap4jSchema.olap4jCatalog.getName(), + "SCHEMA_NAME", olap4jSchema.getName(), + "CUBE_NAME", getName(), + "MEMBER_UNIQUE_NAME", memberUniqueName, + "TREE_OP", String.valueOf(treeOpMask) + }); + } + + public List getLevelMembers( + XmlaOlap4jLevel level) + throws OlapException + { + assert level.olap4jHierarchy.olap4jDimension.olap4jCube + == XmlaOlap4jCube.this; + final XmlaOlap4jConnection.Context context = + new XmlaOlap4jConnection.Context(level); + List list = new ArrayList(); + olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection + .populateList( + list, + context, + XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_MEMBERS, + new XmlaOlap4jConnection.MemberHandler(), + new Object[] { + "CATALOG_NAME", olap4jSchema.olap4jCatalog.getName(), + "SCHEMA_NAME", olap4jSchema.getName(), + "CUBE_NAME", getName(), + "DIMENSION_UNIQUE_NAME", + level.olap4jHierarchy.olap4jDimension.getUniqueName(), + "HIERARCHY_UNIQUE_NAME", + level.olap4jHierarchy.getUniqueName(), + "LEVEL_UNIQUE_NAME", level.getUniqueName() + }); + return list; } } } diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jHierarchy.java b/src/org/olap4j/driver/xmla/XmlaOlap4jHierarchy.java index 244b24b..1a05c6d 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jHierarchy.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jHierarchy.java @@ -12,6 +12,8 @@ import org.olap4j.impl.*; import org.olap4j.metadata.*; +import java.util.List; + /** * Implementation of {@link org.olap4j.metadata.Hierarchy} * for XML/A providers. @@ -37,7 +39,7 @@ class XmlaOlap4jHierarchy String caption, String description, boolean all, - String defaultMemberUniqueName) + String defaultMemberUniqueName) throws OlapException { super(uniqueName, name, caption, description); assert olap4jDimension != null; @@ -58,26 +60,22 @@ public boolean hasAll() { return all; } - public Member getDefaultMember() { - try { - return olap4jDimension.olap4jCube.lookupMemberByUniqueName( - defaultMemberUniqueName); - } catch (OlapException e) { - // TODO: cache member in hierarchy on creation, and obsolete the - // defaultMemberUniqueName field; do not make this method throw - // OlapException - throw new RuntimeException( - "Internal error: lookup up default member" + - defaultMemberUniqueName, - e); + public Member getDefaultMember() throws OlapException { + if (defaultMemberUniqueName == null) { + return null; } + return olap4jDimension.olap4jCube.getMetadataReader() + .lookupMemberByUniqueName( + defaultMemberUniqueName); } public NamedList getRootMembers() throws OlapException { + final List memberList = + olap4jDimension.olap4jCube.getMetadataReader().getLevelMembers( + levels.get(0)); final NamedList list = new NamedListImpl(); - olap4jDimension.olap4jCube.lookupLevelMembers( - levels.get(0), list); + list.addAll(memberList); return Olap4jUtil.cast(list); } } diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jLevel.java b/src/org/olap4j/driver/xmla/XmlaOlap4jLevel.java index 2b5c4b8..421fbb8 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jLevel.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jLevel.java @@ -57,20 +57,22 @@ protected void populateList( { context.olap4jConnection.populateList( list, context, metadataRequest, handler, - "CATALOG_NAME", - olap4jHierarchy.olap4jDimension.olap4jCube.olap4jSchema - .olap4jCatalog.getName(), - "SCHEMA_NAME", - olap4jHierarchy.olap4jDimension.olap4jCube.olap4jSchema - .getName(), - "CUBE_NAME", - olap4jHierarchy.olap4jDimension.olap4jCube.getName(), - "DIMENSION_UNIQUE_NAME", - olap4jHierarchy.olap4jDimension.getUniqueName(), - "HIERARCHY_UNIQUE_NAME", - olap4jHierarchy.getUniqueName(), - "LEVEL_UNIQUE_NAME", - getUniqueName()); + new Object[] { + "CATALOG_NAME", + olap4jHierarchy.olap4jDimension.olap4jCube.olap4jSchema + .olap4jCatalog.getName(), + "SCHEMA_NAME", + olap4jHierarchy.olap4jDimension.olap4jCube.olap4jSchema + .getName(), + "CUBE_NAME", + olap4jHierarchy.olap4jDimension.olap4jCube.getName(), + "DIMENSION_UNIQUE_NAME", + olap4jHierarchy.olap4jDimension.getUniqueName(), + "HIERARCHY_UNIQUE_NAME", + olap4jHierarchy.getUniqueName(), + "LEVEL_UNIQUE_NAME", + getUniqueName() + }); } }; } @@ -106,11 +108,9 @@ protected String getName(Property property) { } public List getMembers() throws OlapException { - final List list = - new ArrayList(); - olap4jHierarchy.olap4jDimension.olap4jCube.lookupLevelMembers( - this, list); - return Olap4jUtil.cast(list); + return Olap4jUtil.cast( + olap4jHierarchy.olap4jDimension.olap4jCube.getMetadataReader() + .getLevelMembers(this)); } public int getCardinality() { diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java b/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java index e5b2664..39d7a28 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java @@ -22,7 +22,7 @@ *

TODO:

    *
  1. create members with a pointer to their parent member (not the name)
  2. *
  3. implement a member cache (by unique name, belongs to cube, soft)
  4. - *
  5. implement Hierarchy.getRootMembers and Hierarchy.getDefaultMember
  6. + *
  7. implement Hierarchy.getRootMembers
  8. *
* * @author jhyde @@ -82,7 +82,8 @@ public NamedList getChildMembers() throws OlapException { final NamedList list = new NamedListImpl(); getCube() - .lookupMembersByUniqueName( + .getMetadataReader() + .lookupMemberRelatives( EnumSet.of(TreeOp.CHILDREN), uniqueName, list); @@ -100,7 +101,7 @@ public XmlaOlap4jMember getParentMember() { if (parentMember == null) { try { parentMember = - getCube() + getCube().getMetadataReader() .lookupMemberByUniqueName(parentMemberUniqueName); } catch (OlapException e) { throw new RuntimeException("yuck!"); // FIXME diff --git a/src/org/olap4j/metadata/Hierarchy.java b/src/org/olap4j/metadata/Hierarchy.java index bd1734b..e64ff53 100644 --- a/src/org/olap4j/metadata/Hierarchy.java +++ b/src/org/olap4j/metadata/Hierarchy.java @@ -61,7 +61,7 @@ public interface Hierarchy extends MetadataElement { * * @return the default member of this hierarchy */ - Member getDefaultMember(); + Member getDefaultMember() throws OlapException; /** * Returns the root member or members of this Dimension. @@ -72,13 +72,15 @@ public interface Hierarchy extends MetadataElement { *

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

* - *

The result is similar to calling - * getLevels().get(0).getMembers(). The contents will be the - * same, but this method returns a link {@link NamedList} rather than a + *

The result is similar to that returned by + * getLevels().get(0).getMembers(); the contents will be the + * same, but this method returns a {@link NamedList} rather than a * mere {@link java.util.List} because the members of the root level are * known to have unique names. * * @return root members of this hierarchy + * + * @throws OlapException on database error */ NamedList getRootMembers() throws OlapException; } diff --git a/src/org/olap4j/query/Query.java b/src/org/olap4j/query/Query.java index 3b13b2d..3651000 100644 --- a/src/org/olap4j/query/Query.java +++ b/src/org/olap4j/query/Query.java @@ -104,7 +104,7 @@ public QueryAxis getunusedAxis() { return unused; } - public boolean validate() { + public boolean validate() throws OlapException { for (Dimension dimension : cube.getDimensions()) { QueryDimension queryDimension = getDimension(dimension.getName()); diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java index 89d7c49..ea49425 100644 --- a/testsrc/org/olap4j/ConnectionTest.java +++ b/testsrc/org/olap4j/ConnectionTest.java @@ -1667,15 +1667,20 @@ public void testCellSetBug() throws SQLException { OlapConnection olapConnection = ((OlapWrapper) connection).unwrap(OlapConnection.class); final OlapStatement olapStatement = olapConnection.createStatement(); + // Note: substitute [Sales Ragged] for [Sales] below and the query + // takes a very long time against mondrian's XMLA driver, because + // mondrian has a performance bug assigning ordinals to ragged + // hierarchies, and XMLA requests ask for member ordinals along with + // the other attributes of members. CellSet cellSet = olapStatement.executeOlapQuery( "SELECT " + "{[Product].[All Products].[Drink].[Alcoholic Beverages].Children, [Product].[All Products].[Food].[Baked Goods].Children} ON COLUMNS, " + "CrossJoin([Store].[All Stores].[USA].[CA].Children, [Time].[1997].[Q1].Children) ON ROWS " + - "FROM [Sales Ragged]"); + "FROM [Sales]"); TestContext.assertEqualsVerbose( TestContext.fold("Axis #0:\n" + - "{[Measures].[Unit Sales], [Geography].[All Geographys], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n" + + "{[Measures].[Unit Sales], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n" + "Axis #1:\n" + "{[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine]}\n" + "{[Product].[All Products].[Food].[Baked Goods].[Bread]}\n" + @@ -1689,6 +1694,9 @@ public void testCellSetBug() throws SQLException { "{[Store].[All Stores].[USA].[CA].[Los Angeles], [Time].[1997].[Q1].[1]}\n" + "{[Store].[All Stores].[USA].[CA].[Los Angeles], [Time].[1997].[Q1].[2]}\n" + "{[Store].[All Stores].[USA].[CA].[Los Angeles], [Time].[1997].[Q1].[3]}\n" + + "{[Store].[All Stores].[USA].[CA].[San Diego], [Time].[1997].[Q1].[1]}\n" + + "{[Store].[All Stores].[USA].[CA].[San Diego], [Time].[1997].[Q1].[2]}\n" + + "{[Store].[All Stores].[USA].[CA].[San Diego], [Time].[1997].[Q1].[3]}\n" + "{[Store].[All Stores].[USA].[CA].[San Francisco], [Time].[1997].[Q1].[1]}\n" + "{[Store].[All Stores].[USA].[CA].[San Francisco], [Time].[1997].[Q1].[2]}\n" + "{[Store].[All Stores].[USA].[CA].[San Francisco], [Time].[1997].[Q1].[3]}\n" + @@ -1710,12 +1718,18 @@ public void testCellSetBug() throws SQLException { "Row #7: 51\n" + "Row #8: 27\n" + "Row #8: 54\n" + - "Row #9: 6\n" + - "Row #9: 2\n" + - "Row #10: 3\n" + - "Row #10: 7\n" + - "Row #11: 2\n" + - "Row #11: 10\n"), + "Row #9: 54\n" + + "Row #9: 51\n" + + "Row #10: 38\n" + + "Row #10: 48\n" + + "Row #11: 64\n" + + "Row #11: 55\n" + + "Row #12: 6\n" + + "Row #12: 2\n" + + "Row #13: 3\n" + + "Row #13: 7\n" + + "Row #14: 2\n" + + "Row #14: 10\n"), TestContext.toString(cellSet)); } diff --git a/testsrc/org/olap4j/OlapTest.java b/testsrc/org/olap4j/OlapTest.java index 15174f6..911e976 100644 --- a/testsrc/org/olap4j/OlapTest.java +++ b/testsrc/org/olap4j/OlapTest.java @@ -10,6 +10,7 @@ import org.olap4j.metadata.*; import org.olap4j.query.*; +import org.olap4j.test.TestContext; import org.w3c.dom.*; @@ -35,15 +36,17 @@ * @version $Id$ */ public class OlapTest extends TestCase { + final TestContext.Tester tester = TestContext.instance().getTester(); public OlapTest() { super(); } public void testModel() { - try { + Connection jdbcConnection; + if (false) { // define the connection information String schemaUri = "file:/open/mondrian/demo/FoodMart.xml"; String schemaName = "FoodMart"; @@ -54,11 +57,14 @@ public void testModel() { // Create a connection object to the specific implementation of an olap4j source // This is the only provider-specific code Class.forName("mondrian.olap4j.MondrianOlap4jDriver"); - Connection jdbcConnection = DriverManager.getConnection( + jdbcConnection = DriverManager.getConnection( "jdbc:mondrian:Jdbc=" + jdbc + ";User=" + userName + ";Password=" + password + ";Catalog=" + schemaUri); + } else { + jdbcConnection = tester.createConnection(); + } OlapConnection connection = ((OlapWrapper) jdbcConnection).unwrap(OlapConnection.class); @@ -69,7 +75,18 @@ public void testModel() { // The code from here on is generic olap4j stuff // Get a list of the schemas available from this connection and dump their names - NamedList schemas = connection.getCatalogs().get("LOCALDB").getSchemas(); + final String catalogName; + switch (tester.getFlavor()) { + case MONDRIAN: + catalogName = "LOCALDB"; + break; + case XMLA: + default: + catalogName = "FoodMart"; + break; + } + Catalog catalog = connection.getCatalogs().get(catalogName); + NamedList schemas = catalog.getSchemas(); for (Schema schema : schemas) { System.out.println("schema name="+schema.getName()); } @@ -502,7 +519,9 @@ public static void dimensionSelectionsToXml( } } - public static Element hierarchyToXml(Hierarchy hierarchy, Document doc) { + public static Element hierarchyToXml(Hierarchy hierarchy, Document doc) + throws OlapException + { Element hierarchyNode; Element levelNode; Element nameNode; diff --git a/testsrc/org/olap4j/XmlaTester.java b/testsrc/org/olap4j/XmlaTester.java index 7bfdf4f..21dd02a 100644 --- a/testsrc/org/olap4j/XmlaTester.java +++ b/testsrc/org/olap4j/XmlaTester.java @@ -25,6 +25,7 @@ */ public class XmlaTester implements TestContext.Tester { final XmlaOlap4jDriver.Proxy proxy; + private Connection connection; public XmlaTester() throws ClassNotFoundException, IllegalAccessException, @@ -50,6 +51,9 @@ public XmlaTester() } public Connection createConnection() throws SQLException { + if (connection != null) { + return connection; + } try { Class.forName(DRIVER_CLASS_NAME); } catch (ClassNotFoundException e) { @@ -62,10 +66,11 @@ public Connection createConnection() throws SQLException { XmlaOlap4jDriver.Property.UseThreadProxy.name(), "true"); info.setProperty( XmlaOlap4jDriver.Property.Catalog.name(), "FoodMart"); - return + connection = DriverManager.getConnection( getURL(), info); + return connection; } finally { XmlaOlap4jDriver.THREAD_PROXY.set(null); }