diff --git a/build.xml b/build.xml index 8240fd1..a08dc39 100644 --- a/build.xml +++ b/build.xml @@ -15,7 +15,7 @@ - + diff --git a/doc/olap4j_fs.html b/doc/olap4j_fs.html index b34e345..3c49772 100644 --- a/doc/olap4j_fs.html +++ b/doc/olap4j_fs.html @@ -82,7 +82,7 @@

olap4j Specification

Version: 0.9 (beta)
Revision: $Id$ (log)
-Last modified: December 2nd, 2007.

+Last modified: December 10th, 2007.


Contents

@@ -148,6 +148,8 @@

Contents

  • The Member interface
  • The Measure interface
  • The Property interface
  • +
  • The NamedSet interface
  • +
  • The Datatype enum
  • The OlapDatabaseMetaData interface, and schema result sets
      @@ -1302,6 +1304,7 @@
      2.7.2.4. The Cube interface
      • NamedList<Dimension> getDimensions()
      • +
      • NamedList<Hierarchy> getHierarchies()
      • List<Measure> getMeasures()
      • NamedList<NamedSet> getSets()
      • Schema getSchema()
      • @@ -1354,6 +1357,7 @@
        2.7.2.7. The Level interface
      • Level.Type getLevelType()
      • NamedList<Property> getProperties()
      • List<Member> getMembers()
      • +
      • int getCardinality()
      2.7.2.8. The Member interface

      A Member  @@ -1378,6 +1382,7 @@

      2.7.2.8. The Member interface
    1. int getOrdinal()
    2. boolean isHidden()
    3. Member getDataMember()
    4. +
    5. int getChildMemberCount()
    6. 2.7.2.9. The Measure interface
      @@ -1388,6 +1393,13 @@
      2.7.2.9. The Measure interface
      value of each cell, and is usually numeric.

      Every measure is a member of a special dimension called "Measures".

      + +
        +
      • boolean isVisible()
      • +
      • Aggregator getAggregator()
      • +
      • Datatype getDataType()
      • +
      +
      2.7.2.10. The Property interface

      Property  (extends @@ -1407,22 +1419,100 @@

      2.7.2.10. The Property interface
          Property.StandardMemberProperty.CATALOG_NAME);

      Members:

        -
      • Datatype getType()
      • -
      • Scope getScope()
      • -
      • enum Scope { MEMBER, CELL }
      • -
      • enum Datatype { STRING, OTHER, NUMERIC, BOOLEAN }
      • +
      • Datatype getDatatype()
      • +
      • Set<TypeFlag> getType()
      • +
      • ContentType getContentType()
      • +
      • enum TypeFlag { MEMBER, CELL, SYSTEM, BLOB }
      • enum StandardMemberProperty implements Property { CATALOG_NAME, SCHEMA_NAME, CUBE_NAME, ... }
      • enum StandardCellProperty implements Property { BACK_COLOR, CELL_EVALUATION_LIST, ... }
      • +
      • enum ContentType { REGULAR, ID, RELATION_TO_PARENT, ... }
      -
      2.7.2.11. The NamedSet interface
      +
      2.7.2.11. The NamedSet interface

      A NamedSet (extends MetadataElement) describes a set whose value is determined by an MDX expression. It belongs to a cube.

      • Cube getCube()
      • +
      • Expression getExpression()
      + +
      2.7.2.12. The Datatype enum
      + +

      The +Datatype enum describes the type of property and measure values. Because +olap4j drivers need to interoperate with OLE DB for OLAP and XMLA systems, +Datatype values have the same ordinals as in the OLE DB specification, and we +show here the +name and description of the corresponding type in the OLE DB specification. The table shows +the analogous Java type, if there is one.

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      DatatypeJava typeOLE DB typeDescription
      INTEGERintDBTYPE_I4A four-byte, signed integer: INTEGER
      DOUBLEdoubleDBTYPE_R8A double-precision floating-point value: Double
      CURRENCY DBTYPE_CYA currency value: LARGE_INTEGER, Currency is a fixed-point number with four digits to the right of the decimal point. It is stored in an eight-byte signed integer, scaled by 10,000.
      BOOLEANbooleanDBTYPE_BOOLA Boolean value stored in the same way as in Automation: VARIANT_BOOL; 0 means false and ~0 (bitwise, the value is not 0; that is, all bits are set to 1) means true.
      VARIANTObjectDBTYPE_VARIANTAn Automation VARIANT
      UNSIGNED_SHORT-DBTYPE_UI2A two-byte, unsigned integer
      UNSIGNED_INTEGER-DBTYPE_UI4A four-byte, unsigned integer
      LARGE_INTEGERlongDBTYPE_I8An eight-byte, signed integer: LARGE_INTEGER
      STRINGStringDBTYPE_WSTRA null-terminated Unicode character string: wchar_t[length]; If DBTYPE_WSTR is used by itself, the number of bytes allocated for the string, including the null-termination character, is specified by cbMaxLen in the DBBINDING structure. If DBTYPE_WSTR is combined with DBTYPE_BYREF, the number of bytes allocated for the string, including the null-termination character, is at least the length of the string plus two. In either case, the actual length of the string is determined from the bound length value. The maximum length of the string is the number of allocated bytes divided by sizeof(wchar_t) and truncated to the nearest integer.
      +

      2.7.3. The OlapDatabaseMetaData interface, and methods which return schema rowsets

      diff --git a/src/mondrian/olap4j/MondrianOlap4jCube.java b/src/mondrian/olap4j/MondrianOlap4jCube.java index e93a424..773ee49 100644 --- a/src/mondrian/olap4j/MondrianOlap4jCube.java +++ b/src/mondrian/olap4j/MondrianOlap4jCube.java @@ -9,9 +9,8 @@ */ package mondrian.olap4j; -import mondrian.olap.Id; -import mondrian.olap.SchemaReader; import org.olap4j.metadata.*; +import org.olap4j.OlapException; import java.util.*; @@ -64,10 +63,24 @@ public NamedList getDimensions() { return (NamedList) list; } + public NamedList getHierarchies() { + List list = + new NamedListImpl(); + for (mondrian.olap.Dimension dimension : cube.getDimensions()) { + for (mondrian.olap.Hierarchy hierarchy : dimension.getHierarchies()) { + list.add( + new MondrianOlap4jHierarchy( + olap4jSchema, hierarchy)); + } + } + return (NamedList) list; + } + public List getMeasures() { - final Level measuresLevel = - getDimensions().get("Measures").getDefaultHierarchy() - .getLevels().get(0); + final MondrianOlap4jLevel measuresLevel = + (MondrianOlap4jLevel) + getDimensions().get("Measures").getDefaultHierarchy() + .getLevels().get(0); return (List) measuresLevel.getMembers(); } @@ -104,15 +117,18 @@ public String getDescription(Locale locale) { return cube.getDescription(); } - public Member lookupMember(String... nameParts) { + public MondrianOlap4jMember lookupMember(String... nameParts) { final MondrianOlap4jConnection olap4jConnection = olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; - final SchemaReader schemaReader = + final mondrian.olap.SchemaReader schemaReader = cube.getSchemaReader(olap4jConnection.connection.getRole()); - final List segmentList = new ArrayList(); + final List segmentList = + new ArrayList(); for (String namePart : nameParts) { - segmentList.add(new Id.Segment(namePart, Id.Quoting.QUOTED)); + segmentList.add( + new mondrian.olap.Id.Segment( + namePart, mondrian.olap.Id.Quoting.QUOTED)); } final mondrian.olap.Member member = schemaReader.getMemberByUniqueName(segmentList, false); @@ -124,24 +140,25 @@ public Member lookupMember(String... nameParts) { public List lookupMembers( Set treeOps, - String... nameParts) + String... nameParts) throws OlapException { - final Member member = lookupMember(nameParts); + final MondrianOlap4jMember 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(); + final List list = + new ArrayList(); if (treeOps.contains(Member.TreeOp.ANCESTORS)) { - for (Member m = member.getParentMember(); + for (MondrianOlap4jMember m = member.getParentMember(); m != null; m = m.getParentMember()) { list.add(0, m); } } else if (treeOps.contains(Member.TreeOp.PARENT)) { - final Member parentMember = member.getParentMember(); + final MondrianOlap4jMember parentMember = member.getParentMember(); if (parentMember != null) { list.add(parentMember); } @@ -150,21 +167,22 @@ public List lookupMembers( // Add siblings. Siblings which occur after the member are deferred, // because they occur after children and descendants in the // hierarchical ordering. - List remainingSiblingsList = null; + List remainingSiblingsList = null; if (treeOps.contains(Member.TreeOp.SIBLINGS)) { - final Member parentMember = member.getParentMember(); - NamedList siblingMembers; + final MondrianOlap4jMember parentMember = member.getParentMember(); + NamedList siblingMembers; if (parentMember != null) { siblingMembers = parentMember.getChildMembers(); } else { - siblingMembers = member.getHierarchy().getRootMembers(); + siblingMembers = + (NamedList) member.getHierarchy().getRootMembers(); } - List targetList = list; - for (Member siblingMember : siblingMembers) { + List targetList = list; + for (MondrianOlap4jMember siblingMember : siblingMembers) { if (siblingMember.equals(member)) { targetList = remainingSiblingsList = - new ArrayList(); + new ArrayList(); } else { targetList.add(siblingMember); } @@ -178,12 +196,12 @@ public List lookupMembers( // Add descendants and/or children. if (treeOps.contains(Member.TreeOp.DESCENDANTS)) { - for (Member childMember : member.getChildMembers()) { + for (MondrianOlap4jMember childMember : member.getChildMembers()) { list.add(childMember); addDescendants(list, childMember); } } else if (treeOps.contains(Member.TreeOp.CHILDREN)) { - for (Member childMember : member.getChildMembers()) { + for (MondrianOlap4jMember childMember : member.getChildMembers()) { list.add(childMember); } } @@ -192,11 +210,14 @@ public List lookupMembers( if (remainingSiblingsList != null) { list.addAll(remainingSiblingsList); } - return list; + return (List) list; } - private static void addDescendants(List list, Member member) { - for (Member childMember : member.getChildMembers()) { + private static void addDescendants( + List list, + MondrianOlap4jMember member) + { + for (MondrianOlap4jMember childMember : member.getChildMembers()) { list.add(childMember); addDescendants(list, childMember); } diff --git a/src/mondrian/olap4j/MondrianOlap4jLevel.java b/src/mondrian/olap4j/MondrianOlap4jLevel.java index 9059b37..b823371 100644 --- a/src/mondrian/olap4j/MondrianOlap4jLevel.java +++ b/src/mondrian/olap4j/MondrianOlap4jLevel.java @@ -9,13 +9,7 @@ */ 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.*; @@ -31,7 +25,7 @@ class MondrianOlap4jLevel implements Level, Named { private final MondrianOlap4jSchema olap4jSchema; private final mondrian.olap.Level level; - public MondrianOlap4jLevel( + MondrianOlap4jLevel( MondrianOlap4jSchema olap4jSchema, mondrian.olap.Level level) { @@ -83,7 +77,7 @@ protected String getName(Property property) { public List getMembers() { final MondrianOlap4jConnection olap4jConnection = olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; - final SchemaReader schemaReader = + final mondrian.olap.SchemaReader schemaReader = olap4jConnection.connection.getSchemaReader(); final mondrian.olap.Member[] levelMembers = schemaReader.getLevelMembers(level, true); @@ -112,7 +106,12 @@ public String getCaption(Locale locale) { } public String getDescription(Locale locale) { - throw new UnsupportedOperationException(); + // todo: localize + return level.getDescription(); + } + + public int getCardinality() { + return level.getApproxRowCount(); } } diff --git a/src/mondrian/olap4j/MondrianOlap4jMember.java b/src/mondrian/olap4j/MondrianOlap4jMember.java index 0ccb561..4918fcd 100644 --- a/src/mondrian/olap4j/MondrianOlap4jMember.java +++ b/src/mondrian/olap4j/MondrianOlap4jMember.java @@ -10,19 +10,12 @@ package mondrian.olap4j; 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 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, @@ -74,7 +67,11 @@ public int size() { }; } - public Member getParentMember() { + public int getChildMemberCount() { + return olap4jSchema.schemaReader.getMemberChildren(member).length; + } + + public MondrianOlap4jMember getParentMember() { final mondrian.olap.Member parentMember = member.getParentMember(); if (parentMember == null) { return null; diff --git a/src/mondrian/olap4j/MondrianOlap4jProperty.java b/src/mondrian/olap4j/MondrianOlap4jProperty.java index 3c2eedc..078a7da 100644 --- a/src/mondrian/olap4j/MondrianOlap4jProperty.java +++ b/src/mondrian/olap4j/MondrianOlap4jProperty.java @@ -1,61 +1,78 @@ -/* -// 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 +/* +// 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 org.olap4j.metadata.Datatype; + +import java.util.*; + +/** + * 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() { + switch (property.getType()) { + case TYPE_BOOLEAN: + return Datatype.BOOLEAN; + case TYPE_NUMERIC: + return Datatype.UNSIGNED_INTEGER; + case TYPE_STRING: + return Datatype.STRING; + case TYPE_OTHER: + return Datatype.VARIANT; + default: + throw new RuntimeException("unexpected: " + property.getType()); + } + } + + public Set getType() { + return TypeFlag.forMask( + property.isCellProperty() + ? TypeFlag.CELL.xmlaOrdinal + : TypeFlag.MEMBER.xmlaOrdinal); + } + + 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(); + } + + public ContentType getContentType() { + return ContentType.REGULAR; + } +} + +// End MondrianOlap4jProperty.java diff --git a/src/org/olap4j/CellSet.java b/src/org/olap4j/CellSet.java index 2d3fa86..e6cc907 100755 --- a/src/org/olap4j/CellSet.java +++ b/src/org/olap4j/CellSet.java @@ -78,7 +78,9 @@ public interface CellSet extends ResultSet, OlapWrapper { * Retrieves the CellSetAxis representing the filter axis. * *

      This axis always has one row, and contains one member for each - * dimension included on in the WHERE clause of the MDX statement. + * dimension not included in any other axis. Some of these dimensions may + * have been explicitly mentioned in the WHERE clause of the MDX statement; + * others dimensions are represented by their default member. * * @return the filter axis */ @@ -129,6 +131,9 @@ public interface CellSet extends ResultSet, OlapWrapper { * * @return Cell * + * @throws IllegalArgumentException if positions does not have the same + * number of members as the cell set has axes + * * @throws IndexOutOfBoundsException if positions lie outside CellSet * bounds */ diff --git a/src/org/olap4j/CellSetAxisMetaData.java b/src/org/olap4j/CellSetAxisMetaData.java index e699b3a..3f1db1e 100755 --- a/src/org/olap4j/CellSetAxisMetaData.java +++ b/src/org/olap4j/CellSetAxisMetaData.java @@ -72,9 +72,14 @@ public interface CellSetAxisMetaData { /** * Returns the member properties which are returned on this axis. * + *

      This method does not return a {@link NamedList} because the names of + * the properties are not necessarily unique; for example, there might be + * two hierarchies on the axis, each of which returns the DISPLAY_INFO + * property.

      + * * @return list of member properties on this Axis */ - NamedList getProperties(); + List getProperties(); } // End CellSetAxisMetaData.java diff --git a/src/org/olap4j/OlapStatement.java b/src/org/olap4j/OlapStatement.java index 689cae4..e3df6ee 100644 --- a/src/org/olap4j/OlapStatement.java +++ b/src/org/olap4j/OlapStatement.java @@ -29,8 +29,13 @@ public interface OlapStatement extends Statement, OlapWrapper { * Executes an OLAP statement. * * @param mdx MDX SELECT statement + * * @return Cell set - * @throws OlapException if error occurs + * + * @throws OlapException if a database access error occurs, + * this method is called on a closed OlapStatement, + * the query times out (see {@link #setQueryTimeout(int)}) + * or another thread cancels the statement (see {@link #cancel()}) */ CellSet executeOlapQuery(String mdx) throws OlapException; @@ -40,8 +45,13 @@ public interface OlapStatement extends Statement, OlapWrapper { *

      Validates the parse tree before executing it. * * @param selectNode Parse tree of MDX SELECT statement + * * @return Cell set - * @throws OlapException if error occurs + * + * @throws OlapException if a database access error occurs, + * this method is called on a closed OlapStatement, + * the query times out (see {@link #setQueryTimeout(int)}) + * or another thread cancels the statement (see {@link #cancel()}) */ CellSet executeOlapQuery(SelectNode selectNode) throws OlapException; } diff --git a/src/org/olap4j/mdx/IdentifierNode.java b/src/org/olap4j/mdx/IdentifierNode.java index b549439..4bf9326 100644 --- a/src/org/olap4j/mdx/IdentifierNode.java +++ b/src/org/olap4j/mdx/IdentifierNode.java @@ -136,6 +136,178 @@ public String toString() { return buf.toString(); } + /** + * Parses an MDX identifier into a list of segments. + * + *

      Each segment is a name combined with a description of how the name + * was {@link Quoting quoted}. For example, + * + *

      + * parseIdentifier("[Customers].USA.[South Dakota].[Sioux Falls].&[1245]") + *
      + * + * returns + * + *
      + * { Segment("Customers", QUOTED), + * Segment("USA", UNQUOTED), + * Segment("South Dakota", QUOTED), + * Segment("Sioux Falls", QUOTED), + * Segment("1245", KEY) } + *
      + * + * @see org.olap4j.metadata.Cube#lookupMember(String[]) + * + * @param identifier MDX identifier string + * @return List of name segments + */ + public static List parseIdentifier(String identifier) { + if (!identifier.startsWith("[")) { + return Collections.singletonList( + new Segment(null, identifier, Quoting.UNQUOTED)); + } + + List list = new ArrayList(); + int i = 0; + Quoting type; + while (i < identifier.length()) { + if (identifier.charAt(i) != '&' && identifier.charAt(i) != '[') { + throw new RuntimeException("invalid member '" + identifier + "'"); + } + + if (identifier.charAt(i) == '&') { + i++; + type = Quoting.KEY; + } else { + type = Quoting.QUOTED; + } + + if (identifier.charAt(i) != '[') { + throw new RuntimeException("invalid member '" + identifier + "'"); + } + + int j = getEndIndex(identifier, i + 1); + if (j == -1) { + throw new RuntimeException("invalid member '" + identifier + "'"); + } + + list.add( + new Segment( + null, + replace(identifier.substring(i + 1, j), "]]", "]"), + type)); + + i = j + 2; + } + return list; + } + + /** + * Returns the end of the current segment. + * + * @param s Identifier string + * @param i Start of identifier segment + * @return End of segment + */ + private static int getEndIndex(String s, int i) { + while (i < s.length()) { + char ch = s.charAt(i); + if (ch == ']') { + if (i + 1 < s.length() && s.charAt(i + 1) == ']') { // found ]] => skip + i += 2; + } else { + return i; + } + } else { + i++; + } + } + return -1; + } + + /** + * Returns a string with every occurrence of a seek string replaced with + * another. + * + * @param s String to act on + * @param find String to find + * @param replace String to replace it with + * @return The modified string + */ + private static String replace( + String s, + String find, + String replace) + { + // let's be optimistic + int found = s.indexOf(find); + if (found == -1) { + return s; + } + StringBuilder sb = new StringBuilder(s.length() + 20); + int start = 0; + char[] chars = s.toCharArray(); + final int step = find.length(); + if (step == 0) { + // Special case where find is "". + sb.append(s); + replace(sb, 0, find, replace); + } else { + for (;;) { + sb.append(chars, start, found-start); + if (found == s.length()) { + break; + } + sb.append(replace); + start = found + step; + found = s.indexOf(find, start); + if (found == -1) { + found = s.length(); + } + } + } + return sb.toString(); + } + + /** + * Replaces all occurrences of a string in a buffer with another. + * + * @param buf String buffer to act on + * @param start Ordinal within find to start searching + * @param find String to find + * @param replace String to replace it with + * @return The string buffer + */ + private static StringBuilder replace( + StringBuilder buf, + int start, + String find, + String replace) + { + // Search and replace from the end towards the start, to avoid O(n ^ 2) + // copying if the string occurs very commonly. + int findLength = find.length(); + if (findLength == 0) { + // Special case where the seek string is empty. + for (int j = buf.length(); j >= 0; --j) { + buf.insert(j, replace); + } + return buf; + } + int k = buf.length(); + while (k > 0) { + int i = buf.lastIndexOf(find, k); + if (i < start) { + break; + } + buf.replace(i, i + find.length(), replace); + // Step back far enough to ensure that the beginning of the section + // we just replaced does not cause a match. + k = i - findLength; + } + return buf; + } + /** * Component in a compound identifier. It is described by its name and how * the name is quoted. @@ -179,6 +351,22 @@ public Segment(String name) { this(null, name, Quoting.QUOTED); } + /** + * Returns a string representation of this Segment. + * + *

      For example, "[Foo]", "&[123]", "Abc". + * + * @return String representation of this Segment + */ + public String toString() { + switch (quoting) { + case UNQUOTED: //return name; Disabled to pass old tests... + case QUOTED: return "[" + name + "]"; + case KEY: return "&[" + name + "]"; + default: return "UNKNOWN:" + name; + } + } + /** * Returns the region of the source code which this Segment was created * from, if it was created by parsing. diff --git a/src/org/olap4j/metadata/Cube.java b/src/org/olap4j/metadata/Cube.java index c661ec2..75e76ad 100644 --- a/src/org/olap4j/metadata/Cube.java +++ b/src/org/olap4j/metadata/Cube.java @@ -9,6 +9,8 @@ */ package org.olap4j.metadata; +import org.olap4j.OlapException; + import java.util.*; /** @@ -18,6 +20,8 @@ * {@link Dimension}s and a list of {@link Measure}s. It may also have one or * more {@link NamedSet}s. * + * @see org.olap4j.metadata.Cube#getMeasures() + * * @author jhyde * @version $Id$ * @since Aug 22, 2006 @@ -42,6 +46,18 @@ public interface Cube extends MetadataElement { */ NamedList getDimensions(); + /** + * Returns a list of {@link Hierarchy} objects in this Cube. + * + *

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

      + * + * @see org.olap4j.OlapDatabaseMetaData#getHierarchies(String, String, String, String, String) + * + * @return list of Dimensions + */ + NamedList getHierarchies(); + /** * Returns a list of {@link Measure} objects in this Cube. * @@ -102,9 +118,12 @@ public interface Cube extends MetadataElement { * dimension. * * @param nameParts Components of the fully-qualified member name + * * @return member with the given name, or null if not found + * + * @throws OlapException if error occurs */ - Member lookupMember(String... nameParts); + Member lookupMember(String... nameParts) throws OlapException; /** * Finds a collection of members in the current Cube related to a given @@ -114,23 +133,26 @@ public interface Cube extends MetadataElement { * 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. + *

      The returned collection is sorted by level number then by member + * ordinal. 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] - *
      + *
      
      +     * [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 @@ -151,10 +173,12 @@ public interface Cube extends MetadataElement { * * @return collection of members related to the given member, or empty * set if the member is not found + * + * @throws OlapException if error occurs */ List lookupMembers( Set treeOps, - String... nameParts); + String... nameParts) throws OlapException; } // End Cube.java diff --git a/src/org/olap4j/metadata/Datatype.java b/src/org/olap4j/metadata/Datatype.java new file mode 100644 index 0000000..575ac50 --- /dev/null +++ b/src/org/olap4j/metadata/Datatype.java @@ -0,0 +1,98 @@ +/* +// $Id: Property.java 40 2007-11-18 01:13:26Z jhyde $ +// 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-2007 Julian Hyde +// All Rights Reserved. +// You must accept the terms of that agreement to use this software. +*/ +package org.olap4j.metadata; + +import java.util.Map; +import java.util.HashMap; + +/** + * Enumeration of the allowable data types of a Property or Measure. + * + *

      The values derive from the OLE DB specification, specifically a + * subset of the OLE DB Types Indicators returned by SQL Server. + * + * @author jhyde + * @version $Id: Property.java 40 2007-11-18 01:13:26Z jhyde $ + * @since Aug 23, 2006 + */ +public enum Datatype { + /* + * The following values exactly match VARENUM + * in Automation and may be used in VARIANT. + */ + INTEGER(3, "DBTYPE_I4", "A four-byte, signed integer: INTEGER"), + + DOUBLE(5, "DBTYPE_R8", "A double-precision floating-point value: Double"), + + CURRENCY(6, "DBTYPE_CY", "A currency value: LARGE_INTEGER, Currency is a fixed-point number with four digits to the right of the decimal point. It is stored in an eight-byte signed integer, scaled by 10,000."), + + BOOLEAN(11, "DBTYPE_BOOL", "A Boolean value stored in the same way as in Automation: VARIANT_BOOL; 0 means false and ~0 (bitwise, the value is not 0; that is, all bits are set to 1) means true."), + + /** + * Used by SQL Server for value. + */ + VARIANT(12, "DBTYPE_VARIANT", "An Automation VARIANT"), + + /** + * Used by SQL Server for font size. + */ + UNSIGNED_SHORT(18, "DBTYPE_UI2", "A two-byte, unsigned integer"), + + /** + * Used by SQL Server for colors, font flags and cell ordinal. + */ + UNSIGNED_INTEGER(19, "DBTYPE_UI4", "A four-byte, unsigned integer"), + + /* + * The following values exactly match VARENUM + * in Automation but cannot be used in VARIANT. + */ + LARGE_INTEGER(20, "DBTYPE_I8", "An eight-byte, signed integer: LARGE_INTEGER"), + + /* + * The following values are not in VARENUM in OLE. + */ + STRING(130, "DBTYPE_WSTR", "A null-terminated Unicode character string: wchar_t[length]; If DBTYPE_WSTR is used by itself, the number of bytes allocated for the string, including the null-termination character, is specified by cbMaxLen in the DBBINDING structure. If DBTYPE_WSTR is combined with DBTYPE_BYREF, the number of bytes allocated for the string, including the null-termination character, is at least the length of the string plus two. In either case, the actual length of the string is determined from the bound length value. The maximum length of the string is the number of allocated bytes divided by sizeof(wchar_t) and truncated to the nearest integer."); + + private final int xmlaOrdinal; + + private static final Map xmlaMap = + new HashMap(); + + static { + for (Datatype datatype : values()) { + xmlaMap.put(datatype.xmlaOrdinal, datatype); + } + } + + Datatype( + int xmlaOrdinal, + String dbTypeIndicator, + String description) + { + this.xmlaOrdinal = xmlaOrdinal; + } + + /** + * Looks up a Datatype by its XMLA ordinal. + * + * @param xmlaOrdinal Ordinal of a Datatype according to the XMLA + * specification. + * + * @return Datatype with the given ordinal, or null if there is no + * such Datatype + */ + public static Datatype forXmlaOrdinal(int xmlaOrdinal) { + return xmlaMap.get(xmlaOrdinal); + } + +} + +// End Datatype.java diff --git a/src/org/olap4j/metadata/Hierarchy.java b/src/org/olap4j/metadata/Hierarchy.java index dbc24bf..bd1734b 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. @@ -70,9 +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 + * mere {@link java.util.List} because the members of the root level are + * known to have unique names. + * * @return root members of this hierarchy */ - NamedList getRootMembers(); + NamedList getRootMembers() throws OlapException; } // End Hierarchy.java diff --git a/src/org/olap4j/metadata/Level.java b/src/org/olap4j/metadata/Level.java index da5fa5b..b83e5b1 100644 --- a/src/org/olap4j/metadata/Level.java +++ b/src/org/olap4j/metadata/Level.java @@ -9,7 +9,9 @@ */ package org.olap4j.metadata; -import java.util.List; +import org.olap4j.OlapException; + +import java.util.*; /** * Group of {@link Member} objects in a {@link Hierarchy}, @@ -81,7 +83,14 @@ public interface Level extends MetadataElement { * * @return List of members in this Level */ - List getMembers(); + List getMembers() throws OlapException; + + /** + * Returns the number of members in this Level. + * + * @return number of members + */ + int getCardinality(); /** * Enumeration of the types of a {@link Level}. @@ -199,6 +208,15 @@ public enum Type { private final int xmlaOrdinal; + private static final Map xmlaMap = + new HashMap(); + + static { + for (Type type : values()) { + xmlaMap.put(type.xmlaOrdinal, type); + } + } + private Type(int code) { this.xmlaOrdinal = code; } @@ -214,7 +232,20 @@ private Type(int code) { public int xmlaOrdinal() { return xmlaOrdinal; } - + + /** + * Looks up a Type by its XMLA ordinal. + * + * @param xmlaOrdinal Ordinal of a level Type according to XMLA + * specification. + * + * @return Type with the given ordinal, or null if there is no such + * Type + */ + public static Type forXmlaOrdinal(int xmlaOrdinal) { + return xmlaMap.get(xmlaOrdinal); + } + /** * Returns whether this is a time-related level * ({@link #TimeYears}, {@link #TimeQuarters}, {@link #TimeMonths}, diff --git a/src/org/olap4j/metadata/Measure.java b/src/org/olap4j/metadata/Measure.java index 1f1d87c..c1951f4 100644 --- a/src/org/olap4j/metadata/Measure.java +++ b/src/org/olap4j/metadata/Measure.java @@ -9,6 +9,8 @@ */ package org.olap4j.metadata; +import java.util.*; + /** * Data value of primary interest to the user browsing the cube. * @@ -20,6 +22,26 @@ * @since Oct 13, 2006 */ public interface Measure extends Member { + /** + * Returns the Aggregator of this Measure. + * + * @return Aggregator + */ + Aggregator getAggregator(); + + /** + * Returns the data type of this Measure. + * + * @return data type + */ + Datatype getDatatype(); + + /** + * Returns whether this Measure is visible. + * + * @return whether this Measure is visible + */ + boolean isVisible(); /** * Enumeration of the aggregate functions which can be used to derive a @@ -78,6 +100,21 @@ enum Aggregator { private final int xmlaOrdinal; + private static final Map xmlaMap = + new HashMap(); + + static { + for (Aggregator aggregator : values()) { + xmlaMap.put(aggregator.xmlaOrdinal, aggregator); + } + } + + /** + * Creates an Aggregator. + * + * @param xmlaOrdinal Ordinal of the aggregator in the XMLA + * specification + */ private Aggregator(int xmlaOrdinal) { this.xmlaOrdinal = xmlaOrdinal; } @@ -93,6 +130,19 @@ private Aggregator(int xmlaOrdinal) { public final int xmlaOrdinal() { return xmlaOrdinal; } + + /** + * Looks up an Aggregator by its XMLA ordinal. + * + * @param xmlaOrdinal Ordinal of an Aggregator according to the XMLA + * specification. + * + * @return Aggregator with the given ordinal, or null if there is no + * such Aggregator + */ + public static Aggregator forXmlaOrdinal(int xmlaOrdinal) { + return xmlaMap.get(xmlaOrdinal); + } } } diff --git a/src/org/olap4j/metadata/Member.java b/src/org/olap4j/metadata/Member.java index cfb220c..8dc2b0b 100644 --- a/src/org/olap4j/metadata/Member.java +++ b/src/org/olap4j/metadata/Member.java @@ -38,7 +38,17 @@ public interface Member extends MetadataElement { * * @return children of this member */ - NamedList getChildMembers(); + NamedList getChildMembers() throws OlapException; + + /** + * Returns the number of children this Member has. + * + *

      This method has the same effect as + * getChildMembers().size(), but is typically less expensive. + * + * @return number of children + */ + int getChildMemberCount(); /** * Returns the parent of this Member, or null if it has no parent. @@ -169,7 +179,7 @@ private Type(int ordinal) { ParseTreeNode getExpression(); /** - * Returns array of all members, which are ancestor to this. + * Returns array of all members which are ancestor to this. * * @return ancestor Members */ diff --git a/src/org/olap4j/metadata/MetadataElement.java b/src/org/olap4j/metadata/MetadataElement.java index 3b9f240..e251bb4 100644 --- a/src/org/olap4j/metadata/MetadataElement.java +++ b/src/org/olap4j/metadata/MetadataElement.java @@ -36,8 +36,10 @@ public interface MetadataElement { /** * Returns the caption of this element in the given locale. * - * If locale is null or if no caption has been defined for the - * element in that locale, returns the caption in base locale. + *

      If locale is null or if no caption has been defined for + * the element in that locale, returns the caption in base locale.

      + * + *

      This method may return the empty string, but never returns null.

      * * @param locale Locale * @return Caption of this element in the given locale, or the base locale; @@ -48,8 +50,11 @@ public interface MetadataElement { /** * Returns the description of this element in the given locale. * - * If locale is null or if no description has been defined for - * the element in that locale, returns the description in base locale. + *

      If locale is null or if no description has been defined + * for the element in that locale, returns the description in base + * locale.

      + * + *

      This method may return the empty string, but never returns null.

      * * @param locale Locale * @return description of this element in the given locale, or the base diff --git a/src/org/olap4j/metadata/Property.java b/src/org/olap4j/metadata/Property.java index fc7a0af..a7fd48a 100644 --- a/src/org/olap4j/metadata/Property.java +++ b/src/org/olap4j/metadata/Property.java @@ -9,7 +9,7 @@ */ package org.olap4j.metadata; -import java.util.Locale; +import java.util.*; /** * Definition of a property of a {@link Member} or @@ -28,29 +28,113 @@ public interface Property extends MetadataElement { Datatype getDatatype(); /** - * Returns the scope of this property. + * Returns a set of flags which describe the type of this Property. * - * @return scope of this Property + * @return type of this Property */ - Scope getScope(); + Set getType(); /** - * Enumeration of the scope of a Property: whether it belongs to a member - * or a cell. + * Returns the content type of this Property. + * + * @return content type */ - enum Scope { - MEMBER, - CELL - } + ContentType getContentType(); /** - * Enumeration of the allowable data types of a Property. + * Enumeration of aspects of the type of a Property. In particular, whether + * it belongs to a member or a cell. + * + *

      The values are as specified by XMLA for the PROPERTY_TYPE attribute + * of the MDSCHEMA_PROPERTIES data set. + * For example, XMLA specifies that the value 9 (0x1 | 0x8) means that a + * property belongs to a member and is a binary large object (BLOB). + * In this case, {@link Property#getType} will return the {@link Set} + * {{@link #MEMBER}, {@link #BLOB}}. */ - enum Datatype { - TYPE_STRING, - TYPE_OTHER, - TYPE_NUMERIC, - TYPE_BOOLEAN + enum TypeFlag { + /** + * Identifies a property of a member. This property can be used in the + * DIMENSION PROPERTIES clause of the SELECT statement. + */ + MEMBER(1), + + /** + * Identifies a property of a cell. This property can be used in the + * CELL PROPERTIES clause that occurs at the end of the SELECT + * statement. + */ + CELL(2), + + /** + * Identifies an internal property. + */ + SYSTEM(4), + + /** + * Identifies a property which contains a binary large object (blob). + */ + BLOB(8); + + public final int xmlaOrdinal; + private static final Map xmlaMap = + new HashMap(); + + static { + for (TypeFlag typeFlag : values()) { + xmlaMap.put(typeFlag.xmlaOrdinal, typeFlag); + } + } + + private static final Set CELL_TYPE_FLAG = + Collections.unmodifiableSet(EnumSet.of(TypeFlag.CELL)); + private static final Set MEMBER_TYPE_FLAG = + Collections.unmodifiableSet(EnumSet.of(TypeFlag.MEMBER)); + + private TypeFlag(int xmlaOrdinal) { + this.xmlaOrdinal = xmlaOrdinal; + } + + /** + * Looks up a TypeFlag by its XMLA ordinal. + * + * @param xmlaOrdinal Ordinal of a TypeFlag according to the XMLA + * specification. + * + * @return TypeFlag with the given ordinal, or null if there is no + * such TypeFlag + */ + public static TypeFlag forXmlaOrdinal(int xmlaOrdinal) { + return xmlaMap.get(xmlaOrdinal); + } + + /** + * Creates a set of TypeFlag values by parsing a mask. + * + *

      For example, forMask(9) returns the set + * {{@link #MEMBER}, {@link #BLOB}} because 9 = MEMBER (1) | BLOB (8). + * + * @param xmlaOrdinalMask Bit mask + * @return Set of TypeFlag values + */ + public static Set forMask(int xmlaOrdinalMask) { + switch (xmlaOrdinalMask) { + // Optimize common cases {MEMBER} and {CELL}. + case 1: + return MEMBER_TYPE_FLAG; + case 2: + return CELL_TYPE_FLAG; + default: + Set type = + EnumSet.noneOf(TypeFlag.class); + for (TypeFlag typeFlag : values()) { + if ((xmlaOrdinalMask & typeFlag.xmlaOrdinal) != 0) { + type.add(typeFlag); + } + } + return type; + } + } } /** @@ -83,110 +167,110 @@ enum StandardMemberProperty implements Property { * Definition of the property which * holds the name of the current catalog. */ - CATALOG_NAME(Datatype.TYPE_STRING, 10, false, "Optional. The name of the catalog to which this member belongs. NULL if the provider does not support catalogs."), + CATALOG_NAME(Datatype.STRING, 10, false, "Optional. The name of the catalog to which this member belongs. NULL if the provider does not support catalogs."), /** * Definition of the property which * holds the name of the current schema. */ - SCHEMA_NAME(Datatype.TYPE_STRING, 11, false, "Optional. The name of the schema to which this member belongs. NULL if the provider does not support schemas."), + SCHEMA_NAME(Datatype.STRING, 11, false, "Optional. The name of the schema to which this member belongs. NULL if the provider does not support schemas."), /** * Definition of the property which * holds the name of the current cube. */ - CUBE_NAME(Datatype.TYPE_STRING, 12, false, "Required. Name of the cube to which this member belongs."), + CUBE_NAME(Datatype.STRING, 12, false, "Required. Name of the cube to which this member belongs."), /** * Definition of the property which * holds the unique name of the current dimension. */ - DIMENSION_UNIQUE_NAME(Datatype.TYPE_STRING, 13, false, "Required. Unique name of the dimension to which this member belongs. For providers that generate unique names by qualification, each component of this name is delimited."), + DIMENSION_UNIQUE_NAME(Datatype.STRING, 13, false, "Required. Unique name of the dimension to which this member belongs. For providers that generate unique names by qualification, each component of this name is delimited."), /** * Definition of the property which * holds the unique name of the current hierarchy. */ - HIERARCHY_UNIQUE_NAME(Datatype.TYPE_STRING, 14, false, "Required. Unique name of the hierarchy. If the member belongs to more than one hierarchy, there is one row for each hierarchy to which it belongs. For providers that generate unique names by qualification, each component of this name is delimited."), + HIERARCHY_UNIQUE_NAME(Datatype.STRING, 14, false, "Required. Unique name of the hierarchy. If the member belongs to more than one hierarchy, there is one row for each hierarchy to which it belongs. For providers that generate unique names by qualification, each component of this name is delimited."), /** * Definition of the property which * holds the unique name of the current level. */ - LEVEL_UNIQUE_NAME(Datatype.TYPE_STRING, 15, false, "Required. Unique name of the level to which the member belongs. For providers that generate unique names by qualification, each component of this name is delimited."), + LEVEL_UNIQUE_NAME(Datatype.STRING, 15, false, "Required. Unique name of the level to which the member belongs. For providers that generate unique names by qualification, each component of this name is delimited."), /** * Definition of the property which * holds the ordinal of the current level. */ - LEVEL_NUMBER(Datatype.TYPE_STRING, 16, false, "Required. The distance of the member from the root of the hierarchy. The root level is zero."), + LEVEL_NUMBER(Datatype.UNSIGNED_INTEGER, 16, false, "Required. The distance of the member from the root of the hierarchy. The root level is zero."), /** * Definition of the property which * holds the ordinal of the current member. */ - MEMBER_ORDINAL(Datatype.TYPE_NUMERIC, 17, false, "Required. Ordinal number of the member. Sort rank of the member when members of this dimension are sorted in their natural sort order. If providers do not have the concept of natural ordering, this should be the rank when sorted by MEMBER_NAME."), + MEMBER_ORDINAL(Datatype.UNSIGNED_INTEGER, 17, false, "Required. Ordinal number of the member. Sort rank of the member when members of this dimension are sorted in their natural sort order. If providers do not have the concept of natural ordering, this should be the rank when sorted by MEMBER_NAME."), /** * Definition of the property which * holds the name of the current member. */ - MEMBER_NAME(Datatype.TYPE_STRING, 18, false, "Required. Name of the member."), + MEMBER_NAME(Datatype.STRING, 18, false, "Required. Name of the member."), /** * Definition of the property which * holds the unique name of the current member. */ - MEMBER_UNIQUE_NAME(Datatype.TYPE_STRING, 19, false, "Required. Unique name of the member. For providers that generate unique names by qualification, each component of this name is delimited."), + MEMBER_UNIQUE_NAME(Datatype.STRING, 19, false, "Required. Unique name of the member. For providers that generate unique names by qualification, each component of this name is delimited."), /** * Definition of the property which * holds the type of the member. */ - MEMBER_TYPE(Datatype.TYPE_STRING, 20, false, "Required. Type of the member. Can be one of the following values: MDMEMBER_Datatype.TYPE_REGULAR, MDMEMBER_Datatype.TYPE_ALL, MDMEMBER_Datatype.TYPE_FORMULA, MDMEMBER_Datatype.TYPE_MEASURE, MDMEMBER_Datatype.TYPE_UNKNOWN. MDMEMBER_Datatype.TYPE_FORMULA takes precedence over MDMEMBER_Datatype.TYPE_MEASURE. Therefore, if there is a formula (calculated) member on the Measures dimension, it is listed as MDMEMBER_Datatype.TYPE_FORMULA."), + MEMBER_TYPE(Datatype.STRING, 20, false, "Required. Type of the member. Can be one of the following values: MDMEMBER_Datatype.TYPE_REGULAR, MDMEMBER_Datatype.TYPE_ALL, MDMEMBER_Datatype.TYPE_FORMULA, MDMEMBER_Datatype.TYPE_MEASURE, MDMEMBER_Datatype.TYPE_UNKNOWN. MDMEMBER_Datatype.TYPE_FORMULA takes precedence over MDMEMBER_Datatype.TYPE_MEASURE. Therefore, if there is a formula (calculated) member on the Measures dimension, it is listed as MDMEMBER_Datatype.TYPE_FORMULA."), /** * Definition of the property which * holds the GUID of the member */ - MEMBER_GUID(Datatype.TYPE_STRING, 21, false, "Optional. Member GUID. NULL if no GUID exists."), + MEMBER_GUID(Datatype.STRING, 21, false, "Optional. Member GUID. NULL if no GUID exists."), /** * Definition of the property which * holds the label or caption associated with the member, or the * member's name if no caption is defined. */ - MEMBER_CAPTION(Datatype.TYPE_STRING, 22, false, "Required. A label or caption associated with the member. Used primarily for display purposes. If a caption does not exist, MEMBER_NAME is returned."), + MEMBER_CAPTION(Datatype.STRING, 22, false, "Required. A label or caption associated with the member. Used primarily for display purposes. If a caption does not exist, MEMBER_NAME is returned."), /** * Definition of the property which holds the * number of children this member has. */ - CHILDREN_CARDINALITY(Datatype.TYPE_NUMERIC, 23, false, "Required. Number of children that the member has. This can be an estimate, so consumers should not rely on this to be the exact count. Providers should return the best estimate possible."), + CHILDREN_CARDINALITY(Datatype.UNSIGNED_INTEGER, 23, false, "Required. Number of children that the member has. This can be an estimate, so consumers should not rely on this to be the exact count. Providers should return the best estimate possible."), /** * Definition of the property which holds the * distance from the root of the hierarchy of this member's parent. */ - PARENT_LEVEL(Datatype.TYPE_NUMERIC, 24, false, "Required. The distance of the member's parent from the root level of the hierarchy. The root level is zero."), + PARENT_LEVEL(Datatype.UNSIGNED_INTEGER, 24, false, "Required. The distance of the member's parent from the root level of the hierarchy. The root level is zero."), /** * Definition of the property which holds the * Name of the current catalog. */ - PARENT_UNIQUE_NAME(Datatype.TYPE_STRING, 25, false, "Required. Unique name of the member's parent. NULL is returned for any members at the root level. For providers that generate unique names by qualification, each component of this name is delimited."), + PARENT_UNIQUE_NAME(Datatype.STRING, 25, false, "Required. Unique name of the member's parent. NULL is returned for any members at the root level. For providers that generate unique names by qualification, each component of this name is delimited."), /** * Definition of the property which holds the * number of parents that this member has. Generally 1, or 0 for root members. */ - PARENT_COUNT(Datatype.TYPE_NUMERIC, 26, false, "Required. Number of parents that this member has."), + PARENT_COUNT(Datatype.UNSIGNED_INTEGER, 26, false, "Required. Number of parents that this member has."), /** * Definition of the property which holds the * description of this member. */ - DESCRIPTION(Datatype.TYPE_STRING, 27, false, "Optional. A human-readable description of the member."), + DESCRIPTION(Datatype.STRING, 27, false, "Optional. A human-readable description of the member."), /** * Definition of the internal property which holds the @@ -194,7 +278,7 @@ enum StandardMemberProperty implements Property { * (especially a measure or calculated member) in a user interface such as * JPivot. */ - $visible(Datatype.TYPE_BOOLEAN, 28, true, null), + $visible(Datatype.BOOLEAN, 28, true, null), /** * Definition of the property which @@ -203,7 +287,7 @@ enum StandardMemberProperty implements Property { *

      Caution: Level depth of members in parent-child hierarchy isn't from their levels. * It's calculated from the underlying data dynamically. */ - DEPTH(Datatype.TYPE_NUMERIC, 43, true, "The level depth of a member"), + DEPTH(Datatype.UNSIGNED_INTEGER, 43, true, "The level depth of a member"), /** * Definition of the property which @@ -211,14 +295,14 @@ enum StandardMemberProperty implements Property { * *

      Caution: This property's value is calculated based on a specified MDX query, so its value is dynamic at runtime. */ - DISPLAY_INFO(Datatype.TYPE_NUMERIC, 44, false, "Display instruction of a member for XML/A"), + DISPLAY_INFO(Datatype.UNSIGNED_INTEGER, 44, false, "Display instruction of a member for XML/A"), /** * Definition of the property which * holds the value of a cell. Is usually numeric (since most measures are * numeric) but is occasionally another type. */ - VALUE(Datatype.TYPE_NUMERIC, 41, false, "The unformatted value of the cell."); + VALUE(Datatype.VARIANT, 41, false, "The unformatted value of the cell."); private final Datatype type; private final String description; @@ -256,8 +340,12 @@ public Datatype getDatatype() { return type; } - public Scope getScope() { - return Scope.MEMBER; + public Set getType() { + return TypeFlag.forMask(TypeFlag.MEMBER.xmlaOrdinal); + } + + public ContentType getContentType() { + return ContentType.REGULAR; } public boolean isInternal() { @@ -285,47 +373,47 @@ public boolean isInternal() { * */ enum StandardCellProperty implements Property { - BACK_COLOR(Datatype.TYPE_STRING, 30, false, "The background color for displaying the VALUE or FORMATTED_VALUE property. For more information, see FORE_COLOR and BACK_COLOR Contents."), + BACK_COLOR(Datatype.STRING, 30, false, "The background color for displaying the VALUE or FORMATTED_VALUE property. For more information, see FORE_COLOR and BACK_COLOR Contents."), - CELL_EVALUATION_LIST(Datatype.TYPE_STRING, 31, false, "The semicolon-delimited list of evaluated formulas applicable to the cell, in order from lowest to highest solve order. For more information about solve order, see Understanding Pass Order and Solve Order"), + CELL_EVALUATION_LIST(Datatype.STRING, 31, false, "The semicolon-delimited list of evaluated formulas applicable to the cell, in order from lowest to highest solve order. For more information about solve order, see Understanding Pass Order and Solve Order"), - CELL_ORDINAL(Datatype.TYPE_NUMERIC, 32, false, "The ordinal number of the cell in the dataset."), + CELL_ORDINAL(Datatype.UNSIGNED_INTEGER, 32, false, "The ordinal number of the cell in the dataset."), - FORE_COLOR(Datatype.TYPE_STRING, 33, false, "The foreground color for displaying the VALUE or FORMATTED_VALUE property. For more information, see FORE_COLOR and BACK_COLOR Contents."), + FORE_COLOR(Datatype.STRING, 33, false, "The foreground color for displaying the VALUE or FORMATTED_VALUE property. For more information, see FORE_COLOR and BACK_COLOR Contents."), - FONT_NAME(Datatype.TYPE_STRING, 34, false, "The font to be used to display the VALUE or FORMATTED_VALUE property."), + FONT_NAME(Datatype.STRING, 34, false, "The font to be used to display the VALUE or FORMATTED_VALUE property."), - FONT_SIZE(Datatype.TYPE_STRING, 35, false, "Font size to be used to display the VALUE or FORMATTED_VALUE property."), + FONT_SIZE(Datatype.STRING, 35, false, "Font size to be used to display the VALUE or FORMATTED_VALUE property."), - FONT_FLAGS(Datatype.TYPE_NUMERIC, 36, false, "The bitmask detailing effects on the font. The value is the result of a bitwise OR operation of one or more of the following constants: MDFF_BOLD = 1, MDFF_ITALIC = 2, MDFF_UNDERLINE = 4, MDFF_STRIKEOUT = 8. For example, the value 5 represents the combination of bold (MDFF_BOLD) and underline (MDFF_UNDERLINE) font effects."), + FONT_FLAGS(Datatype.UNSIGNED_INTEGER, 36, false, "The bitmask detailing effects on the font. The value is the result of a bitwise OR operation of one or more of the following constants: MDFF_BOLD = 1, MDFF_ITALIC = 2, MDFF_UNDERLINE = 4, MDFF_STRIKEOUT = 8. For example, the value 5 represents the combination of bold (MDFF_BOLD) and underline (MDFF_UNDERLINE) font effects."), /** * Definition of the property which * holds the formatted value of a cell. */ - FORMATTED_VALUE(Datatype.TYPE_STRING, 37, false, "The character string that represents a formatted display of the VALUE property."), + FORMATTED_VALUE(Datatype.STRING, 37, false, "The character string that represents a formatted display of the VALUE property."), /** * Definition of the property which * holds the format string used to format cell values. */ - FORMAT_STRING(Datatype.TYPE_STRING, 38, false, "The format string used to create the FORMATTED_VALUE property value. For more information, see FORMAT_STRING Contents."), + FORMAT_STRING(Datatype.STRING, 38, false, "The format string used to create the FORMATTED_VALUE property value. For more information, see FORMAT_STRING Contents."), - NON_EMPTY_BEHAVIOR(Datatype.TYPE_STRING, 39, false, "The measure used to determine the behavior of calculated members when resolving empty cells."), + NON_EMPTY_BEHAVIOR(Datatype.STRING, 39, false, "The measure used to determine the behavior of calculated members when resolving empty cells."), /** * Definition of the property which * determines the solve order of a calculated member with respect to other * calculated members. */ - SOLVE_ORDER(Datatype.TYPE_NUMERIC, 40, false, "The solve order of the cell."), + SOLVE_ORDER(Datatype.INTEGER, 40, false, "The solve order of the cell."), /** * Definition of the property which * holds the value of a cell. Is usually numeric (since most measures are * numeric) but is occasionally another type. */ - VALUE(Datatype.TYPE_NUMERIC, 41, false, "The unformatted value of the cell."), + VALUE(Datatype.VARIANT, 41, false, "The unformatted value of the cell."), /** * Definition of the property which @@ -335,7 +423,7 @@ enum StandardCellProperty implements Property { * is not specified, the datatype is "Numeric" by default, except measures * whose aggregator is "Count", whose datatype is "Integer". */ - DATATYPE(Datatype.TYPE_STRING, 42, false, "The datatype of the cell."); + DATATYPE(Datatype.STRING, 42, false, "The datatype of the cell."); /** * The datatype of the property. @@ -360,8 +448,8 @@ public Datatype getDatatype() { return type; } - public Scope getScope() { - return Scope.CELL; + public Set getType() { + return TypeFlag.forMask(TypeFlag.CELL.xmlaOrdinal); } public String getName() { @@ -383,6 +471,10 @@ public String getDescription(Locale locale) { public boolean isInternal() { return internal; } + + public ContentType getContentType() { + return ContentType.REGULAR; + } } /** @@ -462,6 +554,15 @@ enum ContentType { private final int xmlaOrdinal; + private static final Map xmlaMap = + new HashMap(); + + static { + for (ContentType contentType : values()) { + xmlaMap.put(contentType.xmlaOrdinal, contentType); + } + } + /** * Returns the ordinal code as specified by XMLA. * @@ -477,6 +578,19 @@ public int xmlaOrdinal() { private ContentType(int xmlaOrdinal) { this.xmlaOrdinal = xmlaOrdinal; } + + /** + * Looks up a ContentType by its XMLA ordinal. + * + * @param xmlaOrdinal Ordinal of a ContentType according to the XMLA + * specification. + * + * @return ContentType with the given ordinal, or null if there is no + * such ContentType + */ + public static ContentType forXmlaOrdinal(int xmlaOrdinal) { + return xmlaMap.get(xmlaOrdinal); + } } } diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java index 130a72c..311de01 100644 --- a/testsrc/org/olap4j/ConnectionTest.java +++ b/testsrc/org/olap4j/ConnectionTest.java @@ -553,14 +553,12 @@ private void checkAxisMetaData(CellSetAxisMetaData cellSetAxisMetaData) { assertEquals(2, hierarchies.size()); assertEquals("Store", hierarchies.get(0).getName()); assertEquals("Gender", hierarchies.get(1).getName()); - final NamedList properties = + final List properties = cellSetAxisMetaData.getProperties(); assertEquals(3, properties.size()); assertEquals("MEMBER_ORDINAL", properties.get(0).getName()); assertEquals("MEMBER_UNIQUE_NAME", properties.get(1).getName()); assertEquals("DISPLAY_INFO", properties.get(2).getName()); - assertNotNull(properties.get("DISPLAY_INFO")); - assertNull(properties.get("FOO_BAR")); } public void testCellSet() throws SQLException { @@ -1117,8 +1115,8 @@ public void testMetadata() throws Exception { assertEquals("MEMBER_CAPTION", property.getName()); assertEquals("MEMBER_CAPTION", property.getUniqueName()); - assertEquals(Property.Scope.MEMBER, property.getScope()); - assertEquals(Property.Datatype.TYPE_STRING, property.getDatatype()); + assertEquals(EnumSet.of(Property.TypeFlag.MEMBER), property.getType()); + assertEquals(Datatype.STRING, property.getDatatype()); } /** diff --git a/testsrc/org/olap4j/OlapTest.java b/testsrc/org/olap4j/OlapTest.java index 43d16bf..15174f6 100644 --- a/testsrc/org/olap4j/OlapTest.java +++ b/testsrc/org/olap4j/OlapTest.java @@ -208,8 +208,7 @@ public static void listLevels(Hierarchy hierarchy) { } } - public static void listMembers(Level level) { - + public static void listMembers(Level level) throws OlapException { List members = level.getMembers(); for (Member member : members) { System.out.println("member name=" + member.getName());