Skip to content

Commit

Permalink
Check in Paul Stoellberger's cube-discovery patch.
Browse files Browse the repository at this point in the history
Fixed bug in XMLA driver where a catalog's schema list would contain one schema
per cube (and, yes, each schema would contain all cubes). This would cause poor
performance and excessive memory usage on any schema with a large number of
cubes.

Add test to ensure that OlapDatabaseMetaData.getCubes uses catalog parameter but
ignores connection's catalog.

Rename 'xxx' method and remove a few spurious casts.

xmlEncode method now writes to a StringBuilder rather than returning a string;
more efficient.


git-svn-id: https://olap4j.svn.sourceforge.net/svnroot/olap4j/trunk@293 c6a108a4-781c-0410-a6c6-c2d559e19af0
  • Loading branch information
julianhyde committed Nov 28, 2009
1 parent 11743a0 commit 58727bb
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 57 deletions.
5 changes: 3 additions & 2 deletions src/org/olap4j/driver/xmla/XmlaOlap4jCatalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.olap4j.OlapDatabaseMetaData;
import org.olap4j.OlapException;
import org.olap4j.impl.Named;
import org.olap4j.impl.Olap4jUtil;
import org.olap4j.metadata.*;

/**
Expand All @@ -25,7 +26,7 @@
class XmlaOlap4jCatalog implements Catalog, Named {
final XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData;
private final String name;
private final DeferredNamedListImpl<XmlaOlap4jSchema> schemas;
final DeferredNamedListImpl<XmlaOlap4jSchema> schemas;

XmlaOlap4jCatalog(
XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData,
Expand Down Expand Up @@ -68,7 +69,7 @@ public boolean equals(Object obj) {
}

public NamedList<Schema> getSchemas() throws OlapException {
return (NamedList) schemas;
return Olap4jUtil.cast(schemas);
}

public String getName() {
Expand Down
55 changes: 52 additions & 3 deletions src/org/olap4j/driver/xmla/XmlaOlap4jCellSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -373,14 +373,34 @@ private XmlaOlap4jCellSetMetaData createMetaData(Element root)
final Element cubeNameNode =
findChild(cubeNode, MDDATASET_NS, "CubeName");
final String cubeName = gatherText(cubeNameNode);
final XmlaOlap4jCube cube =
(XmlaOlap4jCube)
this.olap4jStatement.olap4jConnection.getSchema().getCubes().get(

// REVIEW: If there are multiple cubes with the same name, we should
// qualify by catalog and schema. Currently we just take the first.
XmlaOlap4jCube cube =
lookupCube(
olap4jStatement.olap4jConnection.olap4jDatabaseMetaData,
cubeName);
if (cube == null) {
throw getHelper().createException(
"Internal error: cube '" + cubeName + "' not found");
}
// REVIEW: We should not modify the connection. It is not safe, because
// connection might be shared between multiple statements with different
// cubes. Caller should call
//
// connection.setCatalog(
// cellSet.getMetaData().getCube().getSchema().getCatalog().getName())
//
// before doing metadata queries.
try {
this.olap4jStatement.olap4jConnection.setCatalog(
cube.getSchema().getCatalog().getName());
} catch (SQLException e) {
throw getHelper().createException(
"Internal error: setting catalog '"
+ cube.getSchema().getCatalog().getName()
+ "' caused error");
}
final Element axesInfo =
findChild(olapInfo, MDDATASET_NS, "AxesInfo");
final List<Element> axisInfos =
Expand Down Expand Up @@ -468,6 +488,35 @@ private XmlaOlap4jCellSetMetaData createMetaData(Element root)
cellProperties);
}

/**
* Looks up a cube among all of the schemas in all of the catalogs
* in this connection.
*
* <p>If there are several with the same name, returns the first.
*
* @param databaseMetaData Database metadata
* @param cubeName Cube name
* @return Cube, or null if not found
* @throws OlapException
*/
private XmlaOlap4jCube lookupCube(
XmlaOlap4jDatabaseMetaData databaseMetaData,
String cubeName) throws OlapException
{
for (XmlaOlap4jCatalog catalog
: databaseMetaData.getCatalogObjects())
{
for (Schema schema : catalog.getSchemas()) {
for (Cube cube : schema.getCubes()) {
if (cubeName.equals(cube.getName())) {
return (XmlaOlap4jCube) cube;
}
}
}
}
return null;
}

/**
* Looks up a hierarchy in a cube with a given name or, failing that, a
* given unique name. Throws if not found.
Expand Down
135 changes: 89 additions & 46 deletions src/org/olap4j/driver/xmla/XmlaOlap4jConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ abstract class XmlaOlap4jConnection implements OlapConnection {
*/
private XmlaOlap4jSchema olap4jSchema;

private final XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData;
final XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData;

private static final String CONNECT_STRING_PREFIX = "jdbc:xmla:";

