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