001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2007-2012 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.olap4j;
011
012import mondrian.calc.ResultStyle;
013import mondrian.olap.*;
014import mondrian.rolap.RolapConnection;
015import mondrian.server.*;
016import mondrian.util.Pair;
017
018import org.olap4j.*;
019import org.olap4j.layout.RectangularCellSetFormatter;
020import org.olap4j.mdx.*;
021
022import java.io.PrintWriter;
023import java.io.StringWriter;
024import java.sql.*;
025import java.util.Collections;
026import java.util.List;
027
028/**
029 * Implementation of {@link org.olap4j.OlapStatement}
030 * for the Mondrian OLAP engine.
031 *
032 * @author jhyde
033 * @since May 24, 2007
034 */
035abstract class MondrianOlap4jStatement
036    extends StatementImpl
037    implements OlapStatement, mondrian.server.Statement
038{
039    final MondrianOlap4jConnection olap4jConnection;
040    private boolean closed;
041
042    /**
043     * Support for {@link #closeOnCompletion()} method.
044     */
045    protected boolean closeOnCompletion;
046
047    /**
048     * Current cell set, or null if the statement is not executing anything.
049     * Any method which modifies this member must synchronize
050     * on the MondrianOlap4jStatement.
051     */
052    MondrianOlap4jCellSet openCellSet;
053
054    MondrianOlap4jStatement(
055        MondrianOlap4jConnection olap4jConnection)
056    {
057        assert olap4jConnection != null;
058        this.olap4jConnection = olap4jConnection;
059        this.closed = false;
060    }
061
062    // implement Statement
063
064    public ResultSet executeQuery(String mdx) throws SQLException {
065        return executeQuery2(mdx, false, null, null);
066    }
067
068    ResultSet executeQuery2(
069        String mdx,
070        boolean advanced,
071        String tabFields,
072        int[] rowCountSlot) throws SQLException
073    {
074        if (advanced) {
075            // REVIEW: I removed 'executeDrillThroughAdvanced' in the cleanup.
076            // Do we still need it?
077            throw new UnsupportedOperationException();
078        }
079        QueryPart parseTree;
080        try {
081            parseTree =
082                olap4jConnection.getMondrianConnection().parseStatement(mdx);
083        } catch (MondrianException e) {
084            throw olap4jConnection.helper.createException(
085                "mondrian gave exception while parsing query", e);
086        }
087        if (parseTree instanceof DrillThrough) {
088            DrillThrough drillThrough = (DrillThrough) parseTree;
089            final Query query = drillThrough.getQuery();
090            query.setResultStyle(ResultStyle.LIST);
091            setQuery(query);
092            CellSet cellSet = executeOlapQueryInternal(query, null);
093            final List<Integer> coords = Collections.nCopies(
094                cellSet.getAxes().size(), 0);
095            final MondrianOlap4jCell cell =
096                (MondrianOlap4jCell) cellSet.getCell(coords);
097
098            ResultSet resultSet =
099                cell.drillThroughInternal(
100                    drillThrough.getMaxRowCount(),
101                    drillThrough.getFirstRowOrdinal(),
102                    drillThrough.getReturnList(),
103                    true,
104                    null,
105                    rowCountSlot);
106            if (resultSet == null) {
107                throw new OlapException(
108                    "Cannot do DrillThrough operation on the cell");
109            }
110            return resultSet;
111        } else if (parseTree instanceof Explain) {
112            String plan = explainInternal(((Explain) parseTree).getQuery());
113            return olap4jConnection.factory.newFixedResultSet(
114                olap4jConnection,
115                Collections.singletonList("PLAN"),
116                Collections.singletonList(
117                    Collections.<Object>singletonList(plan)));
118        } else {
119            throw olap4jConnection.helper.createException(
120                "Query does not have relational result. Use a DRILLTHROUGH "
121                + "query, or execute using the executeOlapQuery method.");
122        }
123    }
124
125    private String explainInternal(QueryPart query) {
126        final StringWriter sw = new StringWriter();
127        final PrintWriter pw = new PrintWriter(sw);
128        query.explain(pw);
129        pw.flush();
130        return sw.toString();
131    }
132
133    public int executeUpdate(String sql) throws SQLException {
134        throw new UnsupportedOperationException();
135    }
136
137    public synchronized void close() {
138        if (!closed) {
139            closed = true;
140            olap4jConnection.mondrianServer.removeStatement(this);
141            if (openCellSet != null) {
142                MondrianOlap4jCellSet c = openCellSet;
143                openCellSet = null;
144                c.close();
145            }
146        }
147    }
148
149    public int getMaxFieldSize() throws SQLException {
150        throw new UnsupportedOperationException();
151    }
152
153    public void setMaxFieldSize(int max) throws SQLException {
154        throw new UnsupportedOperationException();
155    }
156
157    public int getMaxRows() throws SQLException {
158        throw new UnsupportedOperationException();
159    }
160
161    public void setMaxRows(int max) throws SQLException {
162        throw new UnsupportedOperationException();
163    }
164
165    public void setEscapeProcessing(boolean enable) throws SQLException {
166        throw new UnsupportedOperationException();
167    }
168
169    public int getQueryTimeout() throws SQLException {
170        long timeoutSeconds = getQueryTimeoutMillis() / 1000;
171        if (timeoutSeconds > Integer.MAX_VALUE) {
172            return Integer.MAX_VALUE;
173        }
174        if (timeoutSeconds == 0 && getQueryTimeoutMillis() > 0) {
175            // Don't return timeout=0 if e.g. timeoutMillis=500. 0 is special.
176            return 1;
177        }
178        return (int) timeoutSeconds;
179    }
180
181    public void setQueryTimeout(int seconds) throws SQLException {
182        if (seconds < 0) {
183            throw olap4jConnection.helper.createException(
184                "illegal timeout value " + seconds);
185        }
186        setQueryTimeoutMillis(seconds * 1000);
187    }
188
189    public synchronized void cancel() throws SQLException {
190        if (openCellSet != null) {
191            openCellSet.cancel();
192        }
193    }
194
195    public SQLWarning getWarnings() throws SQLException {
196        throw new UnsupportedOperationException();
197    }
198
199    public void clearWarnings() throws SQLException {
200        throw new UnsupportedOperationException();
201    }
202
203    public void setCursorName(String name) throws SQLException {
204        throw new UnsupportedOperationException();
205    }
206
207    public boolean execute(String sql) throws SQLException {
208        throw new UnsupportedOperationException();
209    }
210
211    public ResultSet getResultSet() throws SQLException {
212        // NOTE: cell set becomes visible in this member while
213        // executeOlapQueryInternal is still in progress, and before it has
214        // finished executing. Its internal state may not be ready for API
215        // calls. JDBC never claims to be thread-safe! (Except for calls to the
216        // cancel method.) It is not possible to synchronize, because it would
217        // block 'cancel'.
218        return openCellSet;
219    }
220
221    public int getUpdateCount() throws SQLException {
222        throw new UnsupportedOperationException();
223    }
224
225    public boolean getMoreResults() throws SQLException {
226        throw new UnsupportedOperationException();
227    }
228
229    public void setFetchDirection(int direction) throws SQLException {
230        throw new UnsupportedOperationException();
231    }
232
233    public int getFetchDirection() throws SQLException {
234        throw new UnsupportedOperationException();
235    }
236
237    public void setFetchSize(int rows) throws SQLException {
238        throw new UnsupportedOperationException();
239    }
240
241    public int getFetchSize() throws SQLException {
242        throw new UnsupportedOperationException();
243    }
244
245    public int getResultSetConcurrency() throws SQLException {
246        throw new UnsupportedOperationException();
247    }
248
249    public int getResultSetType() throws SQLException {
250        throw new UnsupportedOperationException();
251    }
252
253    public void addBatch(String sql) throws SQLException {
254        throw new UnsupportedOperationException();
255    }
256
257    public void clearBatch() throws SQLException {
258        throw new UnsupportedOperationException();
259    }
260
261    public int[] executeBatch() throws SQLException {
262        throw new UnsupportedOperationException();
263    }
264
265    public OlapConnection getConnection() {
266        return olap4jConnection;
267    }
268
269    public boolean getMoreResults(int current) throws SQLException {
270        throw new UnsupportedOperationException();
271    }
272
273    public ResultSet getGeneratedKeys() throws SQLException {
274        throw new UnsupportedOperationException();
275    }
276
277    public int executeUpdate(
278        String sql, int autoGeneratedKeys) throws SQLException
279    {
280        throw new UnsupportedOperationException();
281    }
282
283    public int executeUpdate(
284        String sql, int columnIndexes[]) throws SQLException
285    {
286        throw new UnsupportedOperationException();
287    }
288
289    public int executeUpdate(
290        String sql, String columnNames[]) throws SQLException
291    {
292        throw new UnsupportedOperationException();
293    }
294
295    public boolean execute(
296        String sql, int autoGeneratedKeys) throws SQLException
297    {
298        throw new UnsupportedOperationException();
299    }
300
301    public boolean execute(
302        String sql, int columnIndexes[]) throws SQLException
303    {
304        throw new UnsupportedOperationException();
305    }
306
307    public boolean execute(
308        String sql, String columnNames[]) throws SQLException
309    {
310        throw new UnsupportedOperationException();
311    }
312
313    public int getResultSetHoldability() throws SQLException {
314        throw new UnsupportedOperationException();
315    }
316
317    public boolean isClosed() throws SQLException {
318        return closed;
319    }
320
321    public void setPoolable(boolean poolable) throws SQLException {
322        throw new UnsupportedOperationException();
323    }
324
325    public boolean isPoolable() throws SQLException {
326        throw new UnsupportedOperationException();
327    }
328
329    // implement Wrapper
330
331    public <T> T unwrap(Class<T> iface) throws SQLException {
332        if (iface.isInstance(this)) {
333            return iface.cast(this);
334        }
335        throw olap4jConnection.helper.createException(
336            "does not implement '" + iface + "'");
337    }
338
339    public boolean isWrapperFor(Class<?> iface) throws SQLException {
340        return iface.isInstance(this);
341    }
342
343    // implement OlapStatement
344
345    public CellSet executeOlapQuery(final String mdx) throws OlapException {
346        final Pair<Query, MondrianOlap4jCellSetMetaData> pair = parseQuery(mdx);
347        return executeOlapQueryInternal(pair.left, pair.right);
348    }
349
350    protected Pair<Query, MondrianOlap4jCellSetMetaData>
351    parseQuery(final String mdx)
352        throws OlapException
353    {
354        try {
355            final RolapConnection mondrianConnection = getMondrianConnection();
356            return Locus.execute(
357                mondrianConnection,
358                "Parsing query",
359                new Locus.Action<Pair<Query, MondrianOlap4jCellSetMetaData>>() {
360                    public Pair<Query, MondrianOlap4jCellSetMetaData> execute()
361                    {
362                        final Query query =
363                            (Query) mondrianConnection.parseStatement(
364                                MondrianOlap4jStatement.this,
365                                mdx,
366                                null,
367                                false);
368                        final MondrianOlap4jCellSetMetaData cellSetMetaData =
369                            new MondrianOlap4jCellSetMetaData(
370                                MondrianOlap4jStatement.this, query);
371                        return Pair.of(query, cellSetMetaData);
372                    }
373                });
374        } catch (MondrianException e) {
375            throw olap4jConnection.helper.createException(
376                "mondrian gave exception while parsing query", e);
377        }
378    }
379
380    /**
381     * Executes a parsed query, closing any previously open cellset.
382     *
383     *
384     * @param query Parsed query
385     * @param cellSetMetaData Cell set metadata
386     * @return Cell set
387     * @throws OlapException if a database error occurs
388     */
389    protected CellSet executeOlapQueryInternal(
390        Query query,
391        MondrianOlap4jCellSetMetaData cellSetMetaData) throws OlapException
392    {
393        // Close the previous open CellSet, if there is one.
394        synchronized (this) {
395            if (openCellSet != null) {
396                final MondrianOlap4jCellSet cs = openCellSet;
397                openCellSet = null;
398                try {
399                    cs.close();
400                } catch (Exception e) {
401                    throw olap4jConnection.helper.createException(
402                        null, "Error while closing previous CellSet", e);
403                }
404            }
405
406            if (olap4jConnection.preferList) {
407                query.setResultStyle(ResultStyle.LIST);
408            }
409            this.query = query;
410            openCellSet = olap4jConnection.factory.newCellSet(this);
411        }
412        // Release the monitor before executing, to give another thread the
413        // opportunity to call cancel.
414        try {
415            openCellSet.execute();
416        } catch (QueryCanceledException e) {
417            throw olap4jConnection.helper.createException(
418                "Query canceled", e);
419        } catch (QueryTimeoutException e) {
420            throw olap4jConnection.helper.createException(
421                e.getMessage(), e);
422        } catch (MondrianException e) {
423            throw olap4jConnection.helper.createException(
424                "mondrian gave exception while executing query", e);
425        }
426        return openCellSet;
427    }
428
429    @Override
430    public void start(Execution execution) {
431        super.start(openCellSet);
432    }
433
434    public CellSet executeOlapQuery(SelectNode selectNode)
435        throws OlapException
436    {
437        final String mdx = toString(selectNode);
438        return executeOlapQuery(mdx);
439    }
440
441    public void addListener(
442        CellSetListener.Granularity granularity,
443        CellSetListener cellSetListener) throws OlapException
444    {
445        // Cell set listener API not supported in this version of mondrian.
446        throw new UnsupportedOperationException();
447    }
448
449    /**
450     * Converts a {@link org.olap4j.mdx.ParseTreeNode} to MDX string.
451     *
452     * @param node Parse tree node
453     * @return MDX text
454     */
455    private static String toString(ParseTreeNode node) {
456        StringWriter sw = new StringWriter();
457        PrintWriter pw = new PrintWriter(sw);
458        ParseTreeWriter parseTreeWriter = new ParseTreeWriter(pw);
459        node.unparse(parseTreeWriter);
460        pw.flush();
461        return sw.toString();
462    }
463
464    public RolapConnection getMondrianConnection() {
465        try {
466            return olap4jConnection.getMondrianConnection();
467        } catch (OlapException e) {
468            throw new RuntimeException(e);
469        }
470    }
471
472    /**
473     * Called by each child result set (most likely a cell set) when it is
474     * closed.
475     *
476     * @param resultSet Result set or cell set
477     */
478    void onResultSetClose(ResultSet resultSet) {
479        if (closeOnCompletion) {
480            close();
481        }
482    }
483}
484
485// End MondrianOlap4jStatement.java