    001    /**
    002     * Copyright 2007-2008 Arthur Blake
    003     *
    004     * Licensed under the Apache License, Version 2.0 (the "License");
    005     * you may not use this file except in compliance with the License.
    006     * You may obtain a copy of the License at
    007     *
    008     *    http://www.apache.org/licenses/LICENSE-2.0
    009     *
    010     * Unless required by applicable law or agreed to in writing, software
    011     * distributed under the License is distributed on an "AS IS" BASIS,
    012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    013     * See the License for the specific language governing permissions and
    014     * limitations under the License.
    015     */
    016    package net.sf.log4jdbc;
    017
    018    import java.sql.Connection;
    019    import java.sql.ResultSet;
    020    import java.sql.SQLException;
    021    import java.sql.SQLWarning;
    022    import java.sql.Statement;
    023    import java.util.List;
    024    import java.util.ArrayList;
    025
    026    /**
    027     * Wraps a Statement and reports method calls, returns and exceptions.
    028     *
    029     * jdbc 4.0 version
    030     *
    031     * @see StatementSpy for the jdbc 3 version.
    032     *
    033     * @author Arthur Blake
    034     */
    035    public class StatementSpy implements Statement, Spy
    036    {
    037      private final SpyLogDelegator log;
    038
    039      /**
    040       * The Connection that created this Statement.
    041       */
    042      protected ConnectionSpy connectionSpy;
    043
    044      /**
    045       * The real statement that this StatementSpy wraps.
    046       */
    047      protected Statement realStatement;
    048
    049      /**
    050       * Create a StatementSpy (JDBC 4.0 version) that wraps another Statement,
    051       * for the purpose of logging all method calls, sql, exceptions and return values.
    052       *
    053       * @param connectionSpy Connection that created this Statement.
    054       * @param realStatement real underlying Statement that this StatementSpy wraps.
    055       */
    056      public StatementSpy(ConnectionSpy connectionSpy, Statement realStatement)
    057      {
    058        if (realStatement == null)
    059        {
    060          throw new IllegalArgumentException("Must pass in a non null real Statement");
    061        }
    062        if (connectionSpy == null)
    063        {
    064          throw new IllegalArgumentException("Must pass in a non null ConnectionSpy");
    065        }
    066        this.realStatement = realStatement;
    067        this.connectionSpy = connectionSpy;
    068
    069        log = SpyLogFactory.getSpyLogDelegator();
    070      }
    071
    072      public String getClassType()
    073      {
    074        return "Statement";
    075      }
    076
    077      public int getConnectionNumber()
    078      {
    079        return connectionSpy.getConnectionNumber();
    080      }
    081
    082      /**
    083       * Report an exception to be logged which includes timing data on a sql failure.
    084       * @param methodCall description of method call and arguments passed to it that generated the exception.
    085       * @param exception exception that was generated
    086       * @param sql SQL associated with the call.
    087       * @param execTime amount of time that the jdbc driver was chugging on the SQL before it threw an exception.
    088       */
    089      protected void reportException(String methodCall, SQLException exception, String sql, long execTime)
    090      {
    091        log.exceptionOccured(this, methodCall, exception, sql, execTime);
    092      }
    093
    094      /**
    095       * Report an exception to be logged.
    096       * @param methodCall description of method call and arguments passed to it that generated the exception.
    097       * @param exception exception that was generated
    098       * @param sql SQL associated with the call.
    099       */
    100      protected void reportException(String methodCall, SQLException exception, String sql)
    101      {
    102        log.exceptionOccured(this, methodCall, exception, sql, -1L);
    103      }
    104
    105      /**
    106       * Report an exception to be logged.
    107       *
    108       * @param methodCall description of method call and arguments passed to it that generated the exception.
    109       * @param exception exception that was generated
    110       */
    111      protected void reportException(String methodCall, SQLException exception)
    112      {
    113        log.exceptionOccured(this, methodCall, exception, null, -1L);
    114      }
    115
    116      /**
    117       * Report (for logging) that a method returned.  All the other reportReturn methods are conveniance methods that call this method.
    118       *
    119       * @param methodCall description of method call and arguments passed to it that returned.
    120       * @param msg description of what the return value that was returned.  may be an empty String for void return types.
    121       */
    122      protected void reportAllReturns(String methodCall, String msg)
    123      {
    124        log.methodReturned(this, methodCall, msg);
    125      }
    126
    127      /**
    128       * Conveniance method to report (for logging) that a method returned a boolean value.
    129       *
    130       * @param methodCall description of method call and arguments passed to it that returned.
    131       * @param value boolean return value.
    132       * @return the boolean return value as passed in.
    133       */
    134      protected boolean reportReturn(String methodCall, boolean value)
    135      {
    136        reportAllReturns(methodCall, "" + value);
    137        return value;
    138      }
    139
    140      /**
    141       * Conveniance method to report (for logging) that a method returned a byte value.
    142       *
    143       * @param methodCall description of method call and arguments passed to it that returned.
    144       * @param value byte return value.
    145       * @return the byte return value as passed in.
    146       */
    147      protected byte reportReturn(String methodCall, byte value)
    148      {
    149        reportAllReturns(methodCall, "" + value);
    150        return value;
    151      }
    152
    153      /**
    154       * Conveniance method to report (for logging) that a method returned a int value.
    155       *
    156       * @param methodCall description of method call and arguments passed to it that returned.
    157       * @param value int return value.
    158       * @return the int return value as passed in.
    159       */
    160      protected int reportReturn(String methodCall, int value)
    161      {
    162        reportAllReturns(methodCall, "" + value);
    163        return value;
    164      }
    165
    166      /**
    167       * Conveniance method to report (for logging) that a method returned a double value.
    168       *
    169       * @param methodCall description of method call and arguments passed to it that returned.
    170       * @param value double return value.
    171       * @return the double return value as passed in.
    172       */
    173      protected double reportReturn(String methodCall, double value)
    174      {
    175        reportAllReturns(methodCall, "" + value);
    176        return value;
    177      }
    178
    179      /**
    180       * Conveniance method to report (for logging) that a method returned a short value.
    181       *
    182       * @param methodCall description of method call and arguments passed to it that returned.
    183       * @param value short return value.
    184       * @return the short return value as passed in.
    185       */
    186      protected short reportReturn(String methodCall, short value)
    187      {
    188        reportAllReturns(methodCall, "" + value);
    189        return value;
    190      }
    191
    192      /**
    193       * Conveniance method to report (for logging) that a method returned a long value.
    194       *
    195       * @param methodCall description of method call and arguments passed to it that returned.
    196       * @param value long return value.
    197       * @return the long return value as passed in.
    198       */
    199      protected long reportReturn(String methodCall, long value)
    200      {
    201        reportAllReturns(methodCall, "" + value);
    202        return value;
    203      }
    204
    205      /**
    206       * Conveniance method to report (for logging) that a method returned a float value.
    207       *
    208       * @param methodCall description of method call and arguments passed to it that returned.
    209       * @param value float return value.
    210       * @return the float return value as passed in.
    211       */
    212      protected float reportReturn(String methodCall, float value)
    213      {
    214        reportAllReturns(methodCall, "" + value);
    215        return value;
    216      }
    217
    218      /**
    219       * Conveniance method to report (for logging) that a method returned an Object.
    220       *
    221       * @param methodCall description of method call and arguments passed to it that returned.
    222       * @param value return Object.
    223       * @return the return Object as passed in.
    224       */
    225      protected Object reportReturn(String methodCall, Object value)
    226      {
    227        reportAllReturns(methodCall, "" + value);
    228        return value;
    229      }
    230
    231      /**
    232       * Conveniance method to report (for logging) that a method returned (void return type).
    233       *
    234       * @param methodCall description of method call and arguments passed to it that returned.
    235       */
    236      protected void reportReturn(String methodCall)
    237      {
    238        reportAllReturns(methodCall, "");
    239      }
    240
    241      /**
    242       * Running one-off statement sql is generally inefficient and a bad idea for various reasons,
    243       * so give a warning when this is done.
    244       */
    245      private static final String StatementSqlWarning = "{WARNING: Statement used to run SQL} ";
    246
    247      /**
    248       * Report SQL for logging with a warning that it was generated from a statement.
    249       *
    250       * @param sql        the SQL being run
    251       * @param methodCall the name of the method that was running the SQL
    252       */
    253      protected void reportStatementSql(String sql, String methodCall)
    254      {
    255        // redirect to one more method call ONLY so that stack trace search is consistent
    256        // with the reportReturn calls
    257        _reportSql(StatementSqlWarning + sql, methodCall);
    258      }
    259
    260      /**
    261       * Report SQL for logging with a warning that it was generated from a statement.
    262       *
    263       * @param execTime   execution time in msec.
    264       * @param sql        the SQL being run
    265       * @param methodCall the name of the method that was running the SQL
    266       */
    267      protected void reportStatementSqlTiming(long execTime, String sql, String methodCall)
    268      {
    269        // redirect to one more method call ONLY so that stack trace search is consistent
    270        // with the reportReturn calls
    271        _reportSqlTiming(execTime, StatementSqlWarning + sql, methodCall);
    272      }
    273
    274      /**
    275       * Report SQL for logging.
    276       *
    277       * @param execTime   execution time in msec.
    278       * @param sql        the SQL being run
    279       * @param methodCall the name of the method that was running the SQL
    280       */
    281      protected void reportSqlTiming(long execTime, String sql, String methodCall)
    282      {
    283        // redirect to one more method call ONLY so that stack trace search is consistent
    284        // with the reportReturn calls
    285        _reportSqlTiming(execTime, sql, methodCall);
    286      }
    287
    288      /**
    289       * Report SQL for logging.
    290       *
    291       * @param sql        the SQL being run
    292       * @param methodCall the name of the method that was running the SQL
    293       */
    294      protected void reportSql(String sql, String methodCall)
    295      {
    296        // redirect to one more method call ONLY so that stack trace search is consistent
    297        // with the reportReturn calls
    298        _reportSql(sql, methodCall);
    299      }
    300
    301      private void _reportSql(String sql, String methodCall)
    302      {
    303        log.sqlOccured(this, methodCall, sql);
    304      }
    305
    306      private void _reportSqlTiming(long execTime, String sql, String methodCall)
    307      {
    308        log.sqlTimingOccured(this, execTime, methodCall, sql);
    309      }
    310
    311      // implementation of interface methods
    312      public SQLWarning getWarnings() throws SQLException
    313      {
    314        String methodCall = "getWarnings()";
    315        try
    316        {
    317          return (SQLWarning) reportReturn(methodCall, realStatement.getWarnings());
    318        }
    319        catch (SQLException s)
    320        {
    321          reportException(methodCall, s);
    322          throw s;
    323        }
    324      }
    325
    326      public int executeUpdate(String sql, String[] columnNames) throws SQLException
    327      {
    328        String methodCall = "executeUpdate(" + sql + ", " + columnNames + ")";
    329        reportStatementSql(sql, methodCall);
    330        long tstart = System.currentTimeMillis();
    331        try
    332        {
    333          int result = realStatement.executeUpdate(sql, columnNames);
    334          reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall);
    335          return reportReturn(methodCall, result);
    336        }
    337        catch (SQLException s)
    338        {
    339          reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
    340          throw s;
    341        }
    342      }
    343
    344      public boolean execute(String sql, String[] columnNames) throws SQLException
    345      {
    346        String methodCall = "execute(" + sql + ", " + columnNames + ")";
    347        reportStatementSql(sql, methodCall);
    348        long tstart = System.currentTimeMillis();
    349        try
    350        {
    351          boolean result = realStatement.execute(sql, columnNames);
    352          reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall);
    353          return reportReturn(methodCall, result);
    354        }
    355        catch (SQLException s)
    356        {
    357          reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
    358          throw s;
    359        }
    360      }
    361
    362      public void setMaxRows(int max) throws SQLException
    363      {
    364        String methodCall = "setMaxRows(" + max + ")";
    365        try
    366        {
    367          realStatement.setMaxRows(max);
    368        }
    369        catch (SQLException s)
    370        {
    371          reportException(methodCall, s);
    372          throw s;
    373        }
    374        reportReturn(methodCall);
    375      }
    376
    377      public boolean getMoreResults() throws SQLException
    378      {
    379        String methodCall = "getMoreResults()";
    380
    381        try
    382        {
    383          return reportReturn(methodCall, realStatement.getMoreResults());
    384        }
    385        catch (SQLException s)
    386        {
    387          reportException(methodCall, s);
    388          throw s;
    389        }
    390      }
    391
    392      public void clearWarnings() throws SQLException
    393      {
    394        String methodCall = "clearWarnings()";
    395        try
    396        {
    397          realStatement.clearWarnings();
    398        }
    399        catch (SQLException s)
    400        {
    401          reportException(methodCall, s);
    402          throw s;
    403        }
    404        reportReturn(methodCall);
    405      }
    406
    407      /**
    408       * Tracking of current batch (see addBatch, clearBatch and executeBatch)
    409       * //todo: should access to this List be synchronized?
    410       */
    411      protected List currentBatch = new ArrayList();
    412
    413      public void addBatch(String sql) throws SQLException
    414      {
    415        String methodCall = "addBatch(" + sql + ")";
    416
    417        currentBatch.add(StatementSqlWarning + sql);
    418        try
    419        {
    420          realStatement.addBatch(sql);
    421        }
    422        catch (SQLException s)
    423        {
    424          reportException(methodCall,s);
    425          throw s;
    426        }
    427        reportReturn(methodCall);
    428      }
    429
    430      public int getResultSetType() throws SQLException
    431      {
    432        String methodCall = "getResultSetType()";
    433        try
    434        {
    435          return reportReturn(methodCall, realStatement.getResultSetType());
    436        }
    437        catch (SQLException s)
    438        {
    439          reportException(methodCall, s);
    440          throw s;
    441        }
    442      }
    443
    444      public void clearBatch() throws SQLException
    445      {
    446        String methodCall = "clearBatch()";
    447        try
    448        {
    449          realStatement.clearBatch();
    450        }
    451        catch (SQLException s)
    452        {
    453          reportException(methodCall, s);
    454          throw s;
    455        }
    456        currentBatch.clear();
    457        reportReturn(methodCall);
    458      }
    459
    460      public void setFetchDirection(int direction) throws SQLException
    461      {
    462        String methodCall = "setFetchDirection(" + direction + ")";
    463        try
    464        {
    465          realStatement.setFetchDirection(direction);
    466        }
    467        catch (SQLException s)
    468        {
    469          reportException(methodCall, s);
    470          throw s;
    471        }
    472        reportReturn(methodCall);
    473      }
    474
    475      public int[] executeBatch() throws SQLException
    476      {
    477        String methodCall = "executeBatch()";
    478
    479        int j=currentBatch.size();
    480        StringBuffer batchReport = new StringBuffer("batching " + j + " statements:");
    481
    482        int fieldSize = (""+j).length();
    483
    484        String sql;
    485        for (int i=0; i < j;)
    486        {
    487          sql = (String) currentBatch.get(i);
    488          batchReport.append("\n");
    489          batchReport.append(Utilities.rightJustify(fieldSize,""+(++i)));
    490          batchReport.append(":  ");
    491          batchReport.append(sql);
    492        }
    493
    494        sql = batchReport.toString();
    495        reportSql(sql, methodCall);
    496        long tstart = System.currentTimeMillis();
    497
    498        int[] updateResults;
    499        try
    500        {
    501          updateResults = realStatement.executeBatch();
    502          reportSqlTiming(System.currentTimeMillis()-tstart, sql, methodCall);
    503        }
    504        catch (SQLException s)
    505        {
    506          reportException(methodCall, s, sql, System.currentTimeMillis()-tstart);
    507          throw s;
    508        }
    509        return (int[])reportReturn(methodCall,updateResults);
    510      }
    511
    512      public void setFetchSize(int rows) throws SQLException
    513      {
    514        String methodCall = "setFetchSize(" + rows + ")";
    515        try
    516        {
    517          realStatement.setFetchSize(rows);
    518        }
    519        catch (SQLException s)
    520        {
    521          reportException(methodCall, s);
    522          throw s;
    523        }
    524        reportReturn(methodCall);
    525      }
    526
    527      public int getQueryTimeout() throws SQLException
    528      {
    529        String methodCall = "getQueryTimeout()";
    530        try
    531        {
    532          return reportReturn(methodCall, realStatement.getQueryTimeout());
    533        }
    534        catch (SQLException s)
    535        {
    536          reportException(methodCall, s);
    537          throw s;
    538        }
    539      }
    540
    541      public Connection getConnection() throws SQLException
    542      {
    543        String methodCall = "getConnection()";
    544        return (Connection) reportReturn(methodCall, connectionSpy);
    545      }
    546
    547      public ResultSet getGeneratedKeys() throws SQLException
    548      {
    549        String methodCall = "getGeneratedKeys()";
    550        try
    551        {
    552          ResultSet r = realStatement.getGeneratedKeys();
    553          if (r == null)
    554          {
    555            return (ResultSet) reportReturn(methodCall, r);
    556          }
    557          else
    558          {
    559            return (ResultSet) reportReturn(methodCall, new ResultSetSpy(this, r));
    560          }
    561        }
    562        catch (SQLException s)
    563        {
    564          reportException(methodCall, s);
    565          throw s;
    566        }
    567      }
    568
    569      public void setEscapeProcessing(boolean enable) throws SQLException
    570      {
    571        String methodCall = "setEscapeProcessing(" + enable + ")";
    572        try
    573        {
    574          realStatement.setEscapeProcessing(enable);
    575        }
    576        catch (SQLException s)
    577        {
    578          reportException(methodCall, s);
    579          throw s;
    580        }
    581        reportReturn(methodCall);
    582      }
    583
    584      public int getFetchDirection() throws SQLException
    585      {
    586        String methodCall = "getFetchDirection()";
    587        try
    588        {
    589          return reportReturn(methodCall, realStatement.getFetchDirection());
    590        }
    591        catch (SQLException s)
    592        {
    593          reportException(methodCall, s);
    594          throw s;
    595        }
    596      }
    597
    598      public void setQueryTimeout(int seconds) throws SQLException
    599      {
    600        String methodCall = "setQueryTimeout(" + seconds + ")";
    601        try
    602        {
    603          realStatement.setQueryTimeout(seconds);
    604        }
    605        catch (SQLException s)
    606        {
    607          reportException(methodCall, s);
    608          throw s;
    609        }
    610        reportReturn(methodCall);
    611      }
    612
    613      public boolean getMoreResults(int current) throws SQLException
    614      {
    615        String methodCall = "getMoreResults(" + current + ")";
    616
    617        try
    618        {
    619          return reportReturn(methodCall, realStatement.getMoreResults(current));
    620        }
    621        catch (SQLException s)
    622        {
    623          reportException(methodCall, s);
    624          throw s;
    625        }
    626      }
    627
    628      public ResultSet executeQuery(String sql) throws SQLException
    629      {
    630        String methodCall = "executeQuery(" + sql + ")";
    631        reportStatementSql(sql, methodCall);
    632        long tstart = System.currentTimeMillis();
    633        try
    634        {
    635          ResultSet result = realStatement.executeQuery(sql);
    636          reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall);
    637          ResultSetSpy r = new ResultSetSpy(this, result);
    638          return (ResultSet) reportReturn(methodCall, r);
    639        }
    640        catch (SQLException s)
    641        {
    642          reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
    643          throw s;
    644        }
    645      }
    646
    647      public int getMaxFieldSize() throws SQLException
    648      {
    649        String methodCall = "getMaxFieldSize()";
    650        try
    651        {
    652          return reportReturn(methodCall, realStatement.getMaxFieldSize());
    653        }
    654        catch (SQLException s)
    655        {
    656          reportException(methodCall, s);
    657          throw s;
    658        }
    659      }
    660
    661      public int executeUpdate(String sql) throws SQLException
    662      {
    663        String methodCall = "executeUpdate(" + sql + ")";
    664        reportStatementSql(sql, methodCall);
    665        long tstart = System.currentTimeMillis();
    666        try
    667        {
    668          int result = realStatement.executeUpdate(sql);
    669          reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall);
    670          return reportReturn(methodCall, result);
    671        }
    672        catch (SQLException s)
    673        {
    674          reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
    675          throw s;
    676        }
    677      }
    678
    679      public void cancel() throws SQLException
    680      {
    681        String methodCall = "cancel()";
    682        try
    683        {
    684          realStatement.cancel();
    685        }
    686        catch (SQLException s)
    687        {
    688          reportException(methodCall, s);
    689          throw s;
    690        }
    691        reportReturn(methodCall);
    692      }
    693
    694      public void setCursorName(String name) throws SQLException
    695      {
    696        String methodCall = "setCursorName(" + name + ")";
    697        try
    698        {
    699          realStatement.setCursorName(name);
    700        }
    701        catch (SQLException s)
    702        {
    703          reportException(methodCall, s);
    704          throw s;
    705        }
    706        reportReturn(methodCall);
    707      }
    708
    709      public int getFetchSize() throws SQLException
    710      {
    711        String methodCall = "getFetchSize()";
    712        try
    713        {
    714          return reportReturn(methodCall, realStatement.getFetchSize());
    715        }
    716        catch (SQLException s)
    717        {
    718          reportException(methodCall, s);
    719          throw s;
    720        }
    721      }
    722
    723      public int getResultSetConcurrency() throws SQLException
    724      {
    725        String methodCall = "getResultSetConcurrency()";
    726        try
    727        {
    728          return reportReturn(methodCall, realStatement.getResultSetConcurrency());
    729        }
    730        catch (SQLException s)
    731        {
    732          reportException(methodCall, s);
    733          throw s;
    734        }
    735      }
    736
    737      public int getResultSetHoldability() throws SQLException
    738      {
    739        String methodCall = "getResultSetHoldability()";
    740        try
    741        {
    742          return reportReturn(methodCall, realStatement.getResultSetHoldability());
    743        }
    744        catch (SQLException s)
    745        {
    746          reportException(methodCall, s);
    747          throw s;
    748        }
    749      }
    750
    751      public boolean isClosed() throws SQLException {
    752        String methodCall = "isClosed()";
    753        try
    754        {
    755          return reportReturn(methodCall, realStatement.isClosed());
    756        }
    757        catch (SQLException s)
    758        {
    759          reportException(methodCall, s);
    760          throw s;
    761        }
    762      }
    763
    764      public void setPoolable(boolean poolable) throws SQLException {
    765        String methodCall = "setPoolable(" + poolable + ")";
    766        try
    767        {
    768          realStatement.setPoolable(poolable);
    769        }
    770        catch (SQLException s)
    771        {
    772          reportException(methodCall, s);
    773          throw s;
    774        }
    775        reportReturn(methodCall);
    776      }
    777
    778      public boolean isPoolable() throws SQLException {
    779        String methodCall = "isPoolable()";
    780        try
    781        {
    782          return reportReturn(methodCall, realStatement.isPoolable());
    783        }
    784        catch (SQLException s)
    785        {
    786          reportException(methodCall, s);
    787          throw s;
    788        }
    789      }
    790
    791      public void setMaxFieldSize(int max) throws SQLException
    792      {
    793        String methodCall = "setMaxFieldSize(" + max + ")";
    794        try
    795        {
    796          realStatement.setMaxFieldSize(max);
    797        }
    798        catch (SQLException s)
    799        {
    800          reportException(methodCall, s);
    801          throw s;
    802        }
    803        reportReturn(methodCall);
    804      }
    805
    806      public boolean execute(String sql) throws SQLException
    807      {
    808        String methodCall = "execute(" + sql + ")";
    809        reportStatementSql(sql, methodCall);
    810        long tstart = System.currentTimeMillis();
    811        try
    812        {
    813          boolean result = realStatement.execute(sql);
    814          reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall);
    815          return reportReturn(methodCall, result);
    816        }
    817        catch (SQLException s)
    818        {
    819          reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
    820          throw s;
    821        }
    822      }
    823
    824      public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException
    825      {
    826        String methodCall = "executeUpdate(" + sql + ", " + autoGeneratedKeys + ")";
    827        reportStatementSql(sql, methodCall);
    828        long tstart = System.currentTimeMillis();
    829        try
    830        {
    831          int result = realStatement.executeUpdate(sql, autoGeneratedKeys);
    832          reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall);
    833          return reportReturn(methodCall, result);
    834        }
    835        catch (SQLException s)
    836        {
    837          reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
    838          throw s;
    839        }
    840      }
    841
    842      public boolean execute(String sql, int autoGeneratedKeys) throws SQLException
    843      {
    844        String methodCall = "execute(" + sql + ", " + autoGeneratedKeys + ")";
    845        reportStatementSql(sql, methodCall);
    846        long tstart = System.currentTimeMillis();
    847        try
    848        {
    849          boolean result = realStatement.execute(sql, autoGeneratedKeys);
    850          reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall);
    851          return reportReturn(methodCall, result);
    852        }
    853        catch (SQLException s)
    854        {
    855          reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
    856          throw s;
    857        }
    858      }
    859
    860      public int executeUpdate(String sql, int[] columnIndexes) throws SQLException
    861      {
    862        String methodCall = "executeUpdate(" + sql + ", " + columnIndexes + ")";
    863        reportStatementSql(sql, methodCall);
    864        long tstart = System.currentTimeMillis();
    865        try
    866        {
    867          int result = realStatement.executeUpdate(sql, columnIndexes);
    868          reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall);
    869          return reportReturn(methodCall, result);
    870        }
    871        catch (SQLException s)
    872        {
    873          reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
    874          throw s;
    875        }
    876      }
    877
    878      public boolean execute(String sql, int[] columnIndexes) throws SQLException
    879      {
    880        String methodCall = "execute(" + sql + ", " + columnIndexes + ")";
    881        reportStatementSql(sql, methodCall);
    882        long tstart = System.currentTimeMillis();
    883        try
    884        {
    885          boolean result = realStatement.execute(sql, columnIndexes);
    886          reportStatementSqlTiming(System.currentTimeMillis() - tstart, sql, methodCall);
    887          return reportReturn(methodCall, result);
    888        }
    889        catch (SQLException s)
    890        {
    891          reportException(methodCall, s, sql, System.currentTimeMillis() - tstart);
    892          throw s;
    893        }
    894      }
    895
    896      public ResultSet getResultSet() throws SQLException
    897      {
    898        String methodCall = "getResultSet()";
    899        try
    900        {
    901          ResultSet r = realStatement.getResultSet();
    902          if (r == null)
    903          {
    904            return (ResultSet) reportReturn(methodCall, r);
    905          }
    906          else
    907          {
    908            return (ResultSet) reportReturn(methodCall, new ResultSetSpy(this, r));
    909          }
    910        }
    911        catch (SQLException s)
    912        {
    913          reportException(methodCall, s);
    914          throw s;
    915        }
    916      }
    917
    918      public int getMaxRows() throws SQLException
    919      {
    920        String methodCall = "getMaxRows()";
    921        try
    922        {
    923          return reportReturn(methodCall, realStatement.getMaxRows());
    924        }
    925        catch (SQLException s)
    926        {
    927          reportException(methodCall, s);
    928          throw s;
    929        }
    930      }
    931
    932      public void close() throws SQLException
    933      {
    934        String methodCall = "close()";
    935        try
    936        {
    937          realStatement.close();
    938        }
    939        catch (SQLException s)
    940        {
    941          reportException(methodCall, s);
    942          throw s;
    943        }
    944        reportReturn(methodCall);
    945      }
    946
    947      public int getUpdateCount() throws SQLException
    948      {
    949        String methodCall = "getUpdateCount()";
    950        try
    951        {
    952          return reportReturn(methodCall, realStatement.getUpdateCount());
    953        }
    954        catch (SQLException s)
    955        {
    956          reportException(methodCall, s);
    957          throw s;
    958        }
    959      }
    960
    961      public <T> T unwrap(Class<T> iface) throws SQLException {
    962        String methodCall = "unwrap(" + (iface==null?"null":iface.getName()) + ")";
    963        try
    964        {
    965          //todo: double check this logic
    966          return (T)reportReturn(methodCall, (iface != null && (iface == Connection.class || iface == Spy.class))?(T)this:realStatement.unwrap(iface));
    967        }
    968        catch (SQLException s)
    969        {
    970          reportException(methodCall,s);
    971          throw s;
    972        }
    973      }
    974
    975      public boolean isWrapperFor(Class<?> iface) throws SQLException
    976      {
    977        String methodCall = "isWrapperFor(" + (iface==null?"null":iface.getName()) + ")";
    978        try
    979        {
    980          return reportReturn(methodCall, (iface != null && (iface == Statement.class || iface == Spy.class)) ||
    981              realStatement.isWrapperFor(iface));
    982        }
    983        catch (SQLException s)
    984        {
    985          reportException(methodCall,s);
    986          throw s;
    987        }
    988      }
    989
    990    }