/*
// $Id$
// 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 and others
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
package mondrian.rolap;

import mondrian.olap.Util;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.io.PrintWriter;

/**
 * SqlStatement contains a SQL statement and associated resources throughout
 * its lifetime.
 *
 * <p>The goal of SqlStatement is to make tracing, error-handling and
 * resource-management easier. None of the methods throws a SQLException;
 * if an error occurs in one of the methods, the method wraps the exception
 * in a {@link RuntimeException} describing the high-level operation, logs
 * that the operation failed, and throws that RuntimeException.
 *
 * <p>If methods succeed, the method generates lifecycle logging such as
 * the elapsed time and number of rows fetched.
 *
 * <p>There are a few obligations on the caller. The caller must:<ul>
 * <li>call the {@link #handle(Exception)} method if one of the contained
 *     objects (say the {@link java.sql.ResultSet}) gives an error;
 * <li>call the {@link #close()} method if all operations complete
 *     successfully.
 * <li>increment the {@link #rowCount} field each time a row is fetched.
 * </ul>
 *
 * <p>The {@link #close()} method is idempotent. You are welcome to call it
 * more than once.
 *
 * <p>SqlStatement is not thread-safe.
 *
 * @version $Id$
 * @author jhyde
 * @since 2.3
 */
public class SqlStatement {
    private final DataSource dataSource;
    private Connection jdbcConnection;
    private ResultSet resultSet;
    private final String sql;
    private final int maxRows;
    private final String component;
    private final int resultSetType;
    private final int resultSetConcurrency;
    private final RolapUtil.Semaphore querySemaphore = RolapUtil
        .getQuerySemaphore();
    private final String message;
    private boolean haveSemaphore;
    public int rowCount;
    private long startTime;
    private final PrintWriter trace;

    SqlStatement(
        DataSource dataSource,
        String sql,
        int maxRows,
        String component,
        String message,
        int resultSetType,
        int resultSetConcurrency)
    {
        this.dataSource = dataSource;
        this.sql = sql;
        this.maxRows = maxRows;
        this.component = component;
        this.message = message;
        this.resultSetType = resultSetType;
        this.resultSetConcurrency = resultSetConcurrency;
        this.trace = RolapUtil.checkTracing();
    }

    public void execute() throws SQLException {
        this.jdbcConnection = dataSource.getConnection();
        querySemaphore.enter();
        haveSemaphore = true;
        Statement statement = null;
        String status = "failed";

        // Trace start of execution.
        if (trace != null) {
            trace.print(component + ": executing sql [");
            if (sql.indexOf('\n') >= 0) {
                // SQL appears to be formatted as multiple lines. Make it
                // start on its own line.
                trace.println();
            }
            trace.print(sql);
            trace.print(']');
            trace.flush();
        }

        // Execute hook.
        RolapUtil.ExecuteQueryHook hook = RolapUtil.threadHooks.get();
        if (hook != null) {
            hook.onExecuteQuery(sql);
        }
        try {
            startTime = System.currentTimeMillis();
            if (resultSetType < 0 || resultSetConcurrency < 0) {
                statement = jdbcConnection.createStatement();
            } else {
                statement = jdbcConnection.createStatement(
                    resultSetType,
                    resultSetConcurrency);
            }
            if (maxRows > 0) {
                statement.setMaxRows(maxRows);
            }
            this.resultSet = statement.executeQuery(sql);
            long time = System.currentTimeMillis();
            final long execMs = time - startTime;
            Util.addDatabaseTime(execMs);
            status = ", exec " + execMs + " ms";
        } catch (SQLException e) {
            status = ", failed (" + e + ")";
            try {
                if (statement != null) {
                    statement.close();
                }
            } catch (SQLException e2) {
                // ignore
            }
            throw handle(e);
        } finally {
            if (trace != null) {
                trace.print(status);
            }
            if (RolapUtil.LOGGER.isDebugEnabled()) {
                RolapUtil.LOGGER.debug(component + ": executing sql [" +
                    sql + "]" + status);
            }
        }
    }

    /**
     * Closes all resources (statement, result set) held by this
     * SqlStatement.
     *
     * <p>If any of them fails, wraps them in a
     * {@link RuntimeException} describing the high-level operation which
     * this statement was performing. No further error-handling is required
     * to produce a descriptive stack trace, unless you want to absorb the
     * error.
     */
    public void close() {
        if (haveSemaphore) {
            haveSemaphore = false;
            querySemaphore.leave();
        }
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw Util.newError(message + "; sql=[" + sql + "]");
            } finally {
                resultSet = null;
            }
        }
        if (jdbcConnection != null) {
            try {
                jdbcConnection.close();
            } catch (SQLException e) {
                throw Util.newError(message + "; sql=[" + sql + "]");
            } finally {
                jdbcConnection = null;
            }
        }
        long time = System.currentTimeMillis();
        long totalMs = time - startTime;
        if (trace != null) {
            trace.println(
                ", exec+fetch " + totalMs + " ms, " + rowCount + " rows");
        }
    }

    public ResultSet getResultSet() {
        return resultSet;
    }

    /**
     * Handles an exception thrown from the ResultSet, implicitly calls
     * {@link #close}, and returns an exception which includes the full
     * stack, including a description of the high-level operation.
     *
     * @param e Exception
     * @return Runtime exception
     */
    public RuntimeException handle(Exception e) {
        RuntimeException runtimeException =
            Util.newError(e, message + "; sql=[" + sql + "]");
        try {
            close();
        } catch (RuntimeException re) {
            // ignore
        }
        return runtimeException;
    }
}

// End SqlStatement.java

@@ -6136,32 +6136,114 @@
 me - startTime;%0A
+        String status = %22, exec+fetch %22 + totalMs + %22 ms, %22 + rowCount + %22 rows%22;%0A
         if (trac
@@ -6281,16 +6281,24 @@
 println(
+status);
 %0A       
@@ -6298,24 +6298,26 @@
         
+%7D%0A
         
 %22, exec+
@@ -6312,64 +6312,157 @@
     
-%22, exec+fetch %22 + totalMs + %22 ms, %22 + rowCount + %22 rows%22
+if (RolapUtil.LOGGER.isDebugEnabled()) %7B%0A            RolapUtil.LOGGER.debug(component + %22: done executing sql %5B%22 +%0A                sql + %22%5D%22 + status
 );%0A 
@@ -6461,32 +6461,40 @@
 atus);%0A        %7D
+        
 %0A    %7D%0A%0A    publ

make fetch time available via log4j.