1 | /* |
2 | Copyright (C) 2002-2004 MySQL AB |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of version 2 of the GNU General Public License as |
6 | published by the Free Software Foundation. |
7 | |
8 | There are special exceptions to the terms and conditions of the GPL |
9 | as it is applied to this software. View the full text of the |
10 | exception in file EXCEPTIONS-CONNECTOR-J in the directory of this |
11 | software distribution. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along with this program; if not, write to the Free Software |
20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
21 | |
22 | |
23 | |
24 | */ |
25 | package com.mysql.jdbc; |
26 | |
27 | import java.io.BufferedInputStream; |
28 | import java.io.IOException; |
29 | import java.io.InputStream; |
30 | import java.io.OutputStream; |
31 | import java.sql.SQLException; |
32 | import java.util.ArrayList; |
33 | import java.util.List; |
34 | |
35 | /** |
36 | * The representation (mapping) in the JavaTM programming language of an SQL |
37 | * BLOB value. An SQL BLOB is a built-in type that stores a Binary Large Object |
38 | * as a column value in a row of a database table. The driver implements Blob |
39 | * using an SQL locator(BLOB), which means that a Blob object contains a logical |
40 | * pointer to the SQL BLOB data rather than the data itself. A Blob object is |
41 | * valid for the duration of the transaction in which is was created. Methods in |
42 | * the interfaces ResultSet, CallableStatement, and PreparedStatement, such as |
43 | * getBlob and setBlob allow a programmer to access an SQL BLOB value. The Blob |
44 | * interface provides methods for getting the length of an SQL BLOB (Binary |
45 | * Large Object) value, for materializing a BLOB value on the client, and for |
46 | * determining the position of a pattern of bytes within a BLOB value. This |
47 | * class is new in the JDBC 2.0 API. |
48 | * |
49 | * @author Mark Matthews |
50 | * |
51 | * @version $Id: BlobFromLocator.java,v 1.1.4.1 2005/05/19 18:31:49 mmatthews |
52 | * Exp $ |
53 | */ |
54 | public class BlobFromLocator implements java.sql.Blob { |
55 | private List primaryKeyColumns = null; |
56 | |
57 | private List primaryKeyValues = null; |
58 | |
59 | /** The ResultSet that created this BLOB */ |
60 | private ResultSet creatorResultSet; |
61 | |
62 | private String blobColumnName = null; |
63 | |
64 | private String tableName = null; |
65 | |
66 | private int numColsInResultSet = 0; |
67 | |
68 | private int numPrimaryKeys = 0; |
69 | |
70 | private String quotedId; |
71 | |
72 | /** |
73 | * Creates an updatable BLOB that can update in-place |
74 | */ |
75 | BlobFromLocator(ResultSet creatorResultSetToSet, int blobColumnIndex) |
76 | throws SQLException { |
77 | this.creatorResultSet = creatorResultSetToSet; |
78 | |
79 | this.numColsInResultSet = this.creatorResultSet.fields.length; |
80 | this.quotedId = this.creatorResultSet.connection.getMetaData() |
81 | .getIdentifierQuoteString(); |
82 | |
83 | if (this.numColsInResultSet > 1) { |
84 | this.primaryKeyColumns = new ArrayList(); |
85 | this.primaryKeyValues = new ArrayList(); |
86 | |
87 | for (int i = 0; i < this.numColsInResultSet; i++) { |
88 | if (this.creatorResultSet.fields[i].isPrimaryKey()) { |
89 | StringBuffer keyName = new StringBuffer(); |
90 | keyName.append(quotedId); |
91 | |
92 | String originalColumnName = this.creatorResultSet.fields[i] |
93 | .getOriginalName(); |
94 | |
95 | if ((originalColumnName != null) |
96 | && (originalColumnName.length() > 0)) { |
97 | keyName.append(originalColumnName); |
98 | } else { |
99 | keyName.append(this.creatorResultSet.fields[i] |
100 | .getName()); |
101 | } |
102 | |
103 | keyName.append(quotedId); |
104 | |
105 | this.primaryKeyColumns.add(keyName.toString()); |
106 | this.primaryKeyValues.add(this.creatorResultSet |
107 | .getString(i + 1)); |
108 | } |
109 | } |
110 | } else { |
111 | notEnoughInformationInQuery(); |
112 | } |
113 | |
114 | this.numPrimaryKeys = this.primaryKeyColumns.size(); |
115 | |
116 | if (this.numPrimaryKeys == 0) { |
117 | notEnoughInformationInQuery(); |
118 | } |
119 | |
120 | if (this.creatorResultSet.fields[0].getOriginalTableName() != null) { |
121 | StringBuffer tableNameBuffer = new StringBuffer(); |
122 | |
123 | String databaseName = this.creatorResultSet.fields[0] |
124 | .getDatabaseName(); |
125 | |
126 | if ((databaseName != null) && (databaseName.length() > 0)) { |
127 | tableNameBuffer.append(quotedId); |
128 | tableNameBuffer.append(databaseName); |
129 | tableNameBuffer.append(quotedId); |
130 | tableNameBuffer.append('.'); |
131 | } |
132 | |
133 | tableNameBuffer.append(quotedId); |
134 | tableNameBuffer.append(this.creatorResultSet.fields[0] |
135 | .getOriginalTableName()); |
136 | tableNameBuffer.append(quotedId); |
137 | |
138 | this.tableName = tableNameBuffer.toString(); |
139 | } else { |
140 | StringBuffer tableNameBuffer = new StringBuffer(); |
141 | |
142 | tableNameBuffer.append(quotedId); |
143 | tableNameBuffer.append(this.creatorResultSet.fields[0] |
144 | .getTableName()); |
145 | tableNameBuffer.append(quotedId); |
146 | |
147 | this.tableName = tableNameBuffer.toString(); |
148 | } |
149 | |
150 | this.blobColumnName = quotedId |
151 | + this.creatorResultSet.getString(blobColumnIndex) + quotedId; |
152 | } |
153 | |
154 | private void notEnoughInformationInQuery() throws SQLException { |
155 | throw SQLError.createSQLException("Emulated BLOB locators must come from " |
156 | + "a ResultSet with only one table selected, and all primary " |
157 | + "keys selected", SQLError.SQL_STATE_GENERAL_ERROR); |
158 | } |
159 | |
160 | /** |
161 | * @see Blob#setBinaryStream(long) |
162 | */ |
163 | public OutputStream setBinaryStream(long indexToWriteAt) |
164 | throws SQLException { |
165 | throw new NotImplemented(); |
166 | } |
167 | |
168 | /** |
169 | * Retrieves the BLOB designated by this Blob instance as a stream. |
170 | * |
171 | * @return this BLOB represented as a binary stream of bytes. |
172 | * |
173 | * @throws SQLException |
174 | * if a database error occurs |
175 | */ |
176 | public java.io.InputStream getBinaryStream() throws SQLException { |
177 | // TODO: Make fetch size configurable |
178 | return new BufferedInputStream(new LocatorInputStream(), |
179 | this.creatorResultSet.connection.getLocatorFetchBufferSize()); |
180 | } |
181 | |
182 | /** |
183 | * @see Blob#setBytes(long, byte[], int, int) |
184 | */ |
185 | public int setBytes(long writeAt, byte[] bytes, int offset, int length) |
186 | throws SQLException { |
187 | java.sql.PreparedStatement pStmt = null; |
188 | |
189 | if ((offset + length) > bytes.length) { |
190 | length = bytes.length - offset; |
191 | } |
192 | |
193 | byte[] bytesToWrite = new byte[length]; |
194 | System.arraycopy(bytes, offset, bytesToWrite, 0, length); |
195 | |
196 | // FIXME: Needs to use identifiers for column/table names |
197 | StringBuffer query = new StringBuffer("UPDATE "); |
198 | query.append(this.tableName); |
199 | query.append(" SET "); |
200 | query.append(this.blobColumnName); |
201 | query.append(" = INSERT("); |
202 | query.append(this.blobColumnName); |
203 | query.append(", "); |
204 | query.append(writeAt); |
205 | query.append(", "); |
206 | query.append(length); |
207 | query.append(", ?) WHERE "); |
208 | |
209 | query.append((String) this.primaryKeyColumns.get(0)); |
210 | query.append(" = ?"); |
211 | |
212 | for (int i = 1; i < this.numPrimaryKeys; i++) { |
213 | query.append(" AND "); |
214 | query.append((String) this.primaryKeyColumns.get(i)); |
215 | query.append(" = ?"); |
216 | } |
217 | |
218 | try { |
219 | // FIXME: Have this passed in instead |
220 | pStmt = this.creatorResultSet.connection.prepareStatement(query |
221 | .toString()); |
222 | |
223 | pStmt.setBytes(1, bytesToWrite); |
224 | |
225 | for (int i = 0; i < this.numPrimaryKeys; i++) { |
226 | pStmt.setString(i + 2, (String) this.primaryKeyValues.get(i)); |
227 | } |
228 | |
229 | int rowsUpdated = pStmt.executeUpdate(); |
230 | |
231 | if (rowsUpdated != 1) { |
232 | throw SQLError.createSQLException( |
233 | "BLOB data not found! Did primary keys change?", |
234 | SQLError.SQL_STATE_GENERAL_ERROR); |
235 | } |
236 | } finally { |
237 | if (pStmt != null) { |
238 | try { |
239 | pStmt.close(); |
240 | } catch (SQLException sqlEx) { |
241 | ; // do nothing |
242 | } |
243 | |
244 | pStmt = null; |
245 | } |
246 | } |
247 | |
248 | return (int) length(); |
249 | } |
250 | |
251 | /** |
252 | * @see Blob#setBytes(long, byte[]) |
253 | */ |
254 | public int setBytes(long writeAt, byte[] bytes) throws SQLException { |
255 | return setBytes(writeAt, bytes, 0, bytes.length); |
256 | } |
257 | |
258 | /** |
259 | * Returns as an array of bytes, part or all of the BLOB value that this |
260 | * Blob object designates. |
261 | * |
262 | * @param pos |
263 | * where to start the part of the BLOB |
264 | * @param length |
265 | * the length of the part of the BLOB you want returned. |
266 | * |
267 | * @return the bytes stored in the blob starting at position |
268 | * <code>pos</code> and having a length of <code>length</code>. |
269 | * |
270 | * @throws SQLException |
271 | * if a database error occurs |
272 | */ |
273 | public byte[] getBytes(long pos, int length) throws SQLException { |
274 | java.sql.ResultSet blobRs = null; |
275 | java.sql.PreparedStatement pStmt = null; |
276 | |
277 | try { |
278 | |
279 | pStmt = createGetBytesStatement(); |
280 | |
281 | return getBytesInternal(pStmt, pos, length); |
282 | } finally { |
283 | if (blobRs != null) { |
284 | try { |
285 | blobRs.close(); |
286 | } catch (SQLException sqlEx) { |
287 | ; // do nothing |
288 | } |
289 | |
290 | blobRs = null; |
291 | } |
292 | } |
293 | } |
294 | |
295 | /** |
296 | * Returns the number of bytes in the BLOB value designated by this Blob |
297 | * object. |
298 | * |
299 | * @return the length of this blob |
300 | * |
301 | * @throws SQLException |
302 | * if a database error occurs |
303 | */ |
304 | public long length() throws SQLException { |
305 | java.sql.ResultSet blobRs = null; |
306 | java.sql.PreparedStatement pStmt = null; |
307 | |
308 | // FIXME: Needs to use identifiers for column/table names |
309 | StringBuffer query = new StringBuffer("SELECT LENGTH("); |
310 | query.append(this.blobColumnName); |
311 | query.append(") FROM "); |
312 | query.append(this.tableName); |
313 | query.append(" WHERE "); |
314 | |
315 | query.append((String) this.primaryKeyColumns.get(0)); |
316 | query.append(" = ?"); |
317 | |
318 | for (int i = 1; i < this.numPrimaryKeys; i++) { |
319 | query.append(" AND "); |
320 | query.append((String) this.primaryKeyColumns.get(i)); |
321 | query.append(" = ?"); |
322 | } |
323 | |
324 | try { |
325 | // FIXME: Have this passed in instead |
326 | pStmt = this.creatorResultSet.connection.prepareStatement(query |
327 | .toString()); |
328 | |
329 | for (int i = 0; i < this.numPrimaryKeys; i++) { |
330 | pStmt.setString(i + 1, (String) this.primaryKeyValues.get(i)); |
331 | } |
332 | |
333 | blobRs = pStmt.executeQuery(); |
334 | |
335 | if (blobRs.next()) { |
336 | return blobRs.getLong(1); |
337 | } |
338 | |
339 | throw SQLError.createSQLException( |
340 | "BLOB data not found! Did primary keys change?", |
341 | SQLError.SQL_STATE_GENERAL_ERROR); |
342 | } finally { |
343 | if (blobRs != null) { |
344 | try { |
345 | blobRs.close(); |
346 | } catch (SQLException sqlEx) { |
347 | ; // do nothing |
348 | } |
349 | |
350 | blobRs = null; |
351 | } |
352 | |
353 | if (pStmt != null) { |
354 | try { |
355 | pStmt.close(); |
356 | } catch (SQLException sqlEx) { |
357 | ; // do nothing |
358 | } |
359 | |
360 | pStmt = null; |
361 | } |
362 | } |
363 | } |
364 | |
365 | /** |
366 | * Finds the position of the given pattern in this BLOB. |
367 | * |
368 | * @param pattern |
369 | * the pattern to find |
370 | * @param start |
371 | * where to start finding the pattern |
372 | * |
373 | * @return the position where the pattern is found in the BLOB, -1 if not |
374 | * found |
375 | * |
376 | * @throws SQLException |
377 | * if a database error occurs |
378 | */ |
379 | public long position(java.sql.Blob pattern, long start) throws SQLException { |
380 | return position(pattern.getBytes(0, (int) pattern.length()), start); |
381 | } |
382 | |
383 | /** |
384 | * @see java.sql.Blob#position(byte[], long) |
385 | */ |
386 | public long position(byte[] pattern, long start) throws SQLException { |
387 | java.sql.ResultSet blobRs = null; |
388 | java.sql.PreparedStatement pStmt = null; |
389 | |
390 | // FIXME: Needs to use identifiers for column/table names |
391 | StringBuffer query = new StringBuffer("SELECT LOCATE("); |
392 | query.append("?, "); |
393 | query.append(this.blobColumnName); |
394 | query.append(", "); |
395 | query.append(start); |
396 | query.append(") FROM "); |
397 | query.append(this.tableName); |
398 | query.append(" WHERE "); |
399 | |
400 | query.append((String) this.primaryKeyColumns.get(0)); |
401 | query.append(" = ?"); |
402 | |
403 | for (int i = 1; i < this.numPrimaryKeys; i++) { |
404 | query.append(" AND "); |
405 | query.append((String) this.primaryKeyColumns.get(i)); |
406 | query.append(" = ?"); |
407 | } |
408 | |
409 | try { |
410 | // FIXME: Have this passed in instead |
411 | pStmt = this.creatorResultSet.connection.prepareStatement(query |
412 | .toString()); |
413 | pStmt.setBytes(1, pattern); |
414 | |
415 | for (int i = 0; i < this.numPrimaryKeys; i++) { |
416 | pStmt.setString(i + 2, (String) this.primaryKeyValues.get(i)); |
417 | } |
418 | |
419 | blobRs = pStmt.executeQuery(); |
420 | |
421 | if (blobRs.next()) { |
422 | return blobRs.getLong(1); |
423 | } |
424 | |
425 | throw SQLError.createSQLException( |
426 | "BLOB data not found! Did primary keys change?", |
427 | SQLError.SQL_STATE_GENERAL_ERROR); |
428 | } finally { |
429 | if (blobRs != null) { |
430 | try { |
431 | blobRs.close(); |
432 | } catch (SQLException sqlEx) { |
433 | ; // do nothing |
434 | } |
435 | |
436 | blobRs = null; |
437 | } |
438 | |
439 | if (pStmt != null) { |
440 | try { |
441 | pStmt.close(); |
442 | } catch (SQLException sqlEx) { |
443 | ; // do nothing |
444 | } |
445 | |
446 | pStmt = null; |
447 | } |
448 | } |
449 | } |
450 | |
451 | /** |
452 | * @see Blob#truncate(long) |
453 | */ |
454 | public void truncate(long length) throws SQLException { |
455 | java.sql.PreparedStatement pStmt = null; |
456 | |
457 | // FIXME: Needs to use identifiers for column/table names |
458 | StringBuffer query = new StringBuffer("UPDATE "); |
459 | query.append(this.tableName); |
460 | query.append(" SET "); |
461 | query.append(this.blobColumnName); |
462 | query.append(" = LEFT("); |
463 | query.append(this.blobColumnName); |
464 | query.append(", "); |
465 | query.append(length); |
466 | query.append(") WHERE "); |
467 | |
468 | query.append((String) this.primaryKeyColumns.get(0)); |
469 | query.append(" = ?"); |
470 | |
471 | for (int i = 1; i < this.numPrimaryKeys; i++) { |
472 | query.append(" AND "); |
473 | query.append((String) this.primaryKeyColumns.get(i)); |
474 | query.append(" = ?"); |
475 | } |
476 | |
477 | try { |
478 | // FIXME: Have this passed in instead |
479 | pStmt = this.creatorResultSet.connection.prepareStatement(query |
480 | .toString()); |
481 | |
482 | for (int i = 0; i < this.numPrimaryKeys; i++) { |
483 | pStmt.setString(i + 1, (String) this.primaryKeyValues.get(i)); |
484 | } |
485 | |
486 | int rowsUpdated = pStmt.executeUpdate(); |
487 | |
488 | if (rowsUpdated != 1) { |
489 | throw SQLError.createSQLException( |
490 | "BLOB data not found! Did primary keys change?", |
491 | SQLError.SQL_STATE_GENERAL_ERROR); |
492 | } |
493 | } finally { |
494 | if (pStmt != null) { |
495 | try { |
496 | pStmt.close(); |
497 | } catch (SQLException sqlEx) { |
498 | ; // do nothing |
499 | } |
500 | |
501 | pStmt = null; |
502 | } |
503 | } |
504 | } |
505 | |
506 | java.sql.PreparedStatement createGetBytesStatement() throws SQLException { |
507 | StringBuffer query = new StringBuffer("SELECT SUBSTRING("); |
508 | |
509 | query.append(this.blobColumnName); |
510 | query.append(", "); |
511 | query.append("?"); |
512 | query.append(", "); |
513 | query.append("?"); |
514 | query.append(") FROM "); |
515 | query.append(this.tableName); |
516 | query.append(" WHERE "); |
517 | |
518 | query.append((String) this.primaryKeyColumns.get(0)); |
519 | query.append(" = ?"); |
520 | |
521 | for (int i = 1; i < this.numPrimaryKeys; i++) { |
522 | query.append(" AND "); |
523 | query.append((String) this.primaryKeyColumns.get(i)); |
524 | query.append(" = ?"); |
525 | } |
526 | |
527 | return this.creatorResultSet.connection.prepareStatement(query |
528 | .toString()); |
529 | } |
530 | |
531 | byte[] getBytesInternal(java.sql.PreparedStatement pStmt, long pos, |
532 | int length) throws SQLException { |
533 | |
534 | java.sql.ResultSet blobRs = null; |
535 | |
536 | try { |
537 | |
538 | pStmt.setLong(1, pos); |
539 | pStmt.setInt(2, length); |
540 | |
541 | for (int i = 0; i < this.numPrimaryKeys; i++) { |
542 | pStmt.setString(i + 3, (String) this.primaryKeyValues.get(i)); |
543 | } |
544 | |
545 | blobRs = pStmt.executeQuery(); |
546 | |
547 | if (blobRs.next()) { |
548 | return ((com.mysql.jdbc.ResultSet) blobRs).getBytes(1, true); |
549 | } |
550 | |
551 | throw SQLError.createSQLException( |
552 | "BLOB data not found! Did primary keys change?", |
553 | SQLError.SQL_STATE_GENERAL_ERROR); |
554 | } finally { |
555 | if (blobRs != null) { |
556 | try { |
557 | blobRs.close(); |
558 | } catch (SQLException sqlEx) { |
559 | ; // do nothing |
560 | } |
561 | |
562 | blobRs = null; |
563 | } |
564 | } |
565 | } |
566 | |
567 | class LocatorInputStream extends InputStream { |
568 | long currentPositionInBlob = 0; |
569 | |
570 | long length = 0; |
571 | |
572 | java.sql.PreparedStatement pStmt = null; |
573 | |
574 | LocatorInputStream() throws SQLException { |
575 | length = length(); |
576 | pStmt = createGetBytesStatement(); |
577 | } |
578 | |
579 | public int read() throws IOException { |
580 | if (currentPositionInBlob + 1 > length) { |
581 | return -1; |
582 | } |
583 | |
584 | try { |
585 | byte[] asBytes = getBytesInternal(pStmt, |
586 | (currentPositionInBlob++) + 1, 1); |
587 | |
588 | if (asBytes == null) { |
589 | return -1; |
590 | } |
591 | |
592 | return asBytes[0]; |
593 | } catch (SQLException sqlEx) { |
594 | throw new IOException(sqlEx.toString()); |
595 | } |
596 | } |
597 | |
598 | /* |
599 | * (non-Javadoc) |
600 | * |
601 | * @see java.io.InputStream#read(byte[], int, int) |
602 | */ |
603 | public int read(byte[] b, int off, int len) throws IOException { |
604 | if (currentPositionInBlob + 1 > length) { |
605 | return -1; |
606 | } |
607 | |
608 | try { |
609 | byte[] asBytes = getBytesInternal(pStmt, |
610 | (currentPositionInBlob) + 1, len); |
611 | |
612 | if (asBytes == null) { |
613 | return -1; |
614 | } |
615 | |
616 | System.arraycopy(asBytes, 0, b, off, asBytes.length); |
617 | |
618 | currentPositionInBlob += asBytes.length; |
619 | |
620 | return asBytes.length; |
621 | } catch (SQLException sqlEx) { |
622 | throw new IOException(sqlEx.toString()); |
623 | } |
624 | } |
625 | |
626 | /* |
627 | * (non-Javadoc) |
628 | * |
629 | * @see java.io.InputStream#read(byte[]) |
630 | */ |
631 | public int read(byte[] b) throws IOException { |
632 | if (currentPositionInBlob + 1 > length) { |
633 | return -1; |
634 | } |
635 | |
636 | try { |
637 | byte[] asBytes = getBytesInternal(pStmt, |
638 | (currentPositionInBlob) + 1, b.length); |
639 | |
640 | if (asBytes == null) { |
641 | return -1; |
642 | } |
643 | |
644 | System.arraycopy(asBytes, 0, b, 0, asBytes.length); |
645 | |
646 | currentPositionInBlob += asBytes.length; |
647 | |
648 | return asBytes.length; |
649 | } catch (SQLException sqlEx) { |
650 | throw new IOException(sqlEx.toString()); |
651 | } |
652 | } |
653 | |
654 | /* |
655 | * (non-Javadoc) |
656 | * |
657 | * @see java.io.InputStream#close() |
658 | */ |
659 | public void close() throws IOException { |
660 | if (pStmt != null) { |
661 | try { |
662 | pStmt.close(); |
663 | } catch (SQLException sqlEx) { |
664 | throw new IOException(sqlEx.toString()); |
665 | } |
666 | } |
667 | |
668 | super.close(); |
669 | } |
670 | } |
671 | } |