KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > mysql > jdbc > Statement


1 /*
2    Copyright (C) 2002 MySQL AB
3
4       This program is free software; you can redistribute it and/or modify
5       it under the terms of the GNU General Public License as published by
6       the Free Software Foundation; either version 2 of the License, or
7       (at your option) any later version.
8
9       This program is distributed in the hope that it will be useful,
10       but WITHOUT ANY WARRANTY; without even the implied warranty of
11       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12       GNU General Public License for more details.
13
14       You should have received a copy of the GNU General Public License
15       along with this program; if not, write to the Free Software
16       Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18  */

19 package com.mysql.jdbc;
20
21 import java.io.UnsupportedEncodingException JavaDoc;
22
23 import java.sql.SQLException JavaDoc;
24 import java.sql.SQLWarning JavaDoc;
25 import java.sql.Types JavaDoc;
26
27 import java.util.ArrayList JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.List JavaDoc;
30
31
32 /**
33  * A Statement object is used for executing a static SQL statement and
34  * obtaining the results produced by it.
35  *
36  * <p>
37  * Only one ResultSet per Statement can be open at any point in time.
38  * Therefore, if the reading of one ResultSet is interleaved with the reading
39  * of another, each must have been generated by different Statements. All
40  * statement execute methods implicitly close a statement's current ResultSet
41  * if an open one exists.
42  * </p>
43  *
44  * @author Mark Matthews
45  * @version $Id: Statement.java,v 1.20.2.17 2004/02/13 22:31:25 mmatthew Exp $
46  *
47  * @see java.sql.Statement
48  * @see ResultSet
49  */