Expand All @@ -71,7 +71,7 @@ abstract class XmlaOlap4jConnection implements OlapConnection {

private Locale locale;
private String catalogName;
private static final boolean DEBUG = false;
private final String driverCatalogName;
private String roleName;

/**
Expand All @@ -96,6 +96,8 @@ abstract class XmlaOlap4jConnection implements OlapConnection {
private boolean autoCommit;
private boolean readOnly;

private static final boolean DEBUG = false;

/**
* Name of the "DATA_SOURCE_NAME" column returned from
* {@link org.olap4j.OlapDatabaseMetaData#getDatasources()}.
Expand Down Expand Up @@ -150,7 +152,9 @@ abstract class XmlaOlap4jConnection implements OlapConnection {
this.providerName = map.get(XmlaOlap4jDriver.Property.Provider.name());
this.datasourceName =
map.get(XmlaOlap4jDriver.Property.DataSource.name());
this.catalogName = map.get(XmlaOlap4jDriver.Property.Catalog.name());
this.driverCatalogName =
map.get(XmlaOlap4jDriver.Property.Catalog.name());
this.catalogName = driverCatalogName;

// Set URL of HTTP server.
String serverUrl = map.get(XmlaOlap4jDriver.Property.Server.name());
Expand Down Expand Up @@ -369,7 +373,7 @@ public OlapDatabaseMetaData getMetaData() {
}

public NamedList<Catalog> getCatalogs() {
return olap4jDatabaseMetaData.getCatalogObjects();
return Olap4jUtil.cast(olap4jDatabaseMetaData.getCatalogObjects());
}

public void setReadOnly(boolean readOnly) throws SQLException {
Expand All @@ -385,13 +389,19 @@ public void setCatalog(String catalog) throws SQLException {
}

public String getCatalog() throws OlapException {
// REVIEW: All this logic to deduce and check catalog name should be
// done on initialization (construction, or setCatalog), not here. This
// method should be very quick.

if (this.catalogName == null) {
// This means that no particular catalog name
// was specified by the user.
List<Catalog> catalogs = this.getCatalogs();
if (catalogs.size() == 0) {
throw new OlapException(
"There is no catalog available to query against.");
} else if (driverCatalogName != null) {
this.catalogName = driverCatalogName;
} else {
this.catalogName = catalogs.get(0).getName();
}
Expand Down Expand Up @@ -563,12 +573,11 @@ public synchronized org.olap4j.metadata.Schema getSchema()
{
// initializes the olap4jSchema if necessary
if (this.olap4jSchema == null) {
final XmlaOlap4jCatalog catalog = (XmlaOlap4jCatalog)
final XmlaOlap4jCatalog catalog =
this.olap4jDatabaseMetaData.getCatalogObjects().get(
this.getCatalog());
this.olap4jSchema = (XmlaOlap4jSchema)
catalog.getSchemas().get(0);
}
getCatalog());
this.olap4jSchema = catalog.schemas.get(0);
}
return olap4jSchema;
}

Expand Down Expand Up @@ -632,7 +641,7 @@ <T extends Named> void populateList(
{
String request =
generateRequest(context, metadataRequest, restrictions);
Element root = xxx(request);
Element root = executeMetadataRequest(request);
for (Element o : childElements(root)) {
if (o.getLocalName().equals("row")) {
handler.handle(o, context, list);
Expand All @@ -641,7 +650,15 @@ <T extends Named> void populateList(
handler.sortList(list);
}

Element xxx(String request) throws OlapException {
/**
* Executes an XMLA metadata request and returns the root element of the
* response.
*
* @param request XMLA request string
* @return Root element of the response
* @throws OlapException on error
*/
Element executeMetadataRequest(String request) throws OlapException {
byte[] bytes;
if (DEBUG) {
System.out.println("********************************************");
Expand Down Expand Up @@ -751,8 +768,9 @@ public String generateRequest(
{
final String content = "Data";
final String encoding = proxy.getEncodingCharsetName();
final StringBuilder buf = new StringBuilder(
"<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n"
final StringBuilder buf =
new StringBuilder(
"<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n"
+ "<SOAP-ENV:Envelope\n"
+ " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"\n"
+ " SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
Expand All @@ -765,7 +783,7 @@ public String generateRequest(
"</RequestType>\n"
+ " <Restrictions>\n"
+ " <RestrictionList>\n");
String catalogName = null;
String restrictedCatalogName = null;
if (restrictions.length > 0) {
if (restrictions.length % 2 != 0) {
throw new IllegalArgumentException();
Expand All @@ -776,20 +794,20 @@ public String generateRequest(
if (o instanceof String) {
buf.append("<").append(restriction).append(">");
final String value = (String) o;
buf.append(xmlEncode(value));
xmlEncode(buf, value);
buf.append("</").append(restriction).append(">");

// To remind ourselves to generate a <Catalog> restriction
// if the request supports it.
if (restriction.equals("CATALOG_NAME")) {
catalogName = value;
restrictedCatalogName = value;
}
} else {
//noinspection unchecked
List<String> valueList = (List<String>) o;
for (String value : valueList) {
buf.append("<").append(restriction).append(">");
buf.append(xmlEncode(value));
xmlEncode(buf, value);
buf.append("</").append(restriction).append(">");
}
}
Expand All @@ -804,16 +822,26 @@ public String generateRequest(
// Add the datasource node only if this request requires it.
if (metadataRequest.requiresDatasourceName()) {
buf.append(" <DataSourceInfo>");
buf.append(xmlEncode(context.olap4jConnection.getDataSourceInfo()));
xmlEncode(buf, context.olap4jConnection.getDataSourceInfo());
buf.append("</DataSourceInfo>\n");
}

String requestCatalogName = null;
if (restrictedCatalogName != null
&& restrictedCatalogName.length() > 0)
{
requestCatalogName = restrictedCatalogName;
}

// If the request requires catalog name, and one wasn't specified in the
// restrictions, use the connection's current catalog.
if (catalogName == null
if (context.olap4jCatalog != null) {
requestCatalogName = context.olap4jCatalog.getName();
}
if (requestCatalogName == null
&& metadataRequest.requiresCatalogName())
{
catalogName = context.olap4jConnection.getCatalog();
requestCatalogName = context.olap4jConnection.getCatalog();
}

// Add the catalog node only if this request has specified it as a
Expand All @@ -827,16 +855,16 @@ public String generateRequest(
//
// For high level objects like data source and catalog, the catalog
// restriction does not make sense.
if (catalogName != null
if (requestCatalogName != null
&& metadataRequest.allowsCatalogName())
{
buf.append(" <Catalog>");
buf.append(xmlEncode(catalogName));
xmlEncode(buf, requestCatalogName);
buf.append("</Catalog>\n");
}

buf.append(" <Content>");
buf.append(xmlEncode(content));
xmlEncode(buf, content);
buf.append(
"</Content>\n"
+ " </PropertyList>\n"
Expand All @@ -850,16 +878,33 @@ public String generateRequest(
/**
* Encodes a string for use in an XML CDATA section.
*
* @param value to be xml encoded
* @return an XML encode string or the value is not required.
* @param value Value to be xml encoded
* @param buf Buffer to append to
*/
private static String xmlEncode(String value) {
value = Olap4jUtil.replace(value, "&", "&amp;");
value = Olap4jUtil.replace(value, "<", "&lt;");
value = Olap4jUtil.replace(value, ">", "&gt;");
value = Olap4jUtil.replace(value, "\"", "&quot;");
value = Olap4jUtil.replace(value, "'", "&apos;");
return value;
private static void xmlEncode(StringBuilder buf, String value) {
final int n = value.length();
for (int i = 0; i < n; ++i) {
char c = value.charAt(i);
switch (c) {
case '&':
buf.append("&amp;");
break;
case '<':
buf.append("&lt;");
break;
case '>':
buf.append("&gt;");
break;
case '"':
buf.append("&quot;");
break;
case '\'':
buf.append("&apos;");
break;
default:
buf.append(c);
}
}
}

// ~ inner classes --------------------------------------------------------
Expand Down Expand Up @@ -1464,20 +1509,19 @@ public void handle(
</row>
*/

/*
* We are looking for a schema name from the cubes query restricted
* on the catalog name. Some servers don't support nor include the
* SCHEMA_NAME column in its response. If it's null, we convert it
* to an empty string as to not cause problems later on.
*/
String schemaName = stringElement(row, "SCHEMA_NAME");
String catalogName = stringElement(row, "CATALOG_NAME");

if (this.catalogName.equals(catalogName)) {
// We are looking for a schema name from the cubes query restricted
// on the catalog name. Some servers don't support nor include the
// SCHEMA_NAME column in its response. If it's null, we convert it
// to an empty string as to not cause problems later on.
final String schemaName = stringElement(row, "SCHEMA_NAME");
final String catalogName = stringElement(row, "CATALOG_NAME");
final String schemaName2 = (schemaName == null) ? "" : schemaName;
if (this.catalogName.equals(catalogName)
&& ((NamedList<XmlaOlap4jSchema>)list).get(schemaName2) == null)
{
list.add(
new XmlaOlap4jSchema(
context.getCatalog(row),
(schemaName == null) ? "" : schemaName));
context.getCatalog(row), schemaName2));
}
}
}
Expand Down Expand Up @@ -2054,8 +2098,7 @@ public boolean requiresCatalogName() {
* @return whether this request allows a CatalogName element
*/
public boolean allowsCatalogName() {
return this != DBSCHEMA_CATALOGS
&& this != DISCOVER_DATASOURCES;
return true;
}

/**
Expand Down
Loading

0 comments on commit 58727bb

Please sign in to comment.