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