50 public class Statement implements java.sql.Statement JavaDoc {
51     /** The connection that created us */
52     protected Connection connection = null;
53
54     /** Holds batched commands */
55     protected List JavaDoc batchedArgs;
56
57     /** List of currently-open ResultSets */
58     protected List JavaDoc openResults = new ArrayList JavaDoc();
59
60     /** The next result set */
61     protected ResultSet nextResults = null;
62
63     /** The current results */
64     protected ResultSet results = null;
65
66     /** The warnings chain. */
67     protected SQLWarning JavaDoc warningChain = null;
68     
69     /** The pending warnings chain */
70     protected SQLWarning JavaDoc pendingWarnings = null;
71
72     /** The character converter to use (if available) */
73     protected SingleByteCharsetConverter charConverter = null;
74
75     /** The character encoding to use (if available) */
76     protected String JavaDoc charEncoding = null;
77
78     /** The catalog in use */
79     protected String JavaDoc currentCatalog = null;
80
81     /** Should we process escape codes? */
82     protected boolean doEscapeProcessing = true;
83
84     /** Has this statement been closed? */
85     protected boolean isClosed = false;
86
87     /** Has someone changed this for this statement? */
88     protected boolean maxRowsChanged = false;
89
90     /** Are we in pedantic mode? */
91     protected boolean pedantic = false;
92
93     /** The max field size for this statement */
94     protected int maxFieldSize = MysqlIO.getMaxBuf();
95
96     /**
97      * The maximum number of rows to return for this statement (-1 means _all_
98      * rows)
99      */

100     protected int maxRows = -1;
101
102     /** The concurrency for this result set (updatable or not) */
103     protected int resultSetConcurrency = 0;
104
105     /** The type of this result set (scroll sensitive or in-sensitive) */
106     protected int resultSetType = 0;
107
108     /** The timeout for a query */
109     protected int timeout = 0;
110
111     /** The auto_increment value for the last insert */
112     protected long lastInsertId = -1;
113
114     /** The update count for this statement */
115     protected long updateCount = -1;
116
117     /** The number of rows to fetch at a time (currently ignored) */
118     private int fetchSize = 0;
119
120     /** Does the server support CAST/CONVERT? */
121     private boolean serverSupportsConvertFn;
122     
123     /**
124      * Constructor for a Statement.
125      *
126      * @param c the Connection instantation that creates us
127      * @param catalog the database name in use when we were created
128      *
129      * @throws SQLException if an error occurs.
130      */

131     public Statement(Connection c, String JavaDoc catalog) throws SQLException JavaDoc {
132         if (Driver.TRACE) {
133             Object JavaDoc[] args = { c };
134             Debug.methodCall(this, "constructor", args);
135         }
136
137         if ((c == null) || ((com.mysql.jdbc.Connection) c).isClosed()) {
138             throw new SQLException JavaDoc("Connection is closed.", "08003");
139         }
140
141         this.connection = c;
142         this.currentCatalog = catalog;
143         this.pedantic = this.connection.isPedantic();
144         this.serverSupportsConvertFn = this.connection.getIO().versionMeetsMinimum(4, 0, 2);
145
146         //
147
// Adjust, if we know it
148
//
149
if (connection != null) {
150             maxFieldSize = connection.getMaxAllowedPacket();
151         }
152
153         if (this.connection.useUnicode()) {
154             this.charEncoding = connection.getEncoding();
155             this.charConverter = this.connection.getCharsetConverter(this.charEncoding);
156         }
157         
158         int maxRowsConn = this.connection.getMaxRows();
159         
160         if (maxRowsConn != -1) {
161             setMaxRows(maxRowsConn);
162         }
163     }
164
165     /**
166      * JDBC 2.0 Return the Connection that produced the Statement.
167      *
168      * @return the Connection that produced the Statement
169      *
170      * @throws SQLException if an error occurs
171      */

172     public java.sql.Connection JavaDoc getConnection() throws SQLException JavaDoc {
173         return (java.sql.Connection JavaDoc) connection;
174     }
175
176     /**
177      * setCursorName defines the SQL cursor name that will be used by
178      * subsequent execute methods. This name can then be used in SQL
179      * positioned update/delete statements to identify the current row in the
180      * ResultSet generated by this statement. If a database doesn't support
181      * positioned update/delete, this method is a no-op.
182      *
183      * <p>
184      * <b>Note:</b> This MySQL driver does not support cursors.
185      * </p>
186      *
187      * @param name the new cursor name
188      *
189      * @exception java.sql.SQLException if a database access error occurs
190      */

191     public void setCursorName(String JavaDoc name) throws java.sql.SQLException JavaDoc {
192         if (Driver.TRACE) {
193             Object JavaDoc[] args = { name };
194             Debug.methodCall(this, "setCursorName", args);
195         }
196
197         // No-op
198
}
199
200     /**
201      * If escape scanning is on (the default), the driver will do escape
202      * substitution before sending the SQL to the database.
203      *
204      * @param enable true to enable; false to disable
205      *
206      * @exception java.sql.SQLException if a database access error occurs
207      */

208     public void setEscapeProcessing(boolean enable)
209         throws java.sql.SQLException JavaDoc {
210         if (Driver.TRACE) {
211             Object JavaDoc[] args = { new Boolean JavaDoc(enable) };
212             Debug.methodCall(this, "setEscapeProcessing", args);
213         }
214
215         doEscapeProcessing = enable;
216     }
217
218     //--------------------------JDBC 2.0-----------------------------
219

220     /**
221      * JDBC 2.0 Give a hint as to the direction in which the rows in a result
222      * set will be processed. The hint applies only to result sets created
223      * using this Statement object. The default value is
224      * ResultSet.FETCH_FORWARD.
225      *
226      * @param direction the initial direction for processing rows
227      *
228      * @exception SQLException if a database-access error occurs or direction
229      * is not one of ResultSet.FETCH_FORWARD,
230      * ResultSet.FETCH_REVERSE, or ResultSet.FETCH_UNKNOWN
231      */

232     public void setFetchDirection(int direction) throws SQLException JavaDoc {
233         switch (direction) {
234         case java.sql.ResultSet.FETCH_FORWARD:
235         case java.sql.ResultSet.FETCH_REVERSE:
236         case java.sql.ResultSet.FETCH_UNKNOWN:
237             break;
238
239         default:
240             throw new SQLException JavaDoc("Illegal value for setFetchDirection()",
241                 SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
242         }
243     }
244
245     /**
246      * JDBC 2.0 Determine the fetch direction.
247      *
248      * @return the default fetch direction
249      *
250      * @exception SQLException if a database-access error occurs
251      */

252     public int getFetchDirection() throws SQLException JavaDoc {
253         return java.sql.ResultSet.FETCH_FORWARD;
254     }
255
256     /**
257      * JDBC 2.0 Give the JDBC driver a hint as to the number of rows that
258      * should be fetched from the database when more rows are needed. The
259      * number of rows specified only affects result sets created using this
260      * statement. If the value specified is zero, then the hint is ignored.
261      * The default value is zero.
262      *
263      * @param rows the number of rows to fetch
264      *
265      * @exception SQLException if a database-access error occurs, or the
266      * condition 0 &lt;= rows &lt;= this.getMaxRows() is not
267      * satisfied.
268      */

269     public void setFetchSize(int rows) throws SQLException JavaDoc {
270         if (((rows < 0) && (rows != Integer.MIN_VALUE))
271                 || ((maxRows != 0) && (maxRows != -1)
272                 && (rows > this.getMaxRows()))) {
273             throw new SQLException JavaDoc("Illegal value for setFetchSize()", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
274         }
275
276         fetchSize = rows;
277     }
278
279     /**
280      * JDBC 2.0 Determine the default fetch size.
281      *
282      * @return the number of rows to fetch at a time
283      *
284      * @throws SQLException if an error occurs
285      */

286     public int getFetchSize() throws SQLException JavaDoc {
287         return fetchSize;
288     }
289
290     /**
291      * DOCUMENT ME!
292      *
293      * @return DOCUMENT ME!
294      *
295      * @throws SQLException DOCUMENT ME!
296      */

297     public java.sql.ResultSet JavaDoc getGeneratedKeys() throws SQLException JavaDoc {
298         Field[] fields = new Field[1];
299         fields[0] = new Field("", "GENERATED_KEY", Types.BIGINT, 17);
300
301         ArrayList JavaDoc rowSet = new ArrayList JavaDoc();
302
303         long beginAt = getLastInsertID();
304         int numKeys = getUpdateCount();
305
306         String JavaDoc serverInfo = this.results.getServerInfo();
307
308         //
309
// Only parse server info messages for 'REPLACE'
310
// queries
311
//
312

313         if ((numKeys > 0)
314                 && this.results.getFirstCharOfQuery() == 'R'
315                 && (serverInfo != null)
316                 && (serverInfo.length() > 0)) {
317             numKeys = getRecordCountFromInfo(serverInfo);
318         }
319
320         if ((beginAt > 0) && (numKeys > 0)) {
321             for (int i = 0; i < numKeys; i++) {
322                 byte[][] row = new byte[1][];
323                 row[0] = Long.toString(beginAt++).getBytes();
324                 rowSet.add(row);
325             }
326         }
327
328         return new com.mysql.jdbc.ResultSet(currentCatalog, fields,
329             new RowDataStatic(rowSet), connection);
330     }
331
332     /**
333      * getLastInsertID returns the value of the auto_incremented key after an
334      * executeQuery() or excute() call.
335      *
336      * <p>
337      * This gets around the un-threadsafe behavior of "select LAST_INSERT_ID()"
338      * which is tied to the Connection that created this Statement, and
339      * therefore could have had many INSERTS performed before one gets a
340      * chance to call "select LAST_INSERT_ID()".
341      * </p>
342      *
343      * @return the last update ID.
344      */

345     public long getLastInsertID() {
346         if (Driver.TRACE) {
347             Object JavaDoc[] args = new Object JavaDoc[0];
348             Debug.methodCall(this, "getLastInsertID", args);
349         }
350
351         return lastInsertId;
352     }
353
354     /**
355      * getLongUpdateCount returns the current result as an update count, if the
356      * result is a ResultSet or there are no more results, -1 is returned. It
357      * should only be called once per result.
358      *
359      * <p>
360      * This method returns longs as MySQL server versions newer than 3.22.4
361      * return 64-bit values for update counts
362      * </p>
363      *
364      * @return the current update count.
365      */

366     public long getLongUpdateCount() {
367         if (Driver.TRACE) {
368             Object JavaDoc[] args = new Object JavaDoc[0];
369             Debug.methodCall(this, "getLongUpdateCount", args);
370         }
371
372         if (results == null) {
373             return -1;
374         }
375
376         if (results.reallyResult()) {
377             return -1;
378         }
379
380         return updateCount;
381     }
382
383     /**
384      * Sets the maxFieldSize
385      *
386      * @param max the new max column size limit; zero means unlimited
387      *
388      * @exception SQLException if size exceeds buffer size
389      * @throws SQLException DOCUMENT ME!
390      */

391     public void setMaxFieldSize(int max) throws SQLException JavaDoc {
392         if (Driver.TRACE) {
393             Object JavaDoc[] args = { new Integer JavaDoc(max) };
394             Debug.methodCall(this, "setMaxFieldSize", args);
395         }
396
397         if (max < 0) {
398             throw new SQLException JavaDoc("Illegal value for setMaxFieldSize()",
399                 SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
400         }
401
402         int maxBuf = (connection != null) ? connection.getMaxAllowedPacket()
403                                           : MysqlIO.getMaxBuf();
404
405         if (max > maxBuf) {
406             throw new java.sql.SQLException JavaDoc(
407                 "Can not set max field size > max allowed packet: " + maxBuf,
408                 SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
409         } else {
410             maxFieldSize = max;
411         }
412     }
413
414     /**
415      * The maxFieldSize limit (in bytes) is the maximum amount of data returned
416      * for any column value; it only applies to BINARY, VARBINARY,
417      * LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR columns. If the limit is
418      * exceeded, the excess data is silently discarded.
419      *
420      * @return the current max column size limit; zero means unlimited
421      *
422      * @exception java.sql.SQLException if a database access error occurs
423      */

424     public int getMaxFieldSize() throws java.sql.SQLException JavaDoc {
425         if (Driver.TRACE) {
426             Object JavaDoc[] args = new Object JavaDoc[0];
427             Debug.methodCall(this, "getMaxFieldSize", args);
428         }
429
430         return maxFieldSize;
431     }
432
433     /**
434      * Set the maximum number of rows
435      *
436      * @param max the new max rows limit; zero means unlimited
437      *
438      * @exception java.sql.SQLException if a database access error occurs
439      *
440      * @see getMaxRows
441      */

442     public void setMaxRows(int max) throws java.sql.SQLException JavaDoc {
443         if (Driver.TRACE) {
444             Object JavaDoc[] args = { new Integer JavaDoc(max) };
445             Debug.methodCall(this, "setMaxRows", args);
446         }
447
448         if ((max > MysqlDefs.MAX_ROWS) || (max < 0)) {
449             throw new java.sql.SQLException JavaDoc("setMaxRows() out of range. " + max
450                 + " > " + MysqlDefs.MAX_ROWS + ".", SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
451         }
452
453         if (max == 0) {
454             max = -1;
455         }
456
457         this.maxRows = max;
458         this.maxRowsChanged = true;
459
460         if (maxRows == -1) {
461             connection.unsetMaxRows(this);
462             this.maxRowsChanged = false;
463         } else {
464             // Most people don't use setMaxRows()
465
// so don't penalize them
466
// with the extra query it takes
467
// to do it efficiently unless we need
468
// to.
469
connection.maxRowsChanged(this);
470         }
471     }
472
473     /**
474      * The maxRows limit is set to limit the number of rows that any ResultSet
475      * can contain. If the limit is exceeded, the excess rows are silently
476      * dropped.
477      *
478      * @return the current maximum row limit; zero means unlimited
479      *
480      * @exception java.sql.SQLException if a database access error occurs
481      */

482     public int getMaxRows() throws java.sql.SQLException JavaDoc {
483         if (Driver.TRACE) {
484             Object JavaDoc[] args = new Object JavaDoc[0];
485             Debug.methodCall(this, "getMaxRows", args);
486         }
487
488         if (maxRows <= 0) {
489             return 0;
490         } else {
491             return maxRows;
492         }
493     }
494
495     /**
496      * getMoreResults moves to a Statement's next result. If it returns true,
497      * this result is a ResulSet.
498      *
499      * @return true if the next ResultSet is valid
500      *
501      * @exception java.sql.SQLException if a database access error occurs
502      */

503     public boolean getMoreResults() throws java.sql.SQLException JavaDoc {
504         if (Driver.TRACE) {
505             Object JavaDoc[] args = new Object JavaDoc[0];
506             Debug.methodCall(this, "getMoreResults", args);
507         }
508
509         return getMoreResults(CLOSE_CURRENT_RESULT);
510     }
511
512     /**
513      * @see Statement#getMoreResults(int)
514      */

515     public synchronized boolean getMoreResults(int current)
516         throws SQLException JavaDoc {
517         switch (current) {
518         case Statement.CLOSE_CURRENT_RESULT:
519
520             if (results != null) {
521                 results.close();
522             }
523
524             break;
525
526         case Statement.CLOSE_ALL_RESULTS:
527
528             if (results != null) {
529                 results.close();
530             }
531
532             closeAllOpenResults();
533
534             break;
535
536         case Statement.KEEP_CURRENT_RESULT:
537             openResults.add(results);
538
539             break;
540
541         default:
542             throw new SQLException JavaDoc("Illegal flag for getMoreResults(int)",
543                 SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
544         }
545
546         results = nextResults;
547
548         nextResults = null;
549
550         return ((results != null) && results.reallyResult()) ? true : false;
551     }
552
553     /**
554      * Sets the queryTimeout limit
555      *
556      * @param seconds - the new query timeout limit in seconds
557      *
558      * @exception SQLException if a database access error occurs
559      */

560     public void setQueryTimeout(int seconds) throws SQLException JavaDoc {
561         if (Driver.TRACE) {
562             Object JavaDoc[] args = { new Integer JavaDoc(seconds) };
563             Debug.methodCall(this, "setQueryTimeout", args);
564         }
565
566         if (seconds < 0) {
567             throw new SQLException JavaDoc("Illegal value for setQueryTimeout()",
568                 SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
569         }
570
571         timeout = seconds;
572     }
573
574     /**
575      * The queryTimeout limit is the number of seconds the driver will wait for
576      * a Statement to execute. If the limit is exceeded, a
577      * java.sql.SQLException is thrown.
578      *
579      * @return the current query timeout limit in seconds; 0 = unlimited
580      *
581      * @exception java.sql.SQLException if a database access error occurs
582      */

583     public int getQueryTimeout() throws java.sql.SQLException JavaDoc {
584         if (Driver.TRACE) {
585             Object JavaDoc[] args = new Object JavaDoc[0];
586             Debug.methodCall(this, "getQueryTimeout", args);
587         }
588
589         return timeout;
590     }
591
592     /**
593      * getResultSet returns the current result as a ResultSet. It should only
594      * be called once per result.
595      *
596      * @return the current result set; null if there are no more
597      *
598      * @exception java.sql.SQLException if a database access error occurs
599      * (why?)
600      */

601     public synchronized java.sql.ResultSet JavaDoc getResultSet()
602         throws java.sql.SQLException JavaDoc {
603         if (Driver.TRACE) {
604             Object JavaDoc[] args = new Object JavaDoc[0];
605             Debug.methodCall(this, "getResultSet", args);
606         }
607
608         return ((results != null) && results.reallyResult())
609         ? (java.sql.ResultSet JavaDoc) results : null;
610     }
611
612     /**
613      * JDBC 2.0 Determine the result set concurrency.
614      *
615      * @return CONCUR_UPDATABLE or CONCUR_READONLY
616      *
617      * @throws SQLException if an error occurs
618      */

619     public int getResultSetConcurrency() throws SQLException JavaDoc {
620         return resultSetConcurrency;
621     }
622
623     /**
624      * @see Statement#getResultSetHoldability()
625      */

626     public int getResultSetHoldability() throws SQLException JavaDoc {
627         return ResultSet.HOLD_CURSORS_OVER_COMMIT;
628     }
629
630     /**
631      * JDBC 2.0 Determine the result set type.
632      *
633      * @return the ResultSet type (SCROLL_SENSITIVE or SCROLL_INSENSITIVE)
634      *
635      * @throws SQLException if an error occurs.
636      */

637     public int getResultSetType() throws SQLException JavaDoc {
638         return resultSetType;
639     }
640
641     /**
642      * getUpdateCount returns the current result as an update count, if the
643      * result is a ResultSet or there are no more results, -1 is returned. It
644      * should only be called once per result.
645      *
646      * @return the current result as an update count.
647      *
648      * @exception java.sql.SQLException if a database access error occurs
649      */

650     public synchronized int getUpdateCount() throws java.sql.SQLException JavaDoc {
651         if (Driver.TRACE) {
652             Object JavaDoc[] args = new Object JavaDoc[0];
653             Debug.methodCall(this, "getUpdateCount", args);
654         }
655
656         if (results == null) {
657             return -1;
658         }
659
660         if (results.reallyResult()) {
661             return -1;
662         }
663
664         int truncatedUpdateCount = 0;
665
666         if (results.getUpdateCount() > Integer.MAX_VALUE) {
667             truncatedUpdateCount = Integer.MAX_VALUE;
668         } else {
669             truncatedUpdateCount = (int) results.getUpdateCount();
670         }
671
672         return truncatedUpdateCount;
673     }
674
675     /**
676      * The first warning reported by calls on this Statement is returned. A
677      * Statement's execute methods clear its java.sql.SQLWarning chain.
678      * Subsequent Statement warnings will be chained to this
679      * java.sql.SQLWarning.
680      *
681      * <p>
682      * The Warning chain is automatically cleared each time a statement is
683      * (re)executed.
684      * </p>
685      *
686      * <p>
687      * <B>Note:</B> If you are processing a ResultSet then any warnings
688      * associated with ResultSet reads will be chained on the ResultSet
689      * object.
690      * </p>
691      *
692      * @return the first java.sql.SQLWarning on null
693      *
694      * @exception java.sql.SQLException if a database access error occurs
695      */

696     public synchronized java.sql.SQLWarning JavaDoc getWarnings()
697         throws java.sql.SQLException JavaDoc {
698         if (Driver.TRACE) {
699             Object JavaDoc[] args = new Object JavaDoc[0];
700             Debug.methodCall(this, "getWarnings", args);
701         }
702
703         return warningChain;
704     }
705
706     /**
707      * DOCUMENT ME!
708      *
709      * @param sql DOCUMENT ME!
710      *
711      * @throws SQLException DOCUMENT ME!
712      */

713     public synchronized void addBatch(String JavaDoc sql) throws SQLException JavaDoc {
714         if (batchedArgs == null) {
715             batchedArgs = new ArrayList JavaDoc();
716         }
717
718         if (sql != null) {
719             batchedArgs.add(sql);
720         }
721     }
722
723     /**
724      * Cancel can be used by one thread to cancel a statement that is being
725      * executed by another thread. However this driver is synchronous, so
726      * this really has no meaning - we define it as a no-op (i.e. you can't
727      * cancel, but there is no error if you try.)
728      *
729      * @exception java.sql.SQLException only because thats the spec.
730      */

731     public void cancel() throws java.sql.SQLException JavaDoc {
732         if (Driver.TRACE) {
733             Object JavaDoc[] args = new Object JavaDoc[0];
734             Debug.methodCall(this, "cancel", args);
735         }
736
737         // No-op
738
}
739
740     /**
741      * JDBC 2.0 Make the set of commands in the current batch empty. This
742      * method is optional.
743      *
744      * @exception SQLException if a database-access error occurs, or the driver
745      * does not support batch statements
746      */

747     public synchronized void clearBatch() throws SQLException JavaDoc {
748         if (batchedArgs != null) {
749             batchedArgs.clear();
750         }
751     }
752
753     /**
754      * After this call, getWarnings returns null until a new warning is
755      * reported for this Statement.
756      *
757      * @exception java.sql.SQLException if a database access error occurs
758      * (why?)
759      */

760     public synchronized void clearWarnings() throws java.sql.SQLException JavaDoc {
761         if (Driver.TRACE) {
762             Object JavaDoc[] args = new Object JavaDoc[0];
763             Debug.methodCall(this, "clearWarnings", args);
764         }
765
766         this.warningChain = this.pendingWarnings;
767         this.pendingWarnings = null;
768     }
769
770     /**
771      * In many cases, it is desirable to immediately release a Statement's
772      * database and JDBC resources instead of waiting for this to happen when
773      * it is automatically closed. The close method provides this immediate
774      * release.
775      *
776      * <p>
777      * <B>Note:</B> A Statement is automatically closed when it is garbage
778      * collected. When a Statement is closed, its current ResultSet, if one
779      * exists, is also closed.
780      * </p>
781      *
782      * @exception java.sql.SQLException if a database access error occurs
783      */

784     public synchronized void close() throws java.sql.SQLException JavaDoc {
785         if (Driver.TRACE) {
786             Object JavaDoc[] args = new Object JavaDoc[0];
787             Debug.methodCall(this, "close", args);
788         }
789
790         if (this.isClosed) {
791             return;
792         }
793         
794         if (results != null) {
795             try {
796                 results.close();
797             } catch (Exception JavaDoc ex) {
798                 ;
799             }
800         }
801
802         if (this.maxRowsChanged && this.connection != null) {
803             this.connection.unsetMaxRows(this);
804         }
805
806         this.results = null;
807         this.connection = null;
808         this.warningChain = null;
809         this.isClosed = true;
810         this.closeAllOpenResults();
811         this.openResults = null;
812     }
813
814     /**
815      * Execute a SQL statement that may return multiple results. We don't have
816      * to worry about this since we do not support multiple ResultSets. You
817      * can use getResultSet or getUpdateCount to retrieve the result.
818      *
819      * @param sql any SQL statement
820      *
821      * @return true if the next result is a ResulSet, false if it is an update
822      * count or there are no more results
823      *
824      * @exception SQLException if a database access error occurs
825      */

826     public synchronized boolean execute(String JavaDoc sql) throws SQLException JavaDoc {
827         if (Driver.TRACE) {
828             Object JavaDoc[] args = { sql };
829             Debug.methodCall(this, "execute", args);
830         }
831
832         char firstNonWsChar = StringUtils.firstNonWsCharUc(sql);
833
834         if (connection.isReadOnly()) {
835             if (firstNonWsChar != 'S') {
836                 throw new SQLException JavaDoc("Connection is read-only. "
837                     + "Queries leading to data modification are not allowed",
838                     SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
839             }
840         }
841
842         checkClosed();
843
844         if (this.doEscapeProcessing) {
845             sql = EscapeProcessor.escapeSQL(sql, this.serverSupportsConvertFn);
846         }
847
848         if (results != null) {
849             results.close();
850         }
851
852         ResultSet rs = null;
853
854         // If there isn't a limit clause in the SQL
855
// then limit the number of rows to return in
856
// an efficient manner. Only do this if
857
// setMaxRows() hasn't been used on any Statements
858
// generated from the current Connection (saves
859
// a query, and network traffic).
860
synchronized (connection.getMutex()) {
861             clearWarnings();
862             
863             String JavaDoc oldCatalog = null;
864
865             if (!connection.getCatalog().equals(currentCatalog)) {
866                 oldCatalog = connection.getCatalog();
867                 connection.setCatalog(currentCatalog);
868             }
869
870             boolean isSelect = (firstNonWsChar == 'S');
871
872             //
873
// Only apply max_rows to selects
874
//
875
if (connection.useMaxRows()) {
876                 int rowLimit = -1;
877                 
878                 if (isSelect) {
879                     if (sql.toUpperCase().indexOf("LIMIT") != -1) {
880                        rowLimit = this.maxRows;
881                     } else {
882                         if (maxRows <= 0) {
883                             connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT",
884                                 -1, this.currentCatalog);
885                         } else {
886                             connection.execSQL("SET OPTION SQL_SELECT_LIMIT="
887                                 + maxRows, -1, this.currentCatalog);
888                         }
889                     }
890                 } else {
891                     connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT",
892                         -1, this.currentCatalog);
893                 }
894
895                 // Finally, execute the query
896
rs = connection.execSQL(sql, rowLimit, resultSetConcurrency,
897                         createStreamingResultSet(), isSelect,
898                         this.currentCatalog);
899             } else {
900                 rs = connection.execSQL(sql, -1, resultSetConcurrency,
901                         createStreamingResultSet(), isSelect,
902                         this.currentCatalog);
903             }
904
905             if (oldCatalog != null) {
906                 connection.setCatalog(oldCatalog);
907             }
908         }
909
910         lastInsertId = rs.getUpdateID();
911
912         if (rs != null) {
913             this.results = rs;
914         }
915
916         rs.setFirstCharOfQuery(firstNonWsChar);
917         rs.setConnection(connection);
918         rs.setResultSetType(resultSetType);
919         rs.setResultSetConcurrency(resultSetConcurrency);
920
921         return ((rs != null) && rs.reallyResult());
922     }
923
924     /**
925      * @see Statement#execute(String, int)
926      */

927     public boolean execute(String JavaDoc sql, int returnGeneratedKeys)
928         throws SQLException JavaDoc {
929         if (returnGeneratedKeys == Statement.RETURN_GENERATED_KEYS) {
930             checkClosed();
931
932             synchronized (this.connection.getMutex()) {
933                 // If this is a 'REPLACE' query, we need to be able to parse
934
// the 'info' message returned from the server to determine
935
// the actual number of keys generated.
936
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
937                 this.connection.setReadInfoMsgEnabled(true);
938
939                 try {
940                     return execute(sql);
941                 } finally {
942                     this.connection.setReadInfoMsgEnabled(readInfoMsgState);
943                 }
944             }
945         } else {
946             return execute(sql);
947         }
948     }
949
950     /**
951      * @see Statement#execute(String, int[])
952      */

953     public boolean execute(String JavaDoc sql, int[] generatedKeyIndices)
954         throws SQLException JavaDoc {
955         if ((generatedKeyIndices != null) && (generatedKeyIndices.length > 0)) {
956             checkClosed();
957
958             synchronized (this.connection.getMutex()) {
959                 // If this is a 'REPLACE' query, we need to be able to parse
960
// the 'info' message returned from the server to determine
961
// the actual number of keys generated.
962
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
963                 this.connection.setReadInfoMsgEnabled(true);
964
965                 try {
966                     return execute(sql);
967                 } finally {
968                     this.connection.setReadInfoMsgEnabled(readInfoMsgState);
969                 }
970             }
971         } else {
972             return execute(sql);
973         }
974     }
975
976     /**
977      * @see Statement#execute(String, String[])
978      */

979     public boolean execute(String JavaDoc sql, String JavaDoc[] generatedKeyNames)
980         throws SQLException JavaDoc {
981         if ((generatedKeyNames != null) && (generatedKeyNames.length > 0)) {
982             checkClosed();
983
984             synchronized (this.connection.getMutex()) {
985                 // If this is a 'REPLACE' query, we need to be able to parse
986
// the 'info' message returned from the server to determine
987
// the actual number of keys generated.
988
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
989                 this.connection.setReadInfoMsgEnabled(true);
990
991                 try {
992                     return execute(sql);
993                 } finally {
994                     this.connection.setReadInfoMsgEnabled(readInfoMsgState);
995                 }
996             }
997         } else {
998             return execute(sql);
999         }
1000    }
1001
1002    /**
1003     * JDBC 2.0 Submit a batch of commands to the database for execution. This
1004     * method is optional.
1005     *
1006     * @return an array of update counts containing one element for each
1007     * command in the batch. The array is ordered according to the
1008     * order in which commands were inserted into the batch
1009     *
1010     * @exception SQLException if a database-access error occurs, or the driver
1011     * does not support batch statements
1012     */

1013    public synchronized int[] executeBatch() throws SQLException JavaDoc {
1014        if (connection.isReadOnly()) {
1015            throw new SQLException JavaDoc("Connection is read-only. "
1016                + "Queries leading to data modification are not allowed",
1017                SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1018        }
1019
1020        try {
1021            clearWarnings();
1022            
1023            int[] updateCounts = null;
1024
1025            if (batchedArgs != null) {
1026                int nbrCommands = batchedArgs.size();
1027                updateCounts = new int[nbrCommands];
1028
1029                for (int i = 0; i < nbrCommands; i++) {
1030                    updateCounts[i] = -3;
1031                }
1032
1033                SQLException JavaDoc sqlEx = null;
1034
1035                int commandIndex = 0;
1036
1037                for (commandIndex = 0; commandIndex < nbrCommands;
1038                        commandIndex++) {
1039                    try {
1040                        updateCounts[commandIndex] = executeUpdate((String JavaDoc) batchedArgs
1041                                .get(commandIndex), false);
1042                    } catch (SQLException JavaDoc ex) {
1043                        updateCounts[commandIndex] = EXECUTE_FAILED;
1044
1045                        if (this.connection.continueBatchOnError()) {
1046                            sqlEx = ex;
1047                        } else {
1048                            int[] newUpdateCounts = new int[commandIndex];
1049                            System.arraycopy(updateCounts, 0, newUpdateCounts,
1050                                0, commandIndex);
1051
1052                            throw new java.sql.BatchUpdateException JavaDoc(ex
1053                                .getMessage(), ex.getSQLState(),
1054                                ex.getErrorCode(), newUpdateCounts);
1055                        }
1056                    }
1057                }
1058
1059                if (sqlEx != null) {
1060                    throw new java.sql.BatchUpdateException JavaDoc(sqlEx.getMessage(),
1061                        sqlEx.getSQLState(), sqlEx.getErrorCode(), updateCounts);
1062                }
1063            }
1064
1065            return (updateCounts != null) ? updateCounts : new int[0];
1066        } finally {
1067            clearBatch();
1068        }
1069    }
1070
1071    /**
1072     * Execute a SQL statement that retruns a single ResultSet
1073     *
1074     * @param sql typically a static SQL SELECT statement
1075     *
1076     * @return a ResulSet that contains the data produced by the query
1077     *
1078     * @exception SQLException if a database access error occurs
1079     */

1080    public synchronized java.sql.ResultSet JavaDoc executeQuery(String JavaDoc sql)
1081        throws SQLException JavaDoc {
1082        if (Driver.TRACE) {
1083            Object JavaDoc[] args = { sql };
1084            Debug.methodCall(this, "executeQuery", args);
1085        }
1086
1087        checkClosed();
1088
1089        if (this.doEscapeProcessing) {
1090            sql = EscapeProcessor.escapeSQL(sql, this.serverSupportsConvertFn);
1091        }
1092
1093        char firstStatementChar = StringUtils.firstNonWsCharUc(sql);
1094
1095        if ((firstStatementChar == 'I') || (firstStatementChar == 'U')
1096                || (firstStatementChar == 'D') || (firstStatementChar == 'A')
1097                || (firstStatementChar == 'C')) {
1098            if (StringUtils.startsWithIgnoreCaseAndWs(sql, "INSERT")
1099                    || StringUtils.startsWithIgnoreCaseAndWs(sql, "UPDATE")
1100                    || StringUtils.startsWithIgnoreCaseAndWs(sql, "DELETE")
1101                    || StringUtils.startsWithIgnoreCaseAndWs(sql, "DROP")
1102                    || StringUtils.startsWithIgnoreCaseAndWs(sql, "CREATE")
1103                    || StringUtils.startsWithIgnoreCaseAndWs(sql, "ALTER")) {
1104                throw new SQLException JavaDoc("Can not issue data manipulation statements with executeQuery()",
1105                    SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1106            }
1107        }
1108
1109        if (results != null) {
1110            results.close();
1111        }
1112
1113        // If there isn't a limit clause in the SQL
1114
// then limit the number of rows to return in
1115
// an efficient manner. Only do this if
1116
// setMaxRows() hasn't been used on any Statements
1117
// generated from the current Connection (saves
1118
// a query, and network traffic).
1119
synchronized (connection.getMutex()) {
1120            clearWarnings();
1121            
1122            String JavaDoc oldCatalog = null;
1123
1124            if (!connection.getCatalog().equals(currentCatalog)) {
1125                oldCatalog = connection.getCatalog();
1126                connection.setCatalog(currentCatalog);
1127            }
1128
1129            if (connection.useMaxRows()) {
1130                // We need to execute this all together
1131
// So synchronize on the Connection's mutex (because
1132
// even queries going through there synchronize
1133
// on the connection
1134
if (sql.toUpperCase().indexOf("LIMIT") != -1) {
1135                    results = connection.execSQL(sql, maxRows,
1136                            resultSetConcurrency, createStreamingResultSet(),
1137                            true, this.currentCatalog);
1138                } else {
1139                    if (maxRows <= 0) {
1140                        connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT",
1141                            -1, this.currentCatalog);
1142                    } else {
1143                        connection.execSQL("SET OPTION SQL_SELECT_LIMIT="
1144                            + maxRows, -1, this.currentCatalog);
1145                    }
1146
1147                    results = connection.execSQL(sql, -1, resultSetConcurrency,
1148                            createStreamingResultSet(), true,
1149                            this.currentCatalog);
1150
1151                    if (oldCatalog != null) {
1152                        connection.setCatalog(oldCatalog);
1153                    }
1154                }
1155            } else {
1156                results = connection.execSQL(sql, -1, resultSetConcurrency,
1157                        createStreamingResultSet(), true, this.currentCatalog);
1158            }
1159
1160            if (oldCatalog != null) {
1161                connection.setCatalog(oldCatalog);
1162            }
1163        }
1164
1165        lastInsertId = results.getUpdateID();
1166        nextResults = results;
1167        results.setConnection(connection);
1168        results.setResultSetType(resultSetType);
1169        results.setResultSetConcurrency(resultSetConcurrency);
1170        results.setStatement(this);
1171
1172        if (!results.reallyResult()) {
1173            if (!connection.getAutoCommit()) {
1174                connection.rollback();
1175            }
1176
1177            throw new SQLException JavaDoc("Can not issue INSERT/UPDATE/DELETE with executeQuery()",
1178                SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1179        }
1180
1181        return (java.sql.ResultSet JavaDoc) results;
1182    }
1183
1184    /**
1185     * Execute a SQL INSERT, UPDATE or DELETE statement. In addition SQL
1186     * statements that return nothing such as SQL DDL statements can be
1187     * executed Any IDs generated for AUTO_INCREMENT fields can be retrieved
1188     * by casting this Statement to org.gjt.mm.mysql.Statement and calling the
1189     * getLastInsertID() method.
1190     *
1191     * @param sql a SQL statement
1192     *
1193     * @return either a row count, or 0 for SQL commands
1194     *
1195     * @exception SQLException if a database access error occurs
1196     */

1197    public synchronized int executeUpdate(String JavaDoc sql) throws SQLException JavaDoc {
1198        return executeUpdate(sql, true);
1199    }
1200    
1201    private synchronized int executeUpdate(String JavaDoc sql, boolean clearWarnings) throws SQLException JavaDoc {
1202        if (Driver.TRACE) {
1203            Object JavaDoc[] args = { sql };
1204            Debug.methodCall(this, "executeUpdate", args);
1205        }
1206
1207        if (connection.isReadOnly()) {
1208            throw new SQLException JavaDoc("Connection is read-only. "
1209                + "Queries leading to data modification are not allowed",
1210                SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1211        }
1212
1213        char firstStatementChar = StringUtils.firstNonWsCharUc(sql);
1214
1215        if ((firstStatementChar == 'S')
1216                && StringUtils.startsWithIgnoreCaseAndWs(sql, "select")) {
1217            throw new SQLException JavaDoc("Can not issue SELECT via executeUpdate()",
1218                SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1219        }
1220
1221        checkClosed();
1222
1223        if (this.doEscapeProcessing) {
1224            sql = EscapeProcessor.escapeSQL(sql, this.serverSupportsConvertFn);
1225        }
1226
1227        // The checking and changing of catalogs
1228
// must happen in sequence, so synchronize
1229
// on the same mutex that _conn is using
1230
ResultSet rs = null;
1231
1232        synchronized (connection.getMutex()) {
1233            if (clearWarnings) {
1234                clearWarnings();
1235            }
1236            
1237            String JavaDoc oldCatalog = null;
1238
1239            if (!connection.getCatalog().equals(currentCatalog)) {
1240                oldCatalog = connection.getCatalog();
1241                connection.setCatalog(currentCatalog);
1242            }
1243
1244            //
1245
// Only apply max_rows to selects
1246
//
1247
if (connection.useMaxRows()) {
1248                connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT", -1,
1249                    this.currentCatalog);
1250            }
1251
1252            rs = connection.execSQL(sql, -1,
1253                    java.sql.ResultSet.CONCUR_READ_ONLY, false, false,
1254                    this.currentCatalog);
1255            rs.setConnection(connection);
1256
1257            if (oldCatalog != null) {
1258                connection.setCatalog(oldCatalog);
1259            }
1260        }
1261
1262        this.results = rs;
1263
1264        rs.setFirstCharOfQuery(firstStatementChar);
1265        
1266        updateCount = rs.getUpdateCount();
1267
1268        int truncatedUpdateCount = 0;
1269
1270        if (updateCount > Integer.MAX_VALUE) {
1271            truncatedUpdateCount = Integer.MAX_VALUE;
1272        } else {
1273            truncatedUpdateCount = (int) updateCount;
1274        }
1275
1276        lastInsertId = rs.getUpdateID();
1277
1278        return truncatedUpdateCount;
1279    }
1280
1281    /**
1282     * @see Statement#executeUpdate(String, int)
1283     */

1284    public int executeUpdate(String JavaDoc sql, int returnGeneratedKeys)
1285        throws SQLException JavaDoc {
1286        if (returnGeneratedKeys == Statement.RETURN_GENERATED_KEYS) {
1287            checkClosed();
1288
1289            synchronized (this.connection.getMutex()) {
1290                // If this is a 'REPLACE' query, we need to be able to parse
1291
// the 'info' message returned from the server to determine
1292
// the actual number of keys generated.
1293
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
1294                this.connection.setReadInfoMsgEnabled(true);
1295
1296                try {
1297                    return executeUpdate(sql);
1298                } finally {
1299                    this.connection.setReadInfoMsgEnabled(readInfoMsgState);
1300                }
1301            }
1302        } else {
1303            return executeUpdate(sql);
1304        }
1305    }
1306
1307    /**
1308     * @see Statement#executeUpdate(String, int[])
1309     */

1310    public int executeUpdate(String JavaDoc sql, int[] generatedKeyIndices)
1311        throws SQLException JavaDoc {
1312        if ((generatedKeyIndices != null) && (generatedKeyIndices.length > 0)) {
1313            checkClosed();
1314
1315            synchronized (this.connection.getMutex()) {
1316                // If this is a 'REPLACE' query, we need to be able to parse
1317
// the 'info' message returned from the server to determine
1318
// the actual number of keys generated.
1319
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
1320                this.connection.setReadInfoMsgEnabled(true);
1321
1322                try {
1323                    return executeUpdate(sql);
1324                } finally {
1325                    this.connection.setReadInfoMsgEnabled(readInfoMsgState);
1326                }
1327            }
1328        } else {
1329            return executeUpdate(sql);
1330        }
1331    }
1332
1333    /**
1334     * @see Statement#executeUpdate(String, String[])
1335     */

1336    public int executeUpdate(String JavaDoc sql, String JavaDoc[] generatedKeyNames)
1337        throws SQLException JavaDoc {
1338        if ((generatedKeyNames != null) && (generatedKeyNames.length > 0)) {
1339            checkClosed();
1340
1341            synchronized (this.connection.getMutex()) {
1342                // If this is a 'REPLACE' query, we need to be able to parse
1343
// the 'info' message returned from the server to determine
1344
// the actual number of keys generated.
1345
boolean readInfoMsgState = this.connection.isReadInfoMsgEnabled();
1346                this.connection.setReadInfoMsgEnabled(true);
1347
1348                try {
1349                    return executeUpdate(sql);
1350                } finally {
1351                    this.connection.setReadInfoMsgEnabled(readInfoMsgState);
1352                }
1353            }
1354        } else {
1355            return executeUpdate(sql);
1356        }
1357    }
1358
1359    /**
1360     * Checks if closed() has been called, and throws an exception if so
1361     *
1362     * @throws SQLException if this statement has been closed
1363     */

1364    protected void checkClosed() throws SQLException JavaDoc {
1365        if (this.isClosed) {
1366            throw new SQLException JavaDoc("No operations allowed after statement closed",
1367                SQLError.SQL_STATE_GENERAL_ERROR);
1368        }
1369    }
1370
1371    /**
1372     * Close any open result sets that have been 'held open'
1373     */

1374    protected void closeAllOpenResults() {
1375        if (this.openResults != null) {
1376            for (Iterator JavaDoc iter = this.openResults.iterator(); iter.hasNext();) {
1377                ResultSet element = (ResultSet) iter.next();
1378
1379                try {
1380                    element.close();
1381                } catch (SQLException JavaDoc sqlEx) {
1382                    AssertionFailedException.shouldNotHappen(sqlEx);
1383                }
1384            }
1385
1386            this.openResults.clear();
1387        }
1388    }
1389
1390    /**
1391     * We only stream result sets when they are forward-only, read-only, and
1392     * the fetch size has been set to Integer.MIN_VALUE
1393     *
1394     * @return true if this result set should be streamed row at-a-time, rather
1395     * than read all at once.
1396     */

1397    protected boolean createStreamingResultSet() {
1398        return ((resultSetType == java.sql.ResultSet.TYPE_FORWARD_ONLY)
1399        && (resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY)
1400        && (fetchSize == Integer.MIN_VALUE));
1401    }
1402
1403    /**
1404     * Sets the concurrency for result sets generated by this statement
1405     *
1406     * @param concurrencyFlag DOCUMENT ME!
1407     */

1408    void setResultSetConcurrency(int concurrencyFlag) {
1409        resultSetConcurrency = concurrencyFlag;
1410    }
1411
1412    /**
1413     * Sets the result set type for result sets generated by this statement
1414     *
1415     * @param typeFlag DOCUMENT ME!
1416     */

1417    void setResultSetType(int typeFlag) {
1418        resultSetType = typeFlag;
1419    }
1420    
1421    protected void addWarning(SQLWarning JavaDoc warning) {
1422        if (this.pendingWarnings == null) {
1423            this.pendingWarnings = warning;
1424        } else {
1425            // find the 'end'... this could be handled more
1426
// efficiently, but the only thing that currently
1427
// sets 'client-side' warnings is prepared statements
1428
// when clipping +/- INF and NaN.
1429

1430            SQLWarning JavaDoc lastWarning = this.pendingWarnings;
1431            
1432            while (lastWarning.getNextWarning() != null) {
1433                lastWarning = lastWarning.getNextWarning();
1434            }
1435            
1436            lastWarning.setNextWarning(warning);
1437        }
1438        
1439    }
1440
1441    /**
1442     * Parses actual record count from 'info' message
1443     *
1444     * @param serverInfo DOCUMENT ME!
1445     *
1446     * @return DOCUMENT ME!
1447     */

1448    private int getRecordCountFromInfo(String JavaDoc serverInfo) {
1449        StringBuffer JavaDoc recordsBuf = new StringBuffer JavaDoc();
1450        int recordsCount = 0;
1451        int duplicatesCount = 0;
1452
1453        char c = (char) 0;
1454
1455        int length = serverInfo.length();
1456        int i = 0;
1457
1458        for (; i < length; i++) {
1459            c = serverInfo.charAt(i);
1460
1461            if (Character.isDigit(c)) {
1462                break;
1463            }
1464        }
1465
1466        recordsBuf.append(c);
1467        i++;
1468
1469        for (; i < length; i++) {
1470            c = serverInfo.charAt(i);
1471
1472            if (!Character.isDigit(c)) {
1473                break;
1474            }
1475
1476            recordsBuf.append(c);
1477        }
1478
1479        recordsCount = Integer.parseInt(recordsBuf.toString());
1480
1481        StringBuffer JavaDoc duplicatesBuf = new StringBuffer JavaDoc();
1482
1483        for (; i < length; i++) {
1484            c = serverInfo.charAt(i);
1485
1486            if (Character.isDigit(c)) {
1487                break;
1488            }
1489        }
1490
1491        duplicatesBuf.append(c);
1492        i++;
1493
1494        for (; i < length; i++) {
1495            c = serverInfo.charAt(i);
1496
1497            if (!Character.isDigit(c)) {
1498                break;
1499            }
1500
1501            duplicatesBuf.append(c);
1502        }
1503
1504        duplicatesCount = Integer.parseInt(duplicatesBuf.toString());
1505
1506        return recordsCount - duplicatesCount;
1507    }
1508}
1509
Popular Tags