/*
* This software is subject to the terms of the Eclipse Public License v1.0
* Agreement, available at the following URL:
* http://www.eclipse.org/legal/epl-v10.html.
* You must accept the terms of that agreement to use this software.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package mondrian.olap4j;
import mondrian.calc.ResultStyle;
import mondrian.olap.*;
import mondrian.rolap.RolapConnection;
import mondrian.server.*;
import mondrian.util.Pair;
import org.olap4j.*;
import org.olap4j.layout.RectangularCellSetFormatter;
import org.olap4j.mdx.*;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.*;
import java.util.Collections;
import java.util.List;
/**
* Implementation of {@link org.olap4j.OlapStatement}
* for the Mondrian OLAP engine.
*
* @author jhyde
* @since May 24, 2007
*/
abstract class MondrianOlap4jStatement
extends StatementImpl
implements OlapStatement, mondrian.server.Statement
{
final MondrianOlap4jConnection olap4jConnection;
private boolean closed;
/**
* Support for {@link #closeOnCompletion()} method.
*/
protected boolean closeOnCompletion;
/**
* Current cell set, or null if the statement is not executing anything.
* Any method which modifies this member must synchronize
* on the MondrianOlap4jStatement.
*/
MondrianOlap4jCellSet openCellSet;
MondrianOlap4jStatement(
MondrianOlap4jConnection olap4jConnection)
{
assert olap4jConnection != null;
this.olap4jConnection = olap4jConnection;
this.closed = false;
}
// implement Statement
public ResultSet executeQuery(String mdx) throws SQLException {
return executeQuery2(mdx, false, null, null);
}
ResultSet executeQuery2(
String mdx,
boolean advanced,
String tabFields,
int[] rowCountSlot) throws SQLException
{
if (advanced) {
// REVIEW: I removed 'executeDrillThroughAdvanced' in the cleanup.
// Do we still need it?
throw new UnsupportedOperationException();
}
QueryPart parseTree;
try {
parseTree =
olap4jConnection.getMondrianConnection().parseStatement(mdx);
} catch (MondrianException e) {
throw olap4jConnection.helper.createException(
"mondrian gave exception while parsing query", e);
}
if (parseTree instanceof DrillThrough) {
DrillThrough drillThrough = (DrillThrough) parseTree;
final Query query = drillThrough.getQuery();
query.setResultStyle(ResultStyle.LIST);
setQuery(query);
CellSet cellSet = executeOlapQueryInternal(query, null);
final List<Integer> coords = Collections.nCopies(
cellSet.getAxes().size(), 0);
final MondrianOlap4jCell cell =
(MondrianOlap4jCell) cellSet.getCell(coords);
ResultSet resultSet =
cell.drillThroughInternal(
drillThrough.getMaxRowCount(),
drillThrough.getFirstRowOrdinal(),
drillThrough.getReturnList(),
true,
null,
rowCountSlot);
if (resultSet == null) {
throw new OlapException(
"Cannot do DrillThrough operation on the cell");
}
return resultSet;
} else if (parseTree instanceof Explain) {
String plan = explainInternal(((Explain) parseTree).getQuery());
return olap4jConnection.factory.newFixedResultSet(
olap4jConnection,
Collections.singletonList("PLAN"),
Collections.singletonList(
Collections.<Object>singletonList(plan)));
} else {
throw olap4jConnection.helper.createException(
"Query does not have relational result. Use a DRILLTHROUGH "
+ "query, or execute using the executeOlapQuery method.");
}
}
private String explainInternal(QueryPart query) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
query.explain(pw);
pw.flush();
return sw.toString();
}
public int executeUpdate(String sql) throws SQLException {
throw new UnsupportedOperationException();
}
public synchronized void close() {
if (!closed) {
closed = true;
olap4jConnection.mondrianServer.removeStatement(this);
if (openCellSet != null) {
MondrianOlap4jCellSet c = openCellSet;
openCellSet = null;
c.close();
}
}
}
public int getMaxFieldSize() throws SQLException {
throw new UnsupportedOperationException();
}
public void setMaxFieldSize(int max) throws SQLException {
throw new UnsupportedOperationException();
}
public int getMaxRows() throws SQLException {
throw new UnsupportedOperationException();
}
public void setMaxRows(int max) throws SQLException {
throw new UnsupportedOperationException();
}
public void setEscapeProcessing(boolean enable) throws SQLException {
throw new UnsupportedOperationException();
}
public int getQueryTimeout() throws SQLException {
long timeoutSeconds = getQueryTimeoutMillis() / 1000;
if (timeoutSeconds > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
if (timeoutSeconds == 0 && getQueryTimeoutMillis() > 0) {
// Don't return timeout=0 if e.g. timeoutMillis=500. 0 is special.
return 1;
}
return (int) timeoutSeconds;
}
public void setQueryTimeout(int seconds) throws SQLException {
if (seconds < 0) {
throw olap4jConnection.helper.createException(
"illegal timeout value " + seconds);
}
setQueryTimeoutMillis(seconds * 1000);
}
public synchronized void cancel() throws SQLException {
if (openCellSet != null) {
openCellSet.cancel();
}
}
public SQLWarning getWarnings() throws SQLException {
throw new UnsupportedOperationException();
}
public void clearWarnings() throws SQLException {
throw new UnsupportedOperationException();
}
public void setCursorName(String name) throws SQLException {
throw new UnsupportedOperationException();
}
public boolean execute(String sql) throws SQLException {
throw new UnsupportedOperationException();
}
public ResultSet getResultSet() throws SQLException {
// NOTE: cell set becomes visible in this member while
// executeOlapQueryInternal is still in progress, and before it has
// finished executing. Its internal state may not be ready for API
// calls. JDBC never claims to be thread-safe! (Except for calls to the
// cancel method.) It is not possible to synchronize, because it would
// block 'cancel'.
return openCellSet;
}
public int getUpdateCount() throws SQLException {
throw new UnsupportedOperationException();
}
public boolean getMoreResults() throws SQLException {
throw new UnsupportedOperationException();
}
public void setFetchDirection(int direction) throws SQLException {
throw new UnsupportedOperationException();
}
public int getFetchDirection() throws SQLException {
throw new UnsupportedOperationException();
}
public void setFetchSize(int rows) throws SQLException {
throw new UnsupportedOperationException();
}
public int getFetchSize() throws SQLException {
throw new UnsupportedOperationException();
}
public int getResultSetConcurrency() throws SQLException {
throw new UnsupportedOperationException();
}
public int getResultSetType() throws SQLException {
throw new UnsupportedOperationException();
}
public void addBatch(String sql) throws SQLException {
throw new UnsupportedOperationException();
}
public void clearBatch() throws SQLException {
throw new UnsupportedOperationException();
}
public int[] executeBatch() throws SQLException {
throw new UnsupportedOperationException();
}
public OlapConnection getConnection() {
return olap4jConnection;
}
public boolean getMoreResults(int current) throws SQLException {
throw new UnsupportedOperationException();
}
public ResultSet getGeneratedKeys() throws SQLException {
throw new UnsupportedOperationException();
}
public int executeUpdate(
String sql, int autoGeneratedKeys) throws SQLException
{
throw new UnsupportedOperationException();
}
public int executeUpdate(
String sql, int columnIndexes[]) throws SQLException
{
throw new UnsupportedOperationException();
}
public int executeUpdate(
String sql, String columnNames[]) throws SQLException
{
throw new UnsupportedOperationException();
}
public boolean execute(
String sql, int autoGeneratedKeys) throws SQLException
{
throw new UnsupportedOperationException();
}
public boolean execute(
String sql, int columnIndexes[]) throws SQLException
{
throw new UnsupportedOperationException();
}
public boolean execute(
String sql, String columnNames[]) throws SQLException
{
throw new UnsupportedOperationException();
}
public int getResultSetHoldability() throws SQLException {
throw new UnsupportedOperationException();
}
public boolean isClosed() throws SQLException {
return closed;
}
public void setPoolable(boolean poolable) throws SQLException {
throw new UnsupportedOperationException();
}
public boolean isPoolable() throws SQLException {
throw new UnsupportedOperationException();
}
// implement Wrapper
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return iface.cast(this);
}
throw olap4jConnection.helper.createException(
"does not implement '" + iface + "'");
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface.isInstance(this);
}
// implement OlapStatement
public CellSet executeOlapQuery(final String mdx) throws OlapException {
final Pair<Query, MondrianOlap4jCellSetMetaData> pair = parseQuery(mdx);
return executeOlapQueryInternal(pair.left, pair.right);
}
protected Pair<Query, MondrianOlap4jCellSetMetaData>
parseQuery(final String mdx)
throws OlapException
{
try {
final RolapConnection mondrianConnection = getMondrianConnection();
return Locus.execute(
mondrianConnection,
"Parsing query",
new Locus.Action<Pair<Query, MondrianOlap4jCellSetMetaData>>() {
public Pair<Query, MondrianOlap4jCellSetMetaData> execute()
{
final Query query =
(Query) mondrianConnection.parseStatement(
MondrianOlap4jStatement.this,
mdx,
null,
false);
final MondrianOlap4jCellSetMetaData cellSetMetaData =
new MondrianOlap4jCellSetMetaData(
MondrianOlap4jStatement.this, query);
return Pair.of(query, cellSetMetaData);
}
});
} catch (MondrianException e) {
throw olap4jConnection.helper.createException(
"mondrian gave exception while parsing query", e);
}
}
/**
* Executes a parsed query, closing any previously open cellset.
*
*
* @param query Parsed query
* @param cellSetMetaData Cell set metadata
* @return Cell set
* @throws OlapException if a database error occurs
*/
protected CellSet executeOlapQueryInternal(
Query query,
MondrianOlap4jCellSetMetaData cellSetMetaData) throws OlapException
{
// Close the previous open CellSet, if there is one.
synchronized (this) {
if (openCellSet != null) {
final MondrianOlap4jCellSet cs = openCellSet;
openCellSet = null;
try {
cs.close();
} catch (Exception e) {
throw olap4jConnection.helper.createException(
null, "Error while closing previous CellSet", e);
}
}
if (olap4jConnection.preferList) {
query.setResultStyle(ResultStyle.LIST);
}
this.query = query;
openCellSet = olap4jConnection.factory.newCellSet(this);
}
// Release the monitor before executing, to give another thread the
// opportunity to call cancel.
try {
openCellSet.execute();
} catch (QueryCanceledException e) {
throw olap4jConnection.helper.createException(
"Query canceled", e);
} catch (QueryTimeoutException e) {
throw olap4jConnection.helper.createException(
e.getMessage(), e);
} catch (MondrianException e) {
throw olap4jConnection.helper.createException(
"mondrian gave exception while executing query", e);
}
return openCellSet;
}
@Override
public void start(Execution execution) {
super.start(openCellSet);
}
public CellSet executeOlapQuery(SelectNode selectNode)
throws OlapException
{
final String mdx = toString(selectNode);
return executeOlapQuery(mdx);
}
public void addListener(
CellSetListener.Granularity granularity,
CellSetListener cellSetListener) throws OlapException
{
// Cell set listener API not supported in this version of mondrian.
throw new UnsupportedOperationException();
}
/**
* Converts a {@link org.olap4j.mdx.ParseTreeNode} to MDX string.
*
* @param node Parse tree node
* @return MDX text
*/
private static String toString(ParseTreeNode node) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ParseTreeWriter parseTreeWriter = new ParseTreeWriter(pw);
node.unparse(parseTreeWriter);
pw.flush();
return sw.toString();
}
public RolapConnection getMondrianConnection() {
try {
return olap4jConnection.getMondrianConnection();
} catch (OlapException e) {
throw new RuntimeException(e);
}
}
/**
* Called by each child result set (most likely a cell set) when it is
* closed.
*
* @param resultSet Result set or cell set
*/
void onResultSetClose(ResultSet resultSet) {
if (closeOnCompletion) {
close();
}
}
}
// End MondrianOlap4jStatement.java