001: /*
002: Copyright (C) 2002-2004 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:
024: */
025: package com.mysql.jdbc;
026:
027: import java.io.BufferedInputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.OutputStream;
031: import java.sql.SQLException;
032: import java.util.ArrayList;
033: import java.util.List;
034:
035: import com.mysql.jdbc.exceptions.NotYetImplementedException;
036:
037: /**
038: * The representation (mapping) in the JavaTM programming language of an SQL
039: * BLOB value. An SQL BLOB is a built-in type that stores a Binary Large Object
040: * as a column value in a row of a database table. The driver implements Blob
041: * using an SQL locator(BLOB), which means that a Blob object contains a logical
042: * pointer to the SQL BLOB data rather than the data itself. A Blob object is
043: * valid for the duration of the transaction in which is was created. Methods in
044: * the interfaces ResultSet, CallableStatement, and PreparedStatement, such as
045: * getBlob and setBlob allow a programmer to access an SQL BLOB value. The Blob
046: * interface provides methods for getting the length of an SQL BLOB (Binary
047: * Large Object) value, for materializing a BLOB value on the client, and for
048: * determining the position of a pattern of bytes within a BLOB value. This
049: * class is new in the JDBC 2.0 API.
050: *
051: * @author Mark Matthews
052: *
053: * @version $Id: BlobFromLocator.java,v 1.1.4.1 2005/05/19 18:31:49 mmatthews
054: * Exp $
055: */
056: public class BlobFromLocator implements java.sql.Blob {
057: private List primaryKeyColumns = null;
058:
059: private List primaryKeyValues = null;
060:
061: /** The ResultSet that created this BLOB */
062: private ResultSetImpl creatorResultSet;
063:
064: private String blobColumnName = null;
065:
066: private String tableName = null;
067:
068: private int numColsInResultSet = 0;
069:
070: private int numPrimaryKeys = 0;
071:
072: private String quotedId;
073:
074: /**
075: * Creates an updatable BLOB that can update in-place
076: */
077: BlobFromLocator(ResultSetImpl creatorResultSetToSet,
078: int blobColumnIndex) throws SQLException {
079: this .creatorResultSet = creatorResultSetToSet;
080:
081: this .numColsInResultSet = this .creatorResultSet.fields.length;
082: this .quotedId = this .creatorResultSet.connection.getMetaData()
083: .getIdentifierQuoteString();
084:
085: if (this .numColsInResultSet > 1) {
086: this .primaryKeyColumns = new ArrayList();
087: this .primaryKeyValues = new ArrayList();
088:
089: for (int i = 0; i < this .numColsInResultSet; i++) {
090: if (this .creatorResultSet.fields[i].isPrimaryKey()) {
091: StringBuffer keyName = new StringBuffer();
092: keyName.append(quotedId);
093:
094: String originalColumnName = this .creatorResultSet.fields[i]
095: .getOriginalName();
096:
097: if ((originalColumnName != null)
098: && (originalColumnName.length() > 0)) {
099: keyName.append(originalColumnName);
100: } else {
101: keyName.append(this .creatorResultSet.fields[i]
102: .getName());
103: }
104:
105: keyName.append(quotedId);
106:
107: this .primaryKeyColumns.add(keyName.toString());
108: this .primaryKeyValues.add(this .creatorResultSet
109: .getString(i + 1));
110: }
111: }
112: } else {
113: notEnoughInformationInQuery();
114: }
115:
116: this .numPrimaryKeys = this .primaryKeyColumns.size();
117:
118: if (this .numPrimaryKeys == 0) {
119: notEnoughInformationInQuery();
120: }
121:
122: if (this .creatorResultSet.fields[0].getOriginalTableName() != null) {
123: StringBuffer tableNameBuffer = new StringBuffer();
124:
125: String databaseName = this .creatorResultSet.fields[0]
126: .getDatabaseName();
127:
128: if ((databaseName != null) && (databaseName.length() > 0)) {
129: tableNameBuffer.append(quotedId);
130: tableNameBuffer.append(databaseName);
131: tableNameBuffer.append(quotedId);
132: tableNameBuffer.append('.');
133: }
134:
135: tableNameBuffer.append(quotedId);
136: tableNameBuffer.append(this .creatorResultSet.fields[0]
137: .getOriginalTableName());
138: tableNameBuffer.append(quotedId);
139:
140: this .tableName = tableNameBuffer.toString();
141: } else {
142: StringBuffer tableNameBuffer = new StringBuffer();
143:
144: tableNameBuffer.append(quotedId);
145: tableNameBuffer.append(this .creatorResultSet.fields[0]
146: .getTableName());
147: tableNameBuffer.append(quotedId);
148:
149: this .tableName = tableNameBuffer.toString();
150: }
151:
152: this .blobColumnName = quotedId
153: + this .creatorResultSet.getString(blobColumnIndex)
154: + quotedId;
155: }
156:
157: private void notEnoughInformationInQuery() throws SQLException {
158: throw SQLError
159: .createSQLException(
160: "Emulated BLOB locators must come from "
161: + "a ResultSet with only one table selected, and all primary "
162: + "keys selected",
163: SQLError.SQL_STATE_GENERAL_ERROR);
164: }
165:
166: /**
167: * @see Blob#setBinaryStream(long)
168: */
169: public OutputStream setBinaryStream(long indexToWriteAt)
170: throws SQLException {
171: throw new NotImplemented();
172: }
173:
174: /**
175: * Retrieves the BLOB designated by this Blob instance as a stream.
176: *
177: * @return this BLOB represented as a binary stream of bytes.
178: *
179: * @throws SQLException
180: * if a database error occurs
181: */
182: public java.io.InputStream getBinaryStream() throws SQLException {
183: // TODO: Make fetch size configurable
184: return new BufferedInputStream(new LocatorInputStream(),
185: this .creatorResultSet.connection
186: .getLocatorFetchBufferSize());
187: }
188:
189: /**
190: * @see Blob#setBytes(long, byte[], int, int)
191: */
192: public int setBytes(long writeAt, byte[] bytes, int offset,
193: int length) throws SQLException {
194: java.sql.PreparedStatement pStmt = null;
195:
196: if ((offset + length) > bytes.length) {
197: length = bytes.length - offset;
198: }
199:
200: byte[] bytesToWrite = new byte[length];
201: System.arraycopy(bytes, offset, bytesToWrite, 0, length);
202:
203: // FIXME: Needs to use identifiers for column/table names
204: StringBuffer query = new StringBuffer("UPDATE ");
205: query.append(this .tableName);
206: query.append(" SET ");
207: query.append(this .blobColumnName);
208: query.append(" = INSERT(");
209: query.append(this .blobColumnName);
210: query.append(", ");
211: query.append(writeAt);
212: query.append(", ");
213: query.append(length);
214: query.append(", ?) WHERE ");
215:
216: query.append((String) this .primaryKeyColumns.get(0));
217: query.append(" = ?");
218:
219: for (int i = 1; i < this .numPrimaryKeys; i++) {
220: query.append(" AND ");
221: query.append((String) this .primaryKeyColumns.get(i));
222: query.append(" = ?");
223: }
224:
225: try {
226: // FIXME: Have this passed in instead
227: pStmt = this .creatorResultSet.connection
228: .prepareStatement(query.toString());
229:
230: pStmt.setBytes(1, bytesToWrite);
231:
232: for (int i = 0; i < this .numPrimaryKeys; i++) {
233: pStmt.setString(i + 2, (String) this .primaryKeyValues
234: .get(i));
235: }
236:
237: int rowsUpdated = pStmt.executeUpdate();
238:
239: if (rowsUpdated != 1) {
240: throw SQLError
241: .createSQLException(
242: "BLOB data not found! Did primary keys change?",
243: SQLError.SQL_STATE_GENERAL_ERROR);
244: }
245: } finally {
246: if (pStmt != null) {
247: try {
248: pStmt.close();
249: } catch (SQLException sqlEx) {
250: ; // do nothing
251: }
252:
253: pStmt = null;
254: }
255: }
256:
257: return (int) length();
258: }
259:
260: /**
261: * @see Blob#setBytes(long, byte[])
262: */
263: public int setBytes(long writeAt, byte[] bytes) throws SQLException {
264: return setBytes(writeAt, bytes, 0, bytes.length);
265: }
266:
267: /**
268: * Returns as an array of bytes, part or all of the BLOB value that this
269: * Blob object designates.
270: *
271: * @param pos
272: * where to start the part of the BLOB
273: * @param length
274: * the length of the part of the BLOB you want returned.
275: *
276: * @return the bytes stored in the blob starting at position
277: * <code>pos</code> and having a length of <code>length</code>.
278: *
279: * @throws SQLException
280: * if a database error occurs
281: */
282: public byte[] getBytes(long pos, int length) throws SQLException {
283: java.sql.PreparedStatement pStmt = null;
284:
285: try {
286:
287: pStmt = createGetBytesStatement();
288:
289: return getBytesInternal(pStmt, pos, length);
290: } finally {
291: if (pStmt != null) {
292: try {
293: pStmt.close();
294: } catch (SQLException sqlEx) {
295: ; // do nothing
296: }
297:
298: pStmt = null;
299: }
300: }
301: }
302:
303: /**
304: * Returns the number of bytes in the BLOB value designated by this Blob
305: * object.
306: *
307: * @return the length of this blob
308: *
309: * @throws SQLException
310: * if a database error occurs
311: */
312: public long length() throws SQLException {
313: java.sql.ResultSet blobRs = null;
314: java.sql.PreparedStatement pStmt = null;
315:
316: // FIXME: Needs to use identifiers for column/table names
317: StringBuffer query = new StringBuffer("SELECT LENGTH(");
318: query.append(this .blobColumnName);
319: query.append(") FROM ");
320: query.append(this .tableName);
321: query.append(" WHERE ");
322:
323: query.append((String) this .primaryKeyColumns.get(0));
324: query.append(" = ?");
325:
326: for (int i = 1; i < this .numPrimaryKeys; i++) {
327: query.append(" AND ");
328: query.append((String) this .primaryKeyColumns.get(i));
329: query.append(" = ?");
330: }
331:
332: try {
333: // FIXME: Have this passed in instead
334: pStmt = this .creatorResultSet.connection
335: .prepareStatement(query.toString());
336:
337: for (int i = 0; i < this .numPrimaryKeys; i++) {
338: pStmt.setString(i + 1, (String) this .primaryKeyValues
339: .get(i));
340: }
341:
342: blobRs = pStmt.executeQuery();
343:
344: if (blobRs.next()) {
345: return blobRs.getLong(1);
346: }
347:
348: throw SQLError.createSQLException(
349: "BLOB data not found! Did primary keys change?",
350: SQLError.SQL_STATE_GENERAL_ERROR);
351: } finally {
352: if (blobRs != null) {
353: try {
354: blobRs.close();
355: } catch (SQLException sqlEx) {
356: ; // do nothing
357: }
358:
359: blobRs = null;
360: }
361:
362: if (pStmt != null) {
363: try {
364: pStmt.close();
365: } catch (SQLException sqlEx) {
366: ; // do nothing
367: }
368:
369: pStmt = null;
370: }
371: }
372: }
373:
374: /**
375: * Finds the position of the given pattern in this BLOB.
376: *
377: * @param pattern
378: * the pattern to find
379: * @param start
380: * where to start finding the pattern
381: *
382: * @return the position where the pattern is found in the BLOB, -1 if not
383: * found
384: *
385: * @throws SQLException
386: * if a database error occurs
387: */
388: public long position(java.sql.Blob pattern, long start)
389: throws SQLException {
390: return position(pattern.getBytes(0, (int) pattern.length()),
391: start);
392: }
393:
394: /**
395: * @see java.sql.Blob#position(byte[], long)
396: */
397: public long position(byte[] pattern, long start)
398: throws SQLException {
399: java.sql.ResultSet blobRs = null;
400: java.sql.PreparedStatement pStmt = null;
401:
402: // FIXME: Needs to use identifiers for column/table names
403: StringBuffer query = new StringBuffer("SELECT LOCATE(");
404: query.append("?, ");
405: query.append(this .blobColumnName);
406: query.append(", ");
407: query.append(start);
408: query.append(") FROM ");
409: query.append(this .tableName);
410: query.append(" WHERE ");
411:
412: query.append((String) this .primaryKeyColumns.get(0));
413: query.append(" = ?");
414:
415: for (int i = 1; i < this .numPrimaryKeys; i++) {
416: query.append(" AND ");
417: query.append((String) this .primaryKeyColumns.get(i));
418: query.append(" = ?");
419: }
420:
421: try {
422: // FIXME: Have this passed in instead
423: pStmt = this .creatorResultSet.connection
424: .prepareStatement(query.toString());
425: pStmt.setBytes(1, pattern);
426:
427: for (int i = 0; i < this .numPrimaryKeys; i++) {
428: pStmt.setString(i + 2, (String) this .primaryKeyValues
429: .get(i));
430: }
431:
432: blobRs = pStmt.executeQuery();
433:
434: if (blobRs.next()) {
435: return blobRs.getLong(1);
436: }
437:
438: throw SQLError.createSQLException(
439: "BLOB data not found! Did primary keys change?",
440: SQLError.SQL_STATE_GENERAL_ERROR);
441: } finally {
442: if (blobRs != null) {
443: try {
444: blobRs.close();
445: } catch (SQLException sqlEx) {
446: ; // do nothing
447: }
448:
449: blobRs = null;
450: }
451:
452: if (pStmt != null) {
453: try {
454: pStmt.close();
455: } catch (SQLException sqlEx) {
456: ; // do nothing
457: }
458:
459: pStmt = null;
460: }
461: }
462: }
463:
464: /**
465: * @see Blob#truncate(long)
466: */
467: public void truncate(long length) throws SQLException {
468: java.sql.PreparedStatement pStmt = null;
469:
470: // FIXME: Needs to use identifiers for column/table names
471: StringBuffer query = new StringBuffer("UPDATE ");
472: query.append(this .tableName);
473: query.append(" SET ");
474: query.append(this .blobColumnName);
475: query.append(" = LEFT(");
476: query.append(this .blobColumnName);
477: query.append(", ");
478: query.append(length);
479: query.append(") WHERE ");
480:
481: query.append((String) this .primaryKeyColumns.get(0));
482: query.append(" = ?");
483:
484: for (int i = 1; i < this .numPrimaryKeys; i++) {
485: query.append(" AND ");
486: query.append((String) this .primaryKeyColumns.get(i));
487: query.append(" = ?");
488: }
489:
490: try {
491: // FIXME: Have this passed in instead
492: pStmt = this .creatorResultSet.connection
493: .prepareStatement(query.toString());
494:
495: for (int i = 0; i < this .numPrimaryKeys; i++) {
496: pStmt.setString(i + 1, (String) this .primaryKeyValues
497: .get(i));
498: }
499:
500: int rowsUpdated = pStmt.executeUpdate();
501:
502: if (rowsUpdated != 1) {
503: throw SQLError
504: .createSQLException(
505: "BLOB data not found! Did primary keys change?",
506: SQLError.SQL_STATE_GENERAL_ERROR);
507: }
508: } finally {
509: if (pStmt != null) {
510: try {
511: pStmt.close();
512: } catch (SQLException sqlEx) {
513: ; // do nothing
514: }
515:
516: pStmt = null;
517: }
518: }
519: }
520:
521: java.sql.PreparedStatement createGetBytesStatement()
522: throws SQLException {
523: StringBuffer query = new StringBuffer("SELECT SUBSTRING(");
524:
525: query.append(this .blobColumnName);
526: query.append(", ");
527: query.append("?");
528: query.append(", ");
529: query.append("?");
530: query.append(") FROM ");
531: query.append(this .tableName);
532: query.append(" WHERE ");
533:
534: query.append((String) this .primaryKeyColumns.get(0));
535: query.append(" = ?");
536:
537: for (int i = 1; i < this .numPrimaryKeys; i++) {
538: query.append(" AND ");
539: query.append((String) this .primaryKeyColumns.get(i));
540: query.append(" = ?");
541: }
542:
543: return this .creatorResultSet.connection.prepareStatement(query
544: .toString());
545: }
546:
547: byte[] getBytesInternal(java.sql.PreparedStatement pStmt, long pos,
548: int length) throws SQLException {
549:
550: java.sql.ResultSet blobRs = null;
551:
552: try {
553:
554: pStmt.setLong(1, pos);
555: pStmt.setInt(2, length);
556:
557: for (int i = 0; i < this .numPrimaryKeys; i++) {
558: pStmt.setString(i + 3, (String) this .primaryKeyValues
559: .get(i));
560: }
561:
562: blobRs = pStmt.executeQuery();
563:
564: if (blobRs.next()) {
565: return ((com.mysql.jdbc.ResultSetImpl) blobRs)
566: .getBytes(1, true);
567: }
568:
569: throw SQLError.createSQLException(
570: "BLOB data not found! Did primary keys change?",
571: SQLError.SQL_STATE_GENERAL_ERROR);
572: } finally {
573: if (blobRs != null) {
574: try {
575: blobRs.close();
576: } catch (SQLException sqlEx) {
577: ; // do nothing
578: }
579:
580: blobRs = null;
581: }
582: }
583: }
584:
585: class LocatorInputStream extends InputStream {
586: long currentPositionInBlob = 0;
587:
588: long length = 0;
589:
590: java.sql.PreparedStatement pStmt = null;
591:
592: LocatorInputStream() throws SQLException {
593: length = length();
594: pStmt = createGetBytesStatement();
595: }
596:
597: public int read() throws IOException {
598: if (currentPositionInBlob + 1 > length) {
599: return -1;
600: }
601:
602: try {
603: byte[] asBytes = getBytesInternal(pStmt,
604: (currentPositionInBlob++) + 1, 1);
605:
606: if (asBytes == null) {
607: return -1;
608: }
609:
610: return asBytes[0];
611: } catch (SQLException sqlEx) {
612: throw new IOException(sqlEx.toString());
613: }
614: }
615:
616: /*
617: * (non-Javadoc)
618: *
619: * @see java.io.InputStream#read(byte[], int, int)
620: */
621: public int read(byte[] b, int off, int len) throws IOException {
622: if (currentPositionInBlob + 1 > length) {
623: return -1;
624: }
625:
626: try {
627: byte[] asBytes = getBytesInternal(pStmt,
628: (currentPositionInBlob) + 1, len);
629:
630: if (asBytes == null) {
631: return -1;
632: }
633:
634: System.arraycopy(asBytes, 0, b, off, asBytes.length);
635:
636: currentPositionInBlob += asBytes.length;
637:
638: return asBytes.length;
639: } catch (SQLException sqlEx) {
640: throw new IOException(sqlEx.toString());
641: }
642: }
643:
644: /*
645: * (non-Javadoc)
646: *
647: * @see java.io.InputStream#read(byte[])
648: */
649: public int read(byte[] b) throws IOException {
650: if (currentPositionInBlob + 1 > length) {
651: return -1;
652: }
653:
654: try {
655: byte[] asBytes = getBytesInternal(pStmt,
656: (currentPositionInBlob) + 1, b.length);
657:
658: if (asBytes == null) {
659: return -1;
660: }
661:
662: System.arraycopy(asBytes, 0, b, 0, asBytes.length);
663:
664: currentPositionInBlob += asBytes.length;
665:
666: return asBytes.length;
667: } catch (SQLException sqlEx) {
668: throw new IOException(sqlEx.toString());
669: }
670: }
671:
672: /*
673: * (non-Javadoc)
674: *
675: * @see java.io.InputStream#close()
676: */
677: public void close() throws IOException {
678: if (pStmt != null) {
679: try {
680: pStmt.close();
681: } catch (SQLException sqlEx) {
682: throw new IOException(sqlEx.toString());
683: }
684: }
685:
686: super .close();
687: }
688: }
689:
690: public void free() throws SQLException {
691: this .creatorResultSet = null;
692: this .primaryKeyColumns = null;
693: this .primaryKeyValues = null;
694: }
695:
696: public InputStream getBinaryStream(long pos, long length)
697: throws SQLException {
698: throw new NotYetImplementedException();
699: }
700: }
|