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    }