diff --git a/src/org/olap4j/OlapDatabaseMetaData.java b/src/org/olap4j/OlapDatabaseMetaData.java index 38b8f07..e9a69c9 100644 --- a/src/org/olap4j/OlapDatabaseMetaData.java +++ b/src/org/olap4j/OlapDatabaseMetaData.java @@ -44,6 +44,8 @@ public interface OlapDatabaseMetaData extends DatabaseMetaData, OlapWrapper { *

Specification as for XML/A MDSCHEMA_ACTIONS schema rowset. * *

Each action description has the following columns: + *

  • CATALOG_NAME String (may be null) => The name of + * the database.
  • *
  • SCHEMA_NAME String (may be null) => The name of * the schema to which this action belongs.
  • *
  • CUBE_NAME String => The name of the cube to which this action diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java index e96de07..bfbb106 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java @@ -182,34 +182,23 @@ void populate() throws OlapException { */ final Element axesNode = findChild(root, MDDATASET_NS, "Axes"); + + // 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 axisNode : findChildren(axesNode, MDDATASET_NS, "Axis")) { - final String axisName = axisNode.getAttribute("name"); - final Axis axis = lookupAxis(axisName); - final XmlaOlap4jCellSetAxis cellSetAxis = - new XmlaOlap4jCellSetAxis(this, axis); - if (axis.isFilter()) { - filterAxis = cellSetAxis; - } else { - axisList.add(cellSetAxis); - } final Element tuplesNode = findChild(axisNode, MDDATASET_NS, "Tuples"); - 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")) { @@ -220,9 +209,27 @@ void populate() throws OlapException { uniqueNames.add(uname); } } - metadataReader.lookupMembersByUniqueName(uniqueNames, memberMap); + } - // Second pass, populate the axis. + // Fetch all members on all axes. Hopefully it can all be done in one + // round trip, or they are in cache already. + metadataReader.lookupMembersByUniqueName(uniqueNames, memberMap); + + // Second pass, populate the axis. + final Map propertyValues = + new HashMap(); + for (Element axisNode : findChildren(axesNode, MDDATASET_NS, "Axis")) { + final String axisName = axisNode.getAttribute("name"); + final Axis axis = lookupAxis(axisName); + final XmlaOlap4jCellSetAxis cellSetAxis = + new XmlaOlap4jCellSetAxis(this, axis); + if (axis.isFilter()) { + filterAxis = cellSetAxis; + } else { + axisList.add(cellSetAxis); + } + final Element tuplesNode = + findChild(axisNode, MDDATASET_NS, "Tuples"); for (Element tupleNode : findChildren(tuplesNode, MDDATASET_NS, "Tuple")) { @@ -264,12 +271,11 @@ void populate() throws OlapException { members.add(member); } cellSetAxis.positions.add( - new XmlaOlap4jPosition(members, ordinal++)); + new XmlaOlap4jPosition( + members, cellSetAxis.positions.size())); } } - final Map propertyValues = - new HashMap(); final Element cellDataNode = findChild(root, MDDATASET_NS, "CellData"); for (Element cell : findChildren(cellDataNode, MDDATASET_NS, "Cell")) { propertyValues.clear(); diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java index df22b28..1fdab79 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java @@ -765,6 +765,7 @@ public String generateRequest( "\n" + " \n" + " \n"); + String catalogName = null; if (restrictions.length > 0) { if (restrictions.length % 2 != 0) { throw new IllegalArgumentException(); @@ -778,6 +779,11 @@ public String generateRequest( buf.append(xmlEncode(value)); buf.append(""); + // To remind ourselves to generate a restriction + // if the request supports it. + if (restriction.equals("CATALOG_NAME")) { + catalogName = value; + } } else { //noinspection unchecked List valueList = (List) o; @@ -802,10 +808,30 @@ public String generateRequest( buf.append("\n"); } - // Add the catalog node only if this request requires it. - if (metadataRequest.requiresCatalogName()) { + // If the request requires catalog name, and one wasn't specified in the + // restrictions, use the connection's current catalog. + if (catalogName == null + && metadataRequest.requiresCatalogName()) + { + catalogName = context.olap4jConnection.getCatalog(); + } + + // Add the catalog node only if this request has specified it as a + // restriction. + // + // For low-level objects like cube, the restriction is optional; you can + // specify null to not restrict, "" to match cubes whose catalog name is + // empty, or a string (not interpreted as a wild card). (See + // OlapDatabaseMetaData.getCubes API doc for more details.) We assume + // that the request provides the restriction only if it is valid. + // + // For high level objects like data source and catalog, the catalog + // restriction does not make sense. + if (catalogName != null + && metadataRequest.allowsCatalogName()) + { buf.append(" "); - buf.append(xmlEncode(context.olap4jConnection.getCatalog())); + buf.append(xmlEncode(catalogName)); buf.append("\n"); } @@ -1819,6 +1845,7 @@ enum MetadataRequest { new MetadataColumn("SCHEMA_NAME"), new MetadataColumn("SCHEMA_OWNER")), MDSCHEMA_ACTIONS( + new MetadataColumn("CATALOG_NAME"), new MetadataColumn("SCHEMA_NAME"), new MetadataColumn("CUBE_NAME"), new MetadataColumn("ACTION_NAME"), @@ -2009,7 +2036,26 @@ public boolean requiresDatasourceName() { * @return whether this request requires a CatalogName element */ public boolean requiresCatalogName() { - return (this != DBSCHEMA_CATALOGS && this != DISCOVER_DATASOURCES); + // If we don't specifiy CatalogName in the properties of an + // MDSCHEMA_FUNCTIONS request, Mondrian's XMLA provider will give + // us the whole set of functions multiplied by the number of + // catalogs. JDBC (and Mondrian) assumes that functions belong to a + // catalog whereas XMLA (and SSAS) assume that functions belong to + // the database. Always specifying a catalog is the easiest way to + // reconcile them. + return this == MDSCHEMA_FUNCTIONS; + } + + /** + * Returns whether this request allows a + * {@code <CatalogName>} element in the properties section of the + * request. Even for requests that allow it, it is usually optional. + * + * @return whether this request allows a CatalogName element + */ + public boolean allowsCatalogName() { + return this != DBSCHEMA_CATALOGS + && this != DISCOVER_DATASOURCES; } /** diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java b/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java index 365c483..a26c883 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jCube.java @@ -38,7 +38,10 @@ class XmlaOlap4jCube implements Cube, Named new HashMap(); final Map levelsByUname = new HashMap(); - final NamedList measures; + final List measures = + new ArrayList(); + private final HashMap measuresMap = + new HashMap(); private final NamedList namedSets = new NamedListImpl(); private final MetadataReader metadataReader; @@ -63,7 +66,8 @@ class XmlaOlap4jCube implements Cube, Named this.description = description; this.metadataReader = new CachingMetadataReader( - new RawMetadataReader()); + new RawMetadataReader(), + measuresMap); final XmlaOlap4jConnection olap4jConnection = olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData.olap4jConnection; @@ -78,32 +82,26 @@ class XmlaOlap4jCube implements Cube, Named this.dimensions = new DeferredNamedListImpl( XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_DIMENSIONS, - new XmlaOlap4jConnection.Context( - olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData - .olap4jConnection, - olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData, - olap4jSchema.olap4jCatalog, - olap4jSchema, - this, null, null, null), - new XmlaOlap4jConnection.DimensionHandler(this), - restrictions); - - this.measures = new DeferredNamedListImpl( + context, + new XmlaOlap4jConnection.DimensionHandler(this), + restrictions); + + // populate measures up front; a measure is needed in every query + olap4jConnection.populateList( + measures, + context, XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_MEASURES, - new XmlaOlap4jConnection.Context( - olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData - .olap4jConnection, - olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData, - olap4jSchema.olap4jCatalog, - olap4jSchema, - this, null, null, null), - new XmlaOlap4jConnection.MeasureHandler( - this.dimensions.get("Measures")), - restrictions); + new XmlaOlap4jConnection.MeasureHandler( + this.dimensions.get("Measures")), + restrictions); + for (XmlaOlap4jMeasure measure : measures) { + measuresMap.put(measure.getUniqueName(), measure); + } // populate named sets olap4jConnection.populateList( - namedSets, context, + namedSets, + context, XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_SETS, new XmlaOlap4jConnection.NamedSetHandler(), restrictions); @@ -136,7 +134,7 @@ public NamedList getDimensions() { public NamedList getHierarchies() { // This is a costly operation. It forces the init // of all dimensions and all hierarchies. - // We differ it to this point. + // We defer it to this point. if (this.hierarchies == null) { this.hierarchies = new NamedListImpl(); for (XmlaOlap4jDimension dim : this.dimensions) { @@ -278,6 +276,8 @@ public List getLevelMembers( private static class CachingMetadataReader extends DelegatingMetadataReader { + private final Map measuresMap; + private final Map> memberMap = new HashMap>(); @@ -292,14 +292,28 @@ private static class CachingMetadataReader * Creates a CachingMetadataReader. * * @param metadataReader Underlying metadata reader + * @param measuresMap Map of measures by unique name, inherited from the + * cube and used read-only by this reader */ - CachingMetadataReader(MetadataReader metadataReader) { + CachingMetadataReader( + MetadataReader metadataReader, + Map measuresMap) + { super(metadataReader); + this.measuresMap = measuresMap; } public XmlaOlap4jMember lookupMemberByUniqueName( String memberUniqueName) throws OlapException { + // First, look in measures map. + XmlaOlap4jMeasure measure = + measuresMap.get(memberUniqueName); + if (measure != null) { + return measure; + } + + // Next, look in cache. final SoftReference memberRef = memberMap.get(memberUniqueName); if (memberRef != null) { @@ -308,11 +322,11 @@ public XmlaOlap4jMember lookupMemberByUniqueName( return member; } } + final XmlaOlap4jMember member = super.lookupMemberByUniqueName(memberUniqueName); if (member != null - && !member.getDimension() - .type.equals(Dimension.Type.MEASURE)) + && member.getDimension().type != Dimension.Type.MEASURE) { memberMap.put( memberUniqueName, @@ -328,6 +342,15 @@ public void lookupMembersByUniqueName( final ArrayList remainingMemberUniqueNames = new ArrayList(); for (String memberUniqueName : memberUniqueNames) { + // First, look in measures map. + XmlaOlap4jMeasure measure = + measuresMap.get(memberUniqueName); + if (measure != null) { + memberMap.put(memberUniqueName, measure); + continue; + } + + // Next, look in cache. final SoftReference memberRef = this.memberMap.get(memberUniqueName); final XmlaOlap4jMember member; @@ -335,10 +358,12 @@ public void lookupMembersByUniqueName( && (member = memberRef.get()) != null) { memberMap.put(memberUniqueName, member); - } else { - remainingMemberUniqueNames.add(memberUniqueName); + continue; } + + remainingMemberUniqueNames.add(memberUniqueName); } + // If any of the member names were not in the cache, look them up // by delegating. if (!remainingMemberUniqueNames.isEmpty()) { @@ -350,8 +375,8 @@ public void lookupMembersByUniqueName( XmlaOlap4jMember member = memberMap.get(memberName); if (member != null) { if (!(member instanceof Measure) - && !(member.getDimension().type - .equals(Dimension.Type.MEASURE))) + && member.getDimension().type + != Dimension.Type.MEASURE) { this.memberMap.put( memberName, @@ -376,8 +401,8 @@ public List getLevelMembers( } final List memberList = super.getLevelMembers(level); - if (!level.olap4jHierarchy.olap4jDimension.type - .equals(Dimension.Type.MEASURE)) + if (level.olap4jHierarchy.olap4jDimension.type + != Dimension.Type.MEASURE) { levelMemberListMap.put( level, @@ -421,20 +446,22 @@ public void lookupMembersByUniqueName( .olap4jConnection.getDataSourceInfo() .indexOf("Provider=Mondrian") != -1) //$NON-NLS-1$ { - memberMap.putAll(this.mondrianMembersLookup(memberUniqueNames)); + mondrianMembersLookup(memberUniqueNames, memberMap); } else { - memberMap.putAll(this.genericMembersLookup(memberUniqueNames)); + genericMembersLookup(memberUniqueNames, memberMap); } } /** - * This is an optimized method for Mondrian servers members lookup. + * Looks up members; optimized for Mondrian servers. + * * @param memberUniqueNames A list of the members to lookup - * @return A map of members with their unique name as a key + * @param memberMap Output map of members keyed by unique name * @throws OlapException Gets thrown for communication errors */ - private Map mondrianMembersLookup( - List memberUniqueNames) throws OlapException + private void mondrianMembersLookup( + List memberUniqueNames, + Map memberMap) throws OlapException { final XmlaOlap4jConnection.Context context = new XmlaOlap4jConnection.Context( @@ -453,27 +480,24 @@ private Map mondrianMembersLookup( "CUBE_NAME", getName(), "MEMBER_UNIQUE_NAME", memberUniqueNames }); - final Map memberMap = - new HashMap(memberUniqueNames.size()); for (XmlaOlap4jMember member : memberList) { if (member != null) { memberMap.put(member.getUniqueName(), member); } } - return memberMap; } /** - * This is an generic method for members lookup. + * Looks up members. + * * @param memberUniqueNames A list of the members to lookup - * @return A map of members with their unique name as a key + * @param memberMap Output map of members keyed by unique name * @throws OlapException Gets thrown for communication errors */ - private Map genericMembersLookup( - List memberUniqueNames) throws OlapException + private void genericMembersLookup( + List memberUniqueNames, + Map memberMap) throws OlapException { - final Map memberMap = - new HashMap(memberUniqueNames.size()); // Iterates through member names for (String currentMemberName : memberUniqueNames) { // Only lookup if it is not in the map yet @@ -486,7 +510,6 @@ private Map genericMembersLookup( } } } - return memberMap; } public void lookupMemberRelatives( diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jDatabaseMetaData.java b/src/org/olap4j/driver/xmla/XmlaOlap4jDatabaseMetaData.java index 4180486..48699e5 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jDatabaseMetaData.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jDatabaseMetaData.java @@ -967,6 +967,7 @@ public ResultSet getActions( { return getMetadata( XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_ACTIONS, + "CATALOG_NAME", catalog, "SCHEMA_NAME", wildcard(schemaPattern), "CUBE_NAME", wildcard(cubeNamePattern), "ACTION_NAME", wildcard(actionNamePattern)); @@ -1056,6 +1057,7 @@ public ResultSet getDimensions( { return getMetadata( XmlaOlap4jConnection.MetadataRequest.MDSCHEMA_DIMENSIONS, + "CATALOG_NAME", catalog, "SCHEMA_NAME", wildcard(schemaPattern), "CUBE_NAME", wildcard(cubeNamePattern), "DIMENSION_NAME", wildcard(dimensionNamePattern)); diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jLevel.java b/src/org/olap4j/driver/xmla/XmlaOlap4jLevel.java index 1eab351..ed3f0ee 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jLevel.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jLevel.java @@ -90,7 +90,7 @@ class XmlaOlap4jLevel try { if (olap4jHierarchy.olap4jDimension.getDimensionType() - .equals(Dimension.Type.MEASURE)) + == Dimension.Type.MEASURE) { String[] restrictions = { "CATALOG_NAME", diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jMeasure.java b/src/org/olap4j/driver/xmla/XmlaOlap4jMeasure.java index 64338bf..0eae6b5 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jMeasure.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jMeasure.java @@ -29,6 +29,20 @@ class XmlaOlap4jMeasure private final Datatype datatype; private final boolean visible; + /** + * Creates an XmlaOlap4jMeasure. + * + * @param olap4jLevel Level + * @param uniqueName Unique name + * @param name Name + * @param caption Caption + * @param description Description + * @param parentMemberUniqueName Unique name of parent, or null if no parent + * @param aggregator Aggregator + * @param datatype Data type + * @param visible Whether visible + * @param ordinal Ordinal in its hierarchy + */ XmlaOlap4jMeasure( XmlaOlap4jLevel olap4jLevel, String uniqueName, diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java b/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java index dc18356..65194a5 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jMember.java @@ -45,8 +45,22 @@ class XmlaOlap4jMember private XmlaOlap4jMember parentMember; private final int childMemberCount; private final int ordinal; - private final ArrayMap propertyValueMap; + private final Map propertyValueMap; + /** + * Creates an XmlaOlap4jMember. + * + * @param olap4jLevel Level + * @param uniqueName Unique name + * @param name Name + * @param caption Caption + * @param description Description + * @param parentMemberUniqueName Unique name of parent, or null if no parent + * @param type Type + * @param childMemberCount Number of children + * @param ordinal Ordinal in its hierarchy + * @param propertyValueMap Property values + */ XmlaOlap4jMember( XmlaOlap4jLevel olap4jLevel, String uniqueName, @@ -67,8 +81,12 @@ class XmlaOlap4jMember this.parentMemberUniqueName = parentMemberUniqueName; this.type = type; this.childMemberCount = childMemberCount; - this.propertyValueMap = - new ArrayMap(propertyValueMap); + if (propertyValueMap.isEmpty()) { + this.propertyValueMap = Collections.emptyMap(); + } else { + this.propertyValueMap = + new ArrayMap(propertyValueMap); + } } public int hashCode() { @@ -317,12 +335,6 @@ static int toInteger(Object o) { public Member getDataMember() { throw new UnsupportedOperationException(); } - - public boolean equeals(Object obj) { - return (obj instanceof XmlaOlap4jMember) - && this.uniqueName.equals( - ((XmlaOlap4jMember) obj).getUniqueName()); - } } // End XmlaOlap4jMember.java diff --git a/src/org/olap4j/driver/xmla/XmlaOlap4jPositionMember.java b/src/org/olap4j/driver/xmla/XmlaOlap4jPositionMember.java index 2e7875a..e192462 100644 --- a/src/org/olap4j/driver/xmla/XmlaOlap4jPositionMember.java +++ b/src/org/olap4j/driver/xmla/XmlaOlap4jPositionMember.java @@ -35,7 +35,7 @@ class XmlaOlap4jPositionMember implements XmlaOlap4jMemberBase { private final XmlaOlap4jMemberBase member; - private final Map propertyValues; + private final Map propertyValues; /** * Creates a XmlaOlap4jPositionMember. @@ -45,12 +45,12 @@ class XmlaOlap4jPositionMember */ XmlaOlap4jPositionMember( XmlaOlap4jMemberBase member, - Map propertyValues) + Map propertyValues) { assert member != null; assert propertyValues != null; this.member = member; - this.propertyValues = new ArrayMap(propertyValues); + this.propertyValues = new ArrayMap(propertyValues); } public boolean equals(Object obj) { @@ -156,7 +156,7 @@ public String getPropertyFormattedValue(Property property) { // come back as part of axis tuple. Unformatted property is best we // can do. if (propertyValues.containsKey(property)) { - return propertyValues.get(property); + return String.valueOf(propertyValues.get(property)); } return member.getPropertyFormattedValue(property); } diff --git a/testsrc/org/olap4j/ConnectionTest.java b/testsrc/org/olap4j/ConnectionTest.java index ba78698..a138adb 100644 --- a/testsrc/org/olap4j/ConnectionTest.java +++ b/testsrc/org/olap4j/ConnectionTest.java @@ -533,6 +533,7 @@ public void testMeasureVersusMemberCasting() throws Exception { columnsMember.getUniqueName(), !(columnsMember instanceof Measure)); } + public void testInvalidStatement() throws SQLException { connection = tester.createConnection(); Statement statement = connection.createStatement(); @@ -1557,7 +1558,10 @@ public void testMetadata() throws Exception { // Schema boolean found = false; for (Catalog catalog : olapConnection.getCatalogs()) { + assertSame(olapConnection.getMetaData(), catalog.getMetaData()); + assertNotNull(catalog.getName()); for (Schema schema : catalog.getSchemas()) { + assertSame(schema.getCatalog(), catalog); if (schema.equals(olapConnection.getSchema())) { found = true; break; @@ -1566,6 +1570,16 @@ public void testMetadata() throws Exception { } assertTrue(found); + // We engineered the XMLA test environment to have two catalogs. + switch (tester.getFlavor()) { + case XMLA: + assertEquals(2, olapConnection.getCatalogs().size()); + break; + case MONDRIAN: + assertEquals(1, olapConnection.getCatalogs().size()); + break; + } + Cube cube = olapConnection.getSchema().getCubes().get("Sales"); int z = 0; @@ -2149,9 +2163,11 @@ public void testValidateError() throws Exception { fail("expected parse error, got " + select); } catch (OlapException e) { assertEquals("Validation error", e.getMessage()); + final String stackTrace = TestContext.getStackTrace(e); assertTrue( - TestContext.getStackTrace(e).contains( - "Dimension '[Gender]' appears in more than one " + stackTrace, + stackTrace.contains( + "Hierarchy '[Gender]' appears in more than one " + "independent axis.")); } } diff --git a/testsrc/org/olap4j/MetadataTest.java b/testsrc/org/olap4j/MetadataTest.java index dcd4b3f..c3f171a 100644 --- a/testsrc/org/olap4j/MetadataTest.java +++ b/testsrc/org/olap4j/MetadataTest.java @@ -94,7 +94,7 @@ public class MetadataTest extends TestCase { private static final List SCHEMAS_COLUMN_NAMES = Arrays.asList( "TABLE_SCHEM", "TABLE_CAT"); private static final List ACTIONS_COLUMN_NAMES = Arrays.asList( - "SCHEMA_NAME", "CUBE_NAME", "ACTION_NAME", "COORDINATE", + "CATALOG_NAME", "SCHEMA_NAME", "CUBE_NAME", "ACTION_NAME", "COORDINATE", "COORDINATE_TYPE"); public MetadataTest() throws SQLException { @@ -274,18 +274,34 @@ public void testDatabaseMetaDataGetCatalogs() throws SQLException { String s = checkResultSet( olapDatabaseMetaData.getCatalogs(), CATALOGS_COLUMN_NAMES); - TestContext.assertEqualsVerbose( - "TABLE_CAT=" + catalogName + "\n", - s); + final String expected; + if (tester.getFlavor() == TestContext.Tester.Flavor.XMLA) { + // XMLA test uses dummy duplicate catalog to make sure that we + // get all catalogs + expected = + "TABLE_CAT=" + catalogName + "\n" + + "TABLE_CAT=" + catalogName + "2\n"; + } else { + expected = "TABLE_CAT=" + catalogName + "\n"; + } + TestContext.assertEqualsVerbose(expected, s); } public void testDatabaseMetaDataGetSchemas() throws SQLException { String s = checkResultSet( olapDatabaseMetaData.getSchemas(), SCHEMAS_COLUMN_NAMES); - TestContext.assertEqualsVerbose( - "TABLE_SCHEM=FoodMart, TABLE_CAT=" + catalogName + "\n", - s); + final String expected; + if (tester.getFlavor() == TestContext.Tester.Flavor.XMLA) { + // XMLA test uses dummy duplicate catalog to make sure that we + // get all catalogs + expected = + "TABLE_SCHEM=FoodMart, TABLE_CAT=" + catalogName + "\n" + + "TABLE_SCHEM=FoodMart, TABLE_CAT=" + catalogName + "2\n"; + } else { + expected = "TABLE_SCHEM=FoodMart, TABLE_CAT=" + catalogName + "\n"; + } + TestContext.assertEqualsVerbose(expected, s); } public void testDatabaseMetaDataGetLiterals() throws SQLException { @@ -350,6 +366,25 @@ public void testDatabaseMetaDataGetCubes() throws SQLException { "CATALOG_NAME=" + catalogName + ", SCHEMA_NAME=FoodMart, CUBE_NAME=Sales, ", s); + final int lineCount = linecount(s); + + // again, but with null catalog name. should yield twice as many + // cubes on xmla, where we have two identical catalogs + s = checkResultSet( + olapDatabaseMetaData.getCubes( + catalogName, + null, + null), + CUBE_COLUMN_NAMES); + assertContains( + "CATALOG_NAME=" + catalogName + + ", SCHEMA_NAME=FoodMart, CUBE_NAME=Sales, ", + s); + final int lineCount2 = linecount(s); + + if (tester.getFlavor() == TestContext.Tester.Flavor.XMLA) { + assertEquals(lineCount2, lineCount * 2); + } s = checkResultSet( olapDatabaseMetaData.getCubes( diff --git a/testsrc/org/olap4j/XmlaTester.java b/testsrc/org/olap4j/XmlaTester.java index 45b210a..b9b8beb 100644 --- a/testsrc/org/olap4j/XmlaTester.java +++ b/testsrc/org/olap4j/XmlaTester.java @@ -47,9 +47,19 @@ public XmlaTester() final String catalogUrl = properties.getProperty( TestContext.Property.XMLA_CATALOG_URL.path); + if (catalogUrl == null) { + throw new RuntimeException( + "Property " + TestContext.Property.XMLA_CATALOG_URL.path + + " must be specified"); + } + + // Include the same catalog URL twice with different catalog names. This + // allows us to detect whether operations are restricting to the current + // catalog. (Some should, most should not.) Map catalogNameUrls = new HashMap(); catalogNameUrls.put("FoodMart", catalogUrl); + catalogNameUrls.put("FoodMart2", catalogUrl); String urlString = properties.getProperty(TestContext.Property.CONNECT_URL.path);