001: /*
002: Copyright (C) 2007 MySQL AB
003:
004: This program is free software; you can redistribute it and/or modify
005: it under the terms of version 2 of the GNU General Public License as
006: published by the Free Software Foundation.
007:
008: There are special exceptions to the terms and conditions of the GPL
009: as it is applied to this software. View the full text of the
010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
011: software distribution.
012:
013: This program is distributed in the hope that it will be useful,
014: but WITHOUT ANY WARRANTY; without even the implied warranty of
015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: GNU General Public License for more details.
017:
018: You should have received a copy of the GNU General Public License
019: along with this program; if not, write to the Free Software
020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021:
022: */
023: package com.mysql.jdbc;
024:
025: import java.io.ByteArrayInputStream;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.InputStreamReader;
029: import java.io.Reader;
030: import java.io.UnsupportedEncodingException;
031: import java.sql.Date;
032: import java.sql.SQLException;
033: import java.sql.Time;
034: import java.sql.Timestamp;
035: import java.sql.Types;
036: import java.util.Calendar;
037: import java.util.Iterator;
038: import java.util.LinkedList;
039: import java.util.List;
040: import java.util.TimeZone;
041:
042: /**
043: * A RowHolder implementation that holds one row packet (which is re-used by the
044: * driver, and thus saves memory allocations), and tries when possible to avoid
045: * allocations to break out the results as individual byte[]s.
046: *
047: * (this isn't possible when doing things like reading floating point values).
048: *
049: * @version $Id: $
050: */
051: public class BufferRow extends ResultSetRow {
052: private Buffer rowFromServer;
053:
054: /**
055: * The beginning of the row packet
056: */
057: private int homePosition = 0;
058:
059: /**
060: * The home position before the is-null bitmask for server-side
061: * prepared statement result sets
062: */
063: private int preNullBitmaskHomePosition = 0;
064:
065: /**
066: * The last-requested index, used as an optimization, if you ask for the
067: * same index, we won't seek to find it. If you ask for an index that is >
068: * than the last one requested, we start seeking from the last requested
069: * index.
070: */
071: private int lastRequestedIndex = -1;
072:
073: /**
074: * The position of the last-requested index, optimization in concert with
075: * lastRequestedIndex.
076: */
077: private int lastRequestedPos;
078:
079: /**
080: * The metadata of the fields of this result set.
081: */
082: private Field[] metadata;
083:
084: /**
085: * Is this a row from a server-side prepared statement? If so, they're
086: * encoded differently, so we have different ways of finding where each
087: * column is, and unpacking them.
088: */
089: private boolean isBinaryEncoded;
090:
091: /**
092: * If binary-encoded, the NULL status of each column is at the beginning of
093: * the row, so we
094: */
095: private boolean[] isNull;
096:
097: private List openStreams;
098:
099: public BufferRow(Buffer buf, Field[] fields, boolean isBinaryEncoded)
100: throws SQLException {
101: this .rowFromServer = buf;
102: this .metadata = fields;
103: this .isBinaryEncoded = isBinaryEncoded;
104: this .homePosition = this .rowFromServer.getPosition();
105: this .preNullBitmaskHomePosition = this .homePosition;
106:
107: if (fields != null) {
108: setMetadata(fields);
109: }
110: }
111:
112: public synchronized void closeOpenStreams() {
113: if (this .openStreams != null) {
114: // This would've looked slicker in a "for" loop
115: // but we want to skip over streams that fail to
116: // close (they probably won't ever)
117: // to be more robust and close everything we _can_
118:
119: Iterator iter = this .openStreams.iterator();
120:
121: while (iter.hasNext()) {
122:
123: try {
124: ((InputStream) iter.next()).close();
125: } catch (IOException e) {
126: // ignore - it can't really happen in this case
127: }
128: }
129:
130: this .openStreams.clear();
131: }
132: }
133:
134: private int findAndSeekToOffset(int index) throws SQLException {
135: if (!this .isBinaryEncoded) {
136:
137: if (index == 0) {
138: this .lastRequestedIndex = 0;
139: this .lastRequestedPos = this .homePosition;
140: this .rowFromServer.setPosition(this .homePosition);
141:
142: return 0;
143: }
144:
145: if (index == this .lastRequestedIndex) {
146: this .rowFromServer.setPosition(this .lastRequestedPos);
147:
148: return this .lastRequestedPos;
149: }
150:
151: int startingIndex = 0;
152:
153: if (index > this .lastRequestedIndex) {
154: if (this .lastRequestedIndex >= 0) {
155: startingIndex = this .lastRequestedIndex;
156: } else {
157: startingIndex = 0;
158: }
159:
160: this .rowFromServer.setPosition(this .lastRequestedPos);
161: } else {
162: this .rowFromServer.setPosition(this .homePosition);
163: }
164:
165: for (int i = startingIndex; i < index; i++) {
166: this .rowFromServer.fastSkipLenByteArray();
167: }
168:
169: this .lastRequestedIndex = index;
170: this .lastRequestedPos = this .rowFromServer.getPosition();
171:
172: return this .lastRequestedPos;
173: }
174:
175: return findAndSeekToOffsetForBinaryEncoding(index);
176: }
177:
178: private int findAndSeekToOffsetForBinaryEncoding(int index)
179: throws SQLException {
180: if (index == 0) {
181: this .lastRequestedIndex = 0;
182: this .lastRequestedPos = this .homePosition;
183:
184: return 0;
185: }
186:
187: if (index == this .lastRequestedIndex) {
188: this .rowFromServer.setPosition(this .lastRequestedPos);
189:
190: return this .lastRequestedPos;
191: }
192:
193: int startingIndex = 0;
194:
195: if (index > this .lastRequestedIndex) {
196: if (this .lastRequestedIndex >= 0) {
197: startingIndex = this .lastRequestedIndex;
198: } else {
199: // First-time "scan"
200: startingIndex = 0;
201: this .lastRequestedPos = this .homePosition;
202: }
203:
204: this .rowFromServer.setPosition(this .lastRequestedPos);
205: } else {
206: this .rowFromServer.setPosition(this .homePosition);
207: }
208:
209: for (int i = startingIndex; i < index; i++) {
210: if (this .isNull[i]) {
211: continue;
212: }
213:
214: int curPosition = this .rowFromServer.getPosition();
215:
216: switch (this .metadata[i].getMysqlType()) {
217: case MysqlDefs.FIELD_TYPE_NULL:
218: break; // for dummy binds
219:
220: case MysqlDefs.FIELD_TYPE_TINY:
221:
222: this .rowFromServer.setPosition(curPosition + 1);
223: break;
224:
225: case MysqlDefs.FIELD_TYPE_SHORT:
226: case MysqlDefs.FIELD_TYPE_YEAR:
227: this .rowFromServer.setPosition(curPosition + 2);
228:
229: break;
230: case MysqlDefs.FIELD_TYPE_LONG:
231: case MysqlDefs.FIELD_TYPE_INT24:
232: this .rowFromServer.setPosition(curPosition + 4);
233:
234: break;
235: case MysqlDefs.FIELD_TYPE_LONGLONG:
236: this .rowFromServer.setPosition(curPosition + 8);
237:
238: break;
239: case MysqlDefs.FIELD_TYPE_FLOAT:
240: this .rowFromServer.setPosition(curPosition + 4);
241:
242: break;
243: case MysqlDefs.FIELD_TYPE_DOUBLE:
244: this .rowFromServer.setPosition(curPosition + 8);
245:
246: break;
247: case MysqlDefs.FIELD_TYPE_TIME:
248: this .rowFromServer.fastSkipLenByteArray();
249:
250: break;
251: case MysqlDefs.FIELD_TYPE_DATE:
252:
253: this .rowFromServer.fastSkipLenByteArray();
254:
255: break;
256: case MysqlDefs.FIELD_TYPE_DATETIME:
257: case MysqlDefs.FIELD_TYPE_TIMESTAMP:
258: this .rowFromServer.fastSkipLenByteArray();
259:
260: break;
261: case MysqlDefs.FIELD_TYPE_TINY_BLOB:
262: case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB:
263: case MysqlDefs.FIELD_TYPE_LONG_BLOB:
264: case MysqlDefs.FIELD_TYPE_BLOB:
265: case MysqlDefs.FIELD_TYPE_VAR_STRING:
266: case MysqlDefs.FIELD_TYPE_VARCHAR:
267: case MysqlDefs.FIELD_TYPE_STRING:
268: case MysqlDefs.FIELD_TYPE_DECIMAL:
269: case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
270: case MysqlDefs.FIELD_TYPE_GEOMETRY:
271: case MysqlDefs.FIELD_TYPE_BIT:
272: this .rowFromServer.fastSkipLenByteArray();
273:
274: break;
275:
276: default:
277: throw SQLError.createSQLException(Messages
278: .getString("MysqlIO.97") //$NON-NLS-1$
279: + this .metadata[i].getMysqlType()
280: + Messages.getString("MysqlIO.98")
281: + (i + 1)
282: + Messages.getString("MysqlIO.99") //$NON-NLS-1$ //$NON-NLS-2$
283: + this .metadata.length
284: + Messages.getString("MysqlIO.100"), //$NON-NLS-1$
285: SQLError.SQL_STATE_GENERAL_ERROR);
286: }
287: }
288:
289: this .lastRequestedIndex = index;
290: this .lastRequestedPos = this .rowFromServer.getPosition();
291:
292: return this .lastRequestedPos;
293: }
294:
295: public synchronized InputStream getBinaryInputStream(int columnIndex)
296: throws SQLException {
297: if (this .isBinaryEncoded) {
298: if (isNull(columnIndex)) {
299: return null;
300: }
301: }
302:
303: findAndSeekToOffset(columnIndex);
304:
305: long length = this .rowFromServer.readFieldLength();
306:
307: int offset = this .rowFromServer.getPosition();
308:
309: if (length == Buffer.NULL_LENGTH) {
310: return null;
311: }
312:
313: InputStream stream = new ByteArrayInputStream(
314: this .rowFromServer.getByteBuffer(), offset,
315: (int) length);
316:
317: if (this .openStreams == null) {
318: this .openStreams = new LinkedList();
319: }
320:
321: return stream;
322: }
323:
324: public byte[] getColumnValue(int index) throws SQLException {
325: findAndSeekToOffset(index);
326:
327: if (!this .isBinaryEncoded) {
328: return this .rowFromServer.readLenByteArray(0);
329: }
330:
331: if (this .isNull[index]) {
332: return null;
333: }
334:
335: switch (this .metadata[index].getMysqlType()) {
336: case MysqlDefs.FIELD_TYPE_NULL:
337: return null;
338:
339: case MysqlDefs.FIELD_TYPE_TINY:
340: return new byte[] { this .rowFromServer.readByte() };
341:
342: case MysqlDefs.FIELD_TYPE_SHORT:
343: case MysqlDefs.FIELD_TYPE_YEAR:
344: return this .rowFromServer.getBytes(2);
345:
346: case MysqlDefs.FIELD_TYPE_LONG:
347: case MysqlDefs.FIELD_TYPE_INT24:
348: return this .rowFromServer.getBytes(4);
349:
350: case MysqlDefs.FIELD_TYPE_LONGLONG:
351: return this .rowFromServer.getBytes(8);
352:
353: case MysqlDefs.FIELD_TYPE_FLOAT:
354: return this .rowFromServer.getBytes(4);
355:
356: case MysqlDefs.FIELD_TYPE_DOUBLE:
357: return this .rowFromServer.getBytes(8);
358:
359: case MysqlDefs.FIELD_TYPE_TIME:
360: case MysqlDefs.FIELD_TYPE_DATE:
361: case MysqlDefs.FIELD_TYPE_DATETIME:
362: case MysqlDefs.FIELD_TYPE_TIMESTAMP:
363: case MysqlDefs.FIELD_TYPE_TINY_BLOB:
364: case MysqlDefs.FIELD_TYPE_MEDIUM_BLOB:
365: case MysqlDefs.FIELD_TYPE_LONG_BLOB:
366: case MysqlDefs.FIELD_TYPE_BLOB:
367: case MysqlDefs.FIELD_TYPE_VAR_STRING:
368: case MysqlDefs.FIELD_TYPE_VARCHAR:
369: case MysqlDefs.FIELD_TYPE_STRING:
370: case MysqlDefs.FIELD_TYPE_DECIMAL:
371: case MysqlDefs.FIELD_TYPE_NEW_DECIMAL:
372: case MysqlDefs.FIELD_TYPE_GEOMETRY:
373: case MysqlDefs.FIELD_TYPE_BIT:
374: return this .rowFromServer.readLenByteArray(0);
375:
376: default:
377: throw SQLError.createSQLException(Messages
378: .getString("MysqlIO.97") //$NON-NLS-1$
379: + this .metadata[index].getMysqlType()
380: + Messages.getString("MysqlIO.98")
381: + (index + 1)
382: + Messages.getString("MysqlIO.99") //$NON-NLS-1$ //$NON-NLS-2$
383: + this .metadata.length
384: + Messages.getString("MysqlIO.100"), //$NON-NLS-1$
385: SQLError.SQL_STATE_GENERAL_ERROR);
386: }
387: }
388:
389: public int getInt(int columnIndex) throws SQLException {
390:
391: findAndSeekToOffset(columnIndex);
392:
393: long length = this .rowFromServer.readFieldLength();
394:
395: int offset = this .rowFromServer.getPosition();
396:
397: if (length == Buffer.NULL_LENGTH) {
398: return 0;
399: }
400:
401: return StringUtils.getInt(this .rowFromServer.getByteBuffer(),
402: offset, offset + (int) length);
403: }
404:
405: public long getLong(int columnIndex) throws SQLException {
406: findAndSeekToOffset(columnIndex);
407:
408: long length = this .rowFromServer.readFieldLength();
409:
410: int offset = this .rowFromServer.getPosition();
411:
412: if (length == Buffer.NULL_LENGTH) {
413: return 0;
414: }
415:
416: return StringUtils.getLong(this .rowFromServer.getByteBuffer(),
417: offset, offset + (int) length);
418: }
419:
420: public double getNativeDouble(int columnIndex) throws SQLException {
421: if (isNull(columnIndex)) {
422: return 0;
423: }
424:
425: findAndSeekToOffset(columnIndex);
426:
427: int offset = this .rowFromServer.getPosition();
428:
429: return getNativeDouble(this .rowFromServer.getByteBuffer(),
430: offset);
431: }
432:
433: public float getNativeFloat(int columnIndex) throws SQLException {
434: if (isNull(columnIndex)) {
435: return 0;
436: }
437:
438: findAndSeekToOffset(columnIndex);
439:
440: int offset = this .rowFromServer.getPosition();
441:
442: return getNativeFloat(this .rowFromServer.getByteBuffer(),
443: offset);
444: }
445:
446: public int getNativeInt(int columnIndex) throws SQLException {
447: if (isNull(columnIndex)) {
448: return 0;
449: }
450:
451: findAndSeekToOffset(columnIndex);
452:
453: int offset = this .rowFromServer.getPosition();
454:
455: return getNativeInt(this .rowFromServer.getByteBuffer(), offset);
456: }
457:
458: public long getNativeLong(int columnIndex) throws SQLException {
459: if (isNull(columnIndex)) {
460: return 0;
461: }
462:
463: findAndSeekToOffset(columnIndex);
464:
465: int offset = this .rowFromServer.getPosition();
466:
467: return getNativeLong(this .rowFromServer.getByteBuffer(), offset);
468: }
469:
470: public short getNativeShort(int columnIndex) throws SQLException {
471: if (isNull(columnIndex)) {
472: return 0;
473: }
474:
475: findAndSeekToOffset(columnIndex);
476:
477: int offset = this .rowFromServer.getPosition();
478:
479: return getNativeShort(this .rowFromServer.getByteBuffer(),
480: offset);
481: }
482:
483: public Timestamp getNativeTimestamp(int columnIndex,
484: Calendar targetCalendar, TimeZone tz, boolean rollForward,
485: ConnectionImpl conn, ResultSetImpl rs) throws SQLException {
486: if (isNull(columnIndex)) {
487: return null;
488: }
489:
490: findAndSeekToOffset(columnIndex);
491:
492: long length = this .rowFromServer.readFieldLength();
493:
494: int offset = this .rowFromServer.getPosition();
495:
496: return getNativeTimestamp(this .rowFromServer.getByteBuffer(),
497: offset, (int) length, targetCalendar, tz, rollForward,
498: conn, rs);
499: }
500:
501: public Reader getReader(int columnIndex) throws SQLException {
502: InputStream stream = getBinaryInputStream(columnIndex);
503:
504: if (stream == null) {
505: return null;
506: }
507:
508: try {
509: return new InputStreamReader(stream,
510: this .metadata[columnIndex].getCharacterSet());
511: } catch (UnsupportedEncodingException e) {
512: SQLException sqlEx = SQLError.createSQLException("");
513:
514: sqlEx.initCause(e);
515:
516: throw sqlEx;
517: }
518: }
519:
520: public String getString(int columnIndex, String encoding,
521: ConnectionImpl conn) throws SQLException {
522: if (this .isBinaryEncoded) {
523: if (isNull(columnIndex)) {
524: return null;
525: }
526: }
527:
528: findAndSeekToOffset(columnIndex);
529:
530: long length = this .rowFromServer.readFieldLength();
531:
532: if (length == Buffer.NULL_LENGTH) {
533: return null;
534: }
535:
536: if (length == 0) {
537: return "";
538: }
539:
540: // TODO: I don't like this, would like to push functionality back
541: // to the buffer class somehow
542:
543: int offset = this .rowFromServer.getPosition();
544:
545: return getString(encoding, conn, this .rowFromServer
546: .getByteBuffer(), offset, (int) length);
547: }
548:
549: public Time getTimeFast(int columnIndex, Calendar targetCalendar,
550: TimeZone tz, boolean rollForward, ConnectionImpl conn,
551: ResultSetImpl rs) throws SQLException {
552: if (isNull(columnIndex)) {
553: return null;
554: }
555:
556: findAndSeekToOffset(columnIndex);
557:
558: long length = this .rowFromServer.readFieldLength();
559:
560: int offset = this .rowFromServer.getPosition();
561:
562: return getTimeFast(columnIndex, this .rowFromServer
563: .getByteBuffer(), offset, (int) length, targetCalendar,
564: tz, rollForward, conn, rs);
565: }
566:
567: public Timestamp getTimestampFast(int columnIndex,
568: Calendar targetCalendar, TimeZone tz, boolean rollForward,
569: ConnectionImpl conn, ResultSetImpl rs) throws SQLException {
570: if (isNull(columnIndex)) {
571: return null;
572: }
573:
574: findAndSeekToOffset(columnIndex);
575:
576: long length = this .rowFromServer.readFieldLength();
577:
578: int offset = this .rowFromServer.getPosition();
579:
580: return getTimestampFast(columnIndex, this .rowFromServer
581: .getByteBuffer(), offset, (int) length, targetCalendar,
582: tz, rollForward, conn, rs);
583: }
584:
585: public boolean isFloatingPointNumber(int index) throws SQLException {
586: if (this .isBinaryEncoded) {
587: switch (this .metadata[index].getSQLType()) {
588: case Types.FLOAT:
589: case Types.DOUBLE:
590: case Types.DECIMAL:
591: case Types.NUMERIC:
592: return true;
593: default:
594: return false;
595: }
596: }
597:
598: findAndSeekToOffset(index);
599:
600: long length = this .rowFromServer.readFieldLength();
601:
602: if (length == Buffer.NULL_LENGTH) {
603: return false;
604: }
605:
606: if (length == 0) {
607: return false;
608: }
609:
610: for (int i = 0; i < (int) length; i++) {
611: char c = (char) this .rowFromServer.readByte();
612:
613: if ((c == 'e') || (c == 'E')) {
614: return true;
615: }
616: }
617:
618: return false;
619: }
620:
621: public boolean isNull(int index) throws SQLException {
622: if (!this .isBinaryEncoded) {
623: findAndSeekToOffset(index);
624:
625: return this .rowFromServer.readFieldLength() == Buffer.NULL_LENGTH;
626: }
627:
628: return this .isNull[index];
629: }
630:
631: public long length(int index) throws SQLException {
632: findAndSeekToOffset(index);
633:
634: long length = this .rowFromServer.readFieldLength();
635:
636: if (length == Buffer.NULL_LENGTH) {
637: return 0;
638: }
639:
640: return length;
641: }
642:
643: public void setColumnValue(int index, byte[] value)
644: throws SQLException {
645: throw new OperationNotSupportedException();
646: }
647:
648: public void setMetadata(Field[] f) throws SQLException {
649: super .setMetadata(f);
650:
651: if (this .isBinaryEncoded) {
652: setupIsNullBitmask();
653: }
654: }
655:
656: /**
657: * Unpacks the bitmask at the head of the row packet that tells us what
658: * columns hold null values, and sets the "home" position directly after the
659: * bitmask.
660: */
661: private void setupIsNullBitmask() throws SQLException {
662: if (this .isNull != null) {
663: return; // we've already done this
664: }
665:
666: this .rowFromServer.setPosition(this .preNullBitmaskHomePosition);
667:
668: int nullCount = (this .metadata.length + 9) / 8;
669:
670: byte[] nullBitMask = new byte[nullCount];
671:
672: for (int i = 0; i < nullCount; i++) {
673: nullBitMask[i] = this .rowFromServer.readByte();
674: }
675:
676: this .homePosition = this .rowFromServer.getPosition();
677:
678: this .isNull = new boolean[this .metadata.length];
679:
680: int nullMaskPos = 0;
681: int bit = 4; // first two bits are reserved for future use
682:
683: for (int i = 0; i < this .metadata.length; i++) {
684:
685: this .isNull[i] = ((nullBitMask[nullMaskPos] & bit) != 0);
686:
687: if (((bit <<= 1) & 255) == 0) {
688: bit = 1; /* To next byte */
689:
690: nullMaskPos++;
691: }
692: }
693: }
694:
695: public Date getDateFast(int columnIndex, ConnectionImpl conn,
696: ResultSetImpl rs) throws SQLException {
697: if (isNull(columnIndex)) {
698: return null;
699: }
700:
701: findAndSeekToOffset(columnIndex);
702:
703: long length = this .rowFromServer.readFieldLength();
704:
705: int offset = this .rowFromServer.getPosition();
706:
707: return getDateFast(columnIndex, this .rowFromServer
708: .getByteBuffer(), offset, (int) length, conn, rs);
709: }
710:
711: public java.sql.Date getNativeDate(int columnIndex,
712: ConnectionImpl conn, ResultSetImpl rs) throws SQLException {
713: if (isNull(columnIndex)) {
714: return null;
715: }
716:
717: findAndSeekToOffset(columnIndex);
718:
719: long length = this .rowFromServer.readFieldLength();
720:
721: int offset = this .rowFromServer.getPosition();
722:
723: return getNativeDate(columnIndex, this .rowFromServer
724: .getByteBuffer(), offset, (int) length, conn, rs);
725: }
726:
727: public Object getNativeDateTimeValue(int columnIndex,
728: Calendar targetCalendar, int jdbcType, int mysqlType,
729: TimeZone tz, boolean rollForward, ConnectionImpl conn,
730: ResultSetImpl rs) throws SQLException {
731: if (isNull(columnIndex)) {
732: return null;
733: }
734:
735: findAndSeekToOffset(columnIndex);
736:
737: long length = this .rowFromServer.readFieldLength();
738:
739: int offset = this .rowFromServer.getPosition();
740:
741: return getNativeDateTimeValue(columnIndex, this .rowFromServer
742: .getByteBuffer(), offset, (int) length, targetCalendar,
743: jdbcType, mysqlType, tz, rollForward, conn, rs);
744: }
745:
746: public Time getNativeTime(int columnIndex, Calendar targetCalendar,
747: TimeZone tz, boolean rollForward, ConnectionImpl conn,
748: ResultSetImpl rs) throws SQLException {
749: if (isNull(columnIndex)) {
750: return null;
751: }
752:
753: findAndSeekToOffset(columnIndex);
754:
755: long length = this .rowFromServer.readFieldLength();
756:
757: int offset = this .rowFromServer.getPosition();
758:
759: return getNativeTime(columnIndex, this .rowFromServer
760: .getByteBuffer(), offset, (int) length, targetCalendar,
761: tz, rollForward, conn, rs);
762: }
763: }
|