001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/SqlStatement.java#5 $ 003 // This software is subject to the terms of the Common Public License 004 // Agreement, available at the following URL: 005 // http://www.opensource.org/licenses/cpl.html. 006 // Copyright (C) 2007-2008 Julian Hyde and others 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package mondrian.rolap; 011 012 import mondrian.olap.Util; 013 014 import javax.sql.DataSource; 015 016 import java.sql.Connection; 017 import java.sql.ResultSet; 018 import java.sql.SQLException; 019 import java.sql.Statement; 020 021 /** 022 * SqlStatement contains a SQL statement and associated resources throughout 023 * its lifetime. 024 * 025 * <p>The goal of SqlStatement is to make tracing, error-handling and 026 * resource-management easier. None of the methods throws a SQLException; 027 * if an error occurs in one of the methods, the method wraps the exception 028 * in a {@link RuntimeException} describing the high-level operation, logs 029 * that the operation failed, and throws that RuntimeException. 030 * 031 * <p>If methods succeed, the method generates lifecycle logging such as 032 * the elapsed time and number of rows fetched. 033 * 034 * <p>There are a few obligations on the caller. The caller must:<ul> 035 * <li>call the {@link #handle(Exception)} method if one of the contained 036 * objects (say the {@link java.sql.ResultSet}) gives an error; 037 * <li>call the {@link #close()} method if all operations complete 038 * successfully. 039 * <li>increment the {@link #rowCount} field each time a row is fetched. 040 * </ul> 041 * 042 * <p>The {@link #close()} method is idempotent. You are welcome to call it 043 * more than once. 044 * 045 * <p>SqlStatement is not thread-safe. 046 * 047 * @version $Id: //open/mondrian/src/main/mondrian/rolap/SqlStatement.java#5 $ 048 * @author jhyde 049 * @since 2.3 050 */ 051 public class SqlStatement { 052 053 private final DataSource dataSource; 054 private Connection jdbcConnection; 055 private ResultSet resultSet; 056 private final String sql; 057 private final int maxRows; 058 private final String component; 059 private final int resultSetType; 060 private final int resultSetConcurrency; 061 private final RolapUtil.Semaphore querySemaphore = RolapUtil 062 .getQuerySemaphore(); 063 private final String message; 064 private boolean haveSemaphore; 065 public int rowCount; 066 private long startTime; 067 068 // used for SQL logging, allows for a SQL Statement UID 069 private static long executeCount = -1; 070 071 SqlStatement( 072 DataSource dataSource, 073 String sql, 074 int maxRows, 075 String component, 076 String message, 077 int resultSetType, 078 int resultSetConcurrency) 079 { 080 this.dataSource = dataSource; 081 this.sql = sql; 082 this.maxRows = maxRows; 083 this.component = component; 084 this.message = message; 085 this.resultSetType = resultSetType; 086 this.resultSetConcurrency = resultSetConcurrency; 087 } 088 089 public void execute() throws SQLException { 090 this.jdbcConnection = dataSource.getConnection(); 091 querySemaphore.enter(); 092 haveSemaphore = true; 093 Statement statement = null; 094 String status = "failed"; 095 long currId = 0; 096 // Trace start of execution. 097 if (RolapUtil.SQL_LOGGER.isDebugEnabled()) { 098 currId = ++executeCount; 099 StringBuffer sqllog = new StringBuffer(); 100 sqllog.append(currId + ": " + component + ": executing sql ["); 101 if (sql.indexOf('\n') >= 0) { 102 // SQL appears to be formatted as multiple lines. Make it 103 // start on its own line. 104 sqllog.append("\n"); 105 } 106 sqllog.append(sql); 107 sqllog.append(']'); 108 RolapUtil.SQL_LOGGER.debug(sqllog.toString()); 109 } 110 111 // Execute hook. 112 RolapUtil.ExecuteQueryHook hook = RolapUtil.threadHooks.get(); 113 if (hook != null) { 114 hook.onExecuteQuery(sql); 115 } 116 try { 117 startTime = System.currentTimeMillis(); 118 if (resultSetType < 0 || resultSetConcurrency < 0) { 119 statement = jdbcConnection.createStatement(); 120 } else { 121 statement = jdbcConnection.createStatement( 122 resultSetType, 123 resultSetConcurrency); 124 } 125 if (maxRows > 0) { 126 statement.setMaxRows(maxRows); 127 } 128 this.resultSet = statement.executeQuery(sql); 129 long time = System.currentTimeMillis(); 130 final long execMs = time - startTime; 131 Util.addDatabaseTime(execMs); 132 status = ", exec " + execMs + " ms"; 133 } catch (SQLException e) { 134 status = ", failed (" + e + ")"; 135 try { 136 if (statement != null) { 137 statement.close(); 138 } 139 } catch (SQLException e2) { 140 // ignore 141 } 142 throw handle(e); 143 } finally { 144 RolapUtil.SQL_LOGGER.debug(currId + ": " + status); 145 146 if (RolapUtil.LOGGER.isDebugEnabled()) { 147 RolapUtil.LOGGER.debug(component + ": executing sql [" + 148 sql + "]" + status); 149 } 150 } 151 } 152 153 /** 154 * Closes all resources (statement, result set) held by this 155 * SqlStatement. 156 * 157 * <p>If any of them fails, wraps them in a 158 * {@link RuntimeException} describing the high-level operation which 159 * this statement was performing. No further error-handling is required 160 * to produce a descriptive stack trace, unless you want to absorb the 161 * error. 162 */ 163 public void close() { 164 if (haveSemaphore) { 165 haveSemaphore = false; 166 querySemaphore.leave(); 167 } 168 if (resultSet != null) { 169 try { 170 resultSet.close(); 171 } catch (SQLException e) { 172 throw Util.newError(message + "; sql=[" + sql + "]"); 173 } finally { 174 resultSet = null; 175 } 176 } 177 if (jdbcConnection != null) { 178 try { 179 jdbcConnection.close(); 180 } catch (SQLException e) { 181 throw Util.newError(message + "; sql=[" + sql + "]"); 182 } finally { 183 jdbcConnection = null; 184 } 185 } 186 long time = System.currentTimeMillis(); 187 long totalMs = time - startTime; 188 String status = ", exec+fetch " + totalMs + " ms, " + rowCount + " rows"; 189 190 RolapUtil.SQL_LOGGER.debug(executeCount + ": " + status); 191 192 if (RolapUtil.LOGGER.isDebugEnabled()) { 193 RolapUtil.LOGGER.debug(component + ": done executing sql [" + 194 sql + "]" + status); 195 } 196 } 197 198 public ResultSet getResultSet() { 199 return resultSet; 200 } 201 202 /** 203 * Handles an exception thrown from the ResultSet, implicitly calls 204 * {@link #close}, and returns an exception which includes the full 205 * stack, including a description of the high-level operation. 206 * 207 * @param e Exception 208 * @return Runtime exception 209 */ 210 public RuntimeException handle(Exception e) { 211 RuntimeException runtimeException = 212 Util.newError(e, message + "; sql=[" + sql + "]"); 213 try { 214 close(); 215 } catch (RuntimeException re) { 216 // ignore 217 } 218 return runtimeException; 219 } 220 } 221 222 // End SqlStatement.java