1 | /* |
2 | |
3 | Derby - Class org.apache.derby.impl.jdbc.EmbedBlob |
4 | |
5 | Copyright 2000, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | |
22 | package org.apache.derby.impl.jdbc; |
23 | |
24 | import org.apache.derby.iapi.reference.SQLState; |
25 | import org.apache.derby.iapi.error.StandardException; |
26 | import org.apache.derby.iapi.services.sanity.SanityManager; |
27 | import org.apache.derby.iapi.types.DataValueDescriptor; |
28 | import org.apache.derby.iapi.types.Resetable; |
29 | import org.apache.derby.impl.jdbc.ConnectionChild; |
30 | import org.apache.derby.impl.jdbc.EmbedConnection; |
31 | import org.apache.derby.impl.jdbc.Util; |
32 | import org.apache.derby.iapi.services.io.NewByteArrayInputStream; |
33 | import org.apache.derby.iapi.services.io.InputStreamUtil; |
34 | import org.apache.derby.iapi.services.io.ArrayInputStream; |
35 | |
36 | import java.sql.SQLException; |
37 | import java.sql.Blob; |
38 | import java.io.InputStream; |
39 | import java.io.EOFException; |
40 | import java.io.IOException; |
41 | |
42 | /** |
43 | Implements java.sql.Blob (see the JDBC 2.0 spec). |
44 | A blob sits on top of a BINARY, VARBINARY or LONG VARBINARY column. |
45 | If its data is small (less than 1 page) it is a byte array taken from |
46 | the SQLBit class. If it is large (more than 1 page) it is a long column |
47 | in the database. The long column is accessed as a stream, and is implemented |
48 | in store as an OverflowInputStream. The Resetable interface allows sending |
49 | messages to that stream to initialize itself (reopen its container and |
50 | lock the corresponding row) and to reset itself to the beginning. |
51 | |
52 | NOTE: In the case that the data is large, it is represented as a stream. |
53 | This stream is returned to the user in the getBinaryStream() method. |
54 | This means that we have limited control over the state of the stream, |
55 | since the user can read bytes from it at any time. Thus all methods |
56 | here reset the stream to the beginning before doing any work. |
57 | CAVEAT: The methods may not behave correctly if a user sets up |
58 | multiple threads and sucks data from the stream (returned from |
59 | getBinaryStream()) at the same time as calling the Blob methods. |
60 | |
61 | <P><B>Supports</B> |
62 | <UL> |
63 | <LI> JSR169 - no subsetting for java.sql.Blob |
64 | <LI> JDBC 2.0 |
65 | <LI> JDBC 3.0 - no new dependencies on new JDBC 3.0 or JDK 1.4 classes, |
66 | new update methods can safely be added into implementation. |
67 | </UL> |
68 | |
69 | */ |
70 | |
71 | final class EmbedBlob extends ConnectionChild implements Blob |
72 | { |
73 | // blob is either bytes or stream |
74 | private boolean isBytes; |
75 | private InputStream myStream; |
76 | |
77 | /* |
78 | * Length of the BLOB if known. Set to -1 if |
79 | * the current length of the BLOB is not known. |
80 | */ |
81 | private long myLength = -1; |
82 | |
83 | private byte[] myBytes; |
84 | // note: cannot control position of the stream since user can do a getBinaryStream |
85 | private long pos; |
86 | // this stream sits on top of myStream |
87 | private BinaryToRawStream biStream; |
88 | |
89 | // buffer for reading in blobs from a stream (long column) |
90 | // and trashing them (to set the position of the stream etc.) |
91 | private static int BLOB_BUF_SIZE = 4096; |
92 | private byte buf[]; |
93 | |
94 | //This boolean variable indicates whether the Blob object has |
95 | //been invalidated by calling free() on it |
96 | private boolean isValid = true; |
97 | |
98 | /** |
99 | * This constructor is used to create a empty Blob object. It is used by the |
100 | * Connection interface method createBlob(). |
101 | * |
102 | * @param blobBytes A byte array containing the data to be stores in the |
103 | * Blob. |
104 | * |
105 | * @param con The EmbedConnection object associated with this Blob object. |
106 | * |
107 | */ |
108 | |
109 | EmbedBlob(byte [] blobBytes,EmbedConnection con) { |
110 | super(con); |
111 | myBytes = blobBytes; |
112 | isBytes = true; |
113 | myLength = myBytes.length; |
114 | } |
115 | |
116 | /* |
117 | This constructor should only be called by EmbedResultSet.getBlob |
118 | */ |
119 | protected EmbedBlob(DataValueDescriptor dvd, EmbedConnection con) |
120 | throws StandardException |
121 | { |
122 | super(con); |
123 | // if the underlying column is null, ResultSet.getBlob will return null, |
124 | // never should get this far |
125 | if (SanityManager.DEBUG) |
126 | SanityManager.ASSERT(!dvd.isNull(), "blob is created on top of a null column"); |
127 | |
128 | myStream = dvd.getStream(); |
129 | if (myStream == null) |
130 | { |
131 | isBytes = true; |
132 | // copy bytes into memory so that blob can live after result set |
133 | // is closed |
134 | byte[] dvdBytes = dvd.getBytes(); |
135 | |
136 | if (SanityManager.DEBUG) |
137 | SanityManager.ASSERT(dvdBytes != null,"blob has a null value underneath"); |
138 | |
139 | myLength = dvdBytes.length; |
140 | myBytes = new byte[dvdBytes.length]; |
141 | System.arraycopy(dvdBytes, 0, myBytes, 0, dvdBytes.length); |
142 | } |
143 | else |
144 | { |
145 | isBytes = false; |
146 | |
147 | /* |
148 | We are expecting this stream to be a FormatIdInputStream with an |
149 | OverflowInputStream inside. FormatIdInputStream implements |
150 | Resetable. This should be the case when retrieving |
151 | data from a long column. However, SQLBit, which is the class |
152 | implementing the getStream() method for dvd.getStream(), does not |
153 | guarantee this for us |
154 | */ |
155 | if (SanityManager.DEBUG) |
156 | SanityManager.ASSERT(myStream instanceof Resetable); |
157 | |
158 | try { |
159 | ((Resetable) myStream).initStream(); |
160 | } catch (StandardException se) { |
161 | if (se.getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED)) { |
162 | throw StandardException |
163 | .newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT); |
164 | } |
165 | } |
166 | // set up the buffer for trashing the bytes to set the position of |
167 | // the |
168 | // stream, only need a buffer when we have a long column |
169 | buf = new byte[BLOB_BUF_SIZE]; |
170 | } |
171 | pos = 0; |
172 | } |
173 | |
174 | |
175 | /* |
176 | Sets the position of the stream to position newPos, where position 0 is |
177 | the beginning of the stream. |
178 | |
179 | @param newPos the position to set to |
180 | @exception StandardException (BLOB_SETPOSITION_FAILED) throws this if |
181 | the stream runs out before we get to newPos |
182 | */ |
183 | private void setPosition(long newPos) |
184 | throws StandardException, IOException |
185 | { |
186 | if (SanityManager.DEBUG) |
187 | SanityManager.ASSERT(newPos >= 0); |
188 | if (isBytes) |
189 | pos = newPos; |
190 | else |
191 | { |
192 | // Always resets the stream to the beginning first, because user can |
193 | // influence the state of the stream without letting us know. |
194 | ((Resetable)myStream).resetStream(); |
195 | // PT could try to save creating a new object each time |
196 | biStream = new BinaryToRawStream(myStream, this); |
197 | pos = 0; |
198 | while (pos < newPos) |
199 | { |
200 | int size = biStream.read( |
201 | buf,0,(int) Math.min((newPos-pos), (long) BLOB_BUF_SIZE)); |
202 | if (size <= 0) // ran out of stream |
203 | throw StandardException.newException(SQLState.BLOB_LENGTH_TOO_LONG); |
204 | pos += size; |
205 | } |
206 | } |
207 | } |
208 | |
209 | |
210 | /* |
211 | Reads one byte, either from the byte array or else from the stream. |
212 | */ |
213 | private int read() |
214 | throws IOException |
215 | { |
216 | int c; |
217 | if (isBytes) |
218 | { |
219 | if (pos >= myBytes.length) |
220 | return -1; |
221 | else |
222 | c = myBytes[(int) pos]; |
223 | } |
224 | else |
225 | c = biStream.read(); |
226 | pos++; |
227 | return c; |
228 | } |
229 | |
230 | /** |
231 | * Returns the number of bytes in the <code>BLOB</code> value |
232 | * designated by this <code>Blob</code> object. |
233 | * @return length of the <code>BLOB</code> in bytes |
234 | * @exception SQLException if there is an error accessing the |
235 | * length of the <code>BLOB</code> |
236 | */ |
237 | // PT stream part may get pushed to store |
238 | public long length() |
239 | throws SQLException |
240 | { |
241 | //call checkValidity to exit by throwing a SQLException if |
242 | //the Blob object has been freed by calling free() on it |
243 | checkValidity(); |
244 | |
245 | if (myLength != -1) |
246 | return myLength; |
247 | |
248 | boolean pushStack = false; |
249 | try |
250 | { |
251 | // we have a stream |
252 | synchronized (getConnectionSynchronization()) |
253 | { |
254 | pushStack = !getEmbedConnection().isClosed(); |
255 | if (pushStack) |
256 | setupContextStack(); |
257 | |
258 | setPosition(0); |
259 | // If possible get the length from the encoded |
260 | // length at the front of the raw stream. |
261 | if ((myLength = biStream.getLength()) != -1) { |
262 | biStream.close(); |
263 | return myLength; |
264 | } |
265 | |
266 | // Otherwise have to read the entire stream! |
267 | for (;;) |
268 | { |
269 | int size = biStream.read(buf); |
270 | if (size == -1) |
271 | break; |
272 | pos += size; |
273 | } |
274 | // Save for future uses. |
275 | myLength = pos; |
276 | biStream.close(); |
277 | return pos; |
278 | } |
279 | } |
280 | catch (Throwable t) |
281 | { |
282 | throw handleMyExceptions(t); |
283 | } |
284 | finally |
285 | { |
286 | if (pushStack) |
287 | restoreContextStack(); |
288 | } |
289 | } |
290 | |
291 | |
292 | /** |
293 | * Returns as an array of bytes part or all of the <code>BLOB</code> |
294 | * value that this <code>Blob</code> object designates. The byte |
295 | * array contains up to <code>length</code> consecutive bytes |
296 | * starting at position <code>pos</code>. |
297 | * @param startPos the ordinal position of the first byte in the |
298 | * <code>BLOB</code> value to be extracted; the first byte is at |
299 | * position 1 |
300 | * @param length is the number of consecutive bytes to be copied |
301 | * @return a byte array containing up to <code>length</code> |
302 | * consecutive bytes from the <code>BLOB</code> value designated |
303 | * by this <code>Blob</code> object, starting with the |
304 | * byte at position <code>pos</code>. |
305 | * @exception SQLException if there is an error accessing the |
306 | * <code>BLOB</code> |
307 | NOTE: return new byte[0] if startPos is too large |
308 | */ |
309 | // PT stream part may get pushed to store |
310 | |
311 | public byte[] getBytes(long startPos, int length) |
312 | throws SQLException |
313 | { |
314 | //call checkValidity to exit by throwing a SQLException if |
315 | //the Blob object has been freed by calling free() on it |
316 | checkValidity(); |
317 | |
318 | boolean pushStack = false; |
319 | try |
320 | { |
321 | if (startPos < 1) |
322 | throw StandardException.newException( |
323 | SQLState.BLOB_BAD_POSITION, new Long(startPos)); |
324 | if (length <= 0) |
325 | throw StandardException.newException( |
326 | SQLState.BLOB_NONPOSITIVE_LENGTH, new Integer(length)); |
327 | |
328 | byte[] result; |
329 | // if we have a byte array, not a stream |
330 | if (isBytes) |
331 | { |
332 | // if blob length is less than pos bytes, raise an exception |
333 | if (myBytes.length < startPos) |
334 | throw StandardException.newException( |
335 | SQLState.BLOB_POSITION_TOO_LARGE, new Long(startPos)); |
336 | // cannot go over length of array |
337 | int lengthFromPos = myBytes.length - (int) startPos + 1; |
338 | int actualLength = length > lengthFromPos ? lengthFromPos : length; |
339 | result = new byte[actualLength]; |
340 | System.arraycopy(myBytes, ((int) startPos) - 1, result, 0, actualLength); |
341 | } |
342 | else // we have a stream |
343 | { |
344 | synchronized (getConnectionSynchronization()) |
345 | { |
346 | pushStack = !getEmbedConnection().isClosed(); |
347 | if (pushStack) |
348 | setupContextStack(); |
349 | |
350 | setPosition(startPos-1); |
351 | // read length bytes into a string |
352 | result = new byte[length]; |
353 | int n = InputStreamUtil.readLoop(biStream,result,0,length); |
354 | pos += n; |
355 | /* |
356 | According to the spec, if there are only n < length bytes |
357 | to return, we should just return these bytes. Rather than |
358 | return them in an array of size length, where the trailing |
359 | bytes are not initialized, and the user cannot tell how |
360 | many bytes were actually returned, we should return an |
361 | array of n bytes. |
362 | */ |
363 | if (n < length) |
364 | { |
365 | byte[] result2 = new byte[n]; |
366 | System.arraycopy(result,0,result2,0,n); |
367 | return result2; |
368 | } |
369 | } |
370 | } |
371 | return result; |
372 | } |
373 | catch (StandardException e) |
374 | { // if this is a setPosition exception then we ran out of Blob |
375 | if (e.getMessageId().equals(SQLState.BLOB_LENGTH_TOO_LONG)) |
376 | e = StandardException.newException( |
377 | SQLState.BLOB_POSITION_TOO_LARGE, new Long(startPos)); |
378 | throw handleMyExceptions(e); |
379 | } |
380 | catch (Throwable t) |
381 | { |
382 | throw handleMyExceptions(t); |
383 | } |
384 | finally |
385 | { |
386 | if (pushStack) |
387 | restoreContextStack(); |
388 | } |
389 | |
390 | } |
391 | |
392 | |
393 | /** |
394 | * Retrieves the <code>BLOB</code> designated by this |
395 | * <code>Blob</code> instance as a stream. |
396 | * @return a stream containing the <code>BLOB</code> data |
397 | * @exception SQLException if there is an error accessing the |
398 | * <code>BLOB</code> |
399 | */ |
400 | public java.io.InputStream getBinaryStream() |
401 | throws SQLException |
402 | { |
403 | //call checkValidity to exit by throwing a SQLException if |
404 | //the Blob object has been freed by calling free() on it |
405 | checkValidity(); |
406 | |
407 | boolean pushStack = false; |
408 | try |
409 | { |
410 | // if we have byte array, not a stream |
411 | if (isBytes) |
412 | { |
413 | return new NewByteArrayInputStream(myBytes); |
414 | } |
415 | else |
416 | { |
417 | // have a stream |
418 | |
419 | synchronized (getConnectionSynchronization()) |
420 | { |
421 | pushStack = !getEmbedConnection().isClosed(); |
422 | if (pushStack) |
423 | setupContextStack(); |
424 | |
425 | setPosition(0); |
426 | return biStream; |
427 | } |
428 | } |
429 | } |
430 | catch (Throwable t) |
431 | { |
432 | throw handleMyExceptions(t); |
433 | } |
434 | finally |
435 | { |
436 | if (pushStack) |
437 | restoreContextStack(); |
438 | } |
439 | } |
440 | |
441 | |
442 | /** |
443 | * Determines the byte position at which the specified byte |
444 | * <code>pattern</code> begins within the <code>BLOB</code> |
445 | * value that this <code>Blob</code> object represents. The |
446 | * search for <code>pattern</code. begins at position |
447 | * <code>start</code> |
448 | * @param pattern the byte array for which to search |
449 | * @param start the position at which to begin searching; the |
450 | * first position is 1 |
451 | * @return the position at which the pattern appears, else -1. |
452 | * @exception SQLException if there is an error accessing the |
453 | * <code>BLOB</code> |
454 | */ |
455 | public long position(byte[] pattern, long start) |
456 | throws SQLException |
457 | { |
458 | //call checkValidity to exit by throwing a SQLException if |
459 | //the Blob object has been freed by calling free() on it |
460 | checkValidity(); |
461 | |
462 | boolean pushStack = false; |
463 | try |
464 | { |
465 | if (start < 1) |
466 | throw StandardException.newException( |
467 | SQLState.BLOB_BAD_POSITION, new Long(start)); |
468 | if (pattern == null) |
469 | throw StandardException.newException(SQLState.BLOB_NULL_PATTERN_OR_SEARCH_STR); |
470 | if (pattern.length == 0) |
471 | return start; // match DB2's SQL LOCATE function |
472 | |
473 | synchronized (getConnectionSynchronization()) |
474 | { |
475 | pushStack = !getEmbedConnection().isClosed(); |
476 | if (pushStack) |
477 | setupContextStack(); |
478 | |
479 | setPosition(start-1); |
480 | // look for first character |
481 | int lookFor = pattern[0]; |
482 | long curPos; |
483 | int c; |
484 | while (true) |
485 | { |
486 | c = read(); |
487 | if (c == -1) // run out of stream |
488 | return -1; |
489 | if (c == lookFor) |
490 | { |
491 | curPos = pos; |
492 | if (checkMatch(pattern)) |
493 | return curPos; |
494 | else |
495 | setPosition(curPos); |
496 | } |
497 | } |
498 | } |
499 | } |
500 | catch (StandardException e) |
501 | { // if this is a setPosition exception then not found |
502 | if (e.getMessageId().equals(SQLState.BLOB_LENGTH_TOO_LONG)) |
503 | return -1; |
504 | else |
505 | throw handleMyExceptions(e); |
506 | } |
507 | catch (Throwable t) |
508 | { |
509 | throw handleMyExceptions(t); |
510 | } |
511 | finally |
512 | { |
513 | if (pushStack) |
514 | restoreContextStack(); |
515 | } |
516 | |
517 | } |
518 | |
519 | |
520 | /* |
521 | check whether pattern (starting from the second byte) appears inside |
522 | posStream (at the current position) |
523 | @param posStream the stream to search inside |
524 | @param pattern the byte array passed in by the user to search with |
525 | @return true if match, false otherwise |
526 | */ |
527 | private boolean checkMatch(byte[] pattern) |
528 | throws IOException |
529 | { |
530 | // check whether rest matches |
531 | // might improve performance by reading more |
532 | for (int i = 1; i < pattern.length; i++) |
533 | { |
534 | int b = read(); |
535 | if ((b < 0) || (b != pattern[i])) // mismatch or stream runs out |
536 | return false; |
537 | } |
538 | return true; |
539 | } |
540 | |
541 | /** |
542 | * Determines the byte position in the <code>BLOB</code> value |
543 | * designated by this <code>Blob</code> object at which |
544 | * <code>pattern</code> begins. The search begins at position |
545 | * <code>start</code>. |
546 | * @param pattern the <code>Blob</code> object designating |
547 | * the <code>BLOB</code> value for which to search |
548 | * @param start the position in the <code>BLOB</code> value |
549 | * at which to begin searching; the first position is 1 |
550 | * @return the position at which the pattern begins, else -1 |
551 | * @exception SQLException if there is an error accessing the |
552 | * <code>BLOB</code> |
553 | */ |
554 | public long position(Blob pattern, long start) |
555 | throws SQLException |
556 | { |
557 | //call checkValidity to exit by throwing a SQLException if |
558 | //the Blob object has been freed by calling free() on it |
559 | checkValidity(); |
560 | |
561 | boolean pushStack = false; |
562 | try |
563 | { |
564 | if (start < 1) |
565 | throw StandardException.newException( |
566 | SQLState.BLOB_BAD_POSITION, new Long(start)); |
567 | if (pattern == null) |
568 | throw StandardException.newException(SQLState.BLOB_NULL_PATTERN_OR_SEARCH_STR); |
569 | synchronized (getConnectionSynchronization()) |
570 | { |
571 | pushStack = !getEmbedConnection().isClosed(); |
572 | if (pushStack) |
573 | setupContextStack(); |
574 | |
575 | setPosition(start-1); |
576 | // look for first character |
577 | byte[] b; |
578 | try |
579 | { // pattern is not necessarily a cloudscape Blob |
580 | b = pattern.getBytes(1,1); |
581 | } |
582 | catch (SQLException e) |
583 | { |
584 | throw StandardException.newException(SQLState.BLOB_UNABLE_TO_READ_PATTERN); |
585 | } |
586 | if (b == null || b.length < 1) // the 'empty' blob |
587 | return start; // match DB2's SQL LOCATE function |
588 | int lookFor = b[0]; |
589 | int c; |
590 | long curPos; |
591 | while (true) |
592 | { |
593 | c = read(); |
594 | if (c == -1) // run out of stream |
595 | return -1; |
596 | if (c == lookFor) |
597 | { |
598 | curPos = pos; |
599 | if (checkMatch(pattern)) |
600 | return curPos; |
601 | else |
602 | setPosition(curPos); |
603 | } |
604 | } |
605 | } |
606 | } |
607 | catch (StandardException e) |
608 | { // if this is a setPosition exception then not found |
609 | if (e.getMessageId().equals(SQLState.BLOB_LENGTH_TOO_LONG)) |
610 | return -1; |
611 | else |
612 | throw handleMyExceptions(e); |
613 | } |
614 | catch (Throwable t) |
615 | { |
616 | throw handleMyExceptions(t); |
617 | } |
618 | finally |
619 | { |
620 | if (pushStack) |
621 | restoreContextStack(); |
622 | } |
623 | |
624 | } |
625 | |
626 | |
627 | /* |
628 | check whether pattern (starting from the second byte) appears inside |
629 | posStream (at the current position) |
630 | @param posStream the stream to search inside |
631 | @param pattern the blob passed in by the user to search with |
632 | @return true if match, false otherwise |
633 | */ |
634 | private boolean checkMatch(Blob pattern) |
635 | throws IOException |
636 | { |
637 | // check whether rest matches |
638 | // might improve performance by reading buffer at a time |
639 | InputStream pStream; |
640 | try |
641 | { |
642 | pStream = pattern.getBinaryStream(); |
643 | } |
644 | catch (SQLException e) |
645 | { |
646 | return false; |
647 | } |
648 | if (pStream == null) |
649 | return false; |
650 | // throw away first character since we already read it in the calling |
651 | // method |
652 | int b1 = pStream.read(); |
653 | if (b1 < 0) |
654 | return false; |
655 | while (true) |
656 | { |
657 | b1 = pStream.read(); |
658 | if (b1 < 0) // search blob runs out |
659 | return true; |
660 | int b2 = read(); |
661 | if ((b1 != b2) || (b2 < 0)) // mismatch or stream runs out |
662 | return false; |
663 | } |
664 | } |
665 | |
666 | /* |
667 | Convert exceptions where needed before calling handleException to convert |
668 | them to SQLExceptions. |
669 | */ |
670 | private SQLException handleMyExceptions(Throwable t) |
671 | throws SQLException |
672 | { |
673 | if (t instanceof StandardException) |
674 | { |
675 | // container closed means the blob or clob was accessed after commit |
676 | if (((StandardException) t).getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED)) |
677 | { |
678 | t = StandardException.newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT); |
679 | } |
680 | } |
681 | return handleException(t); |
682 | } |
683 | |
684 | |
685 | /* |
686 | If we have a stream, release the resources associated with it. |
687 | */ |
688 | protected void finalize() |
689 | { |
690 | if (!isBytes) |
691 | ((Resetable)myStream).closeStream(); |
692 | } |
693 | |
694 | /** |
695 | Following methods are for the new JDBC 3.0 methods in java.sql.Blob |
696 | (see the JDBC 3.0 spec). We have the JDBC 3.0 methods in Local20 |
697 | package, so we don't have to have a new class in Local30. |
698 | The new JDBC 3.0 methods don't make use of any new JDBC3.0 classes and |
699 | so this will work fine in jdbc2.0 configuration. |
700 | */ |
701 | |
702 | ///////////////////////////////////////////////////////////////////////// |
703 | // |
704 | // JDBC 3.0 - New public methods |
705 | // |
706 | ///////////////////////////////////////////////////////////////////////// |
707 | |
708 | /** |
709 | * JDBC 3.0 |
710 | * |
711 | * Writes the given array of bytes to the BLOB value that this Blob object |
712 | * represents, starting at position pos, and returns the number of bytes written. |
713 | * |
714 | * @param pos - the position in the BLOB object at which to start writing |
715 | * @param bytes - the array of bytes to be written to the BLOB value that this |
716 | * Blob object represents |
717 | * @return the number of bytes written |
718 | * @exception SQLException Feature not implemented for now. |
719 | */ |
720 | public int setBytes(long pos, |
721 | byte[] bytes) |
722 | throws SQLException |
723 | { |
724 | throw Util.notImplemented(); |
725 | } |
726 | |
727 | /** |
728 | * JDBC 3.0 |
729 | * |
730 | * Writes all or part of the given array of byte array to the BLOB value that |
731 | * this Blob object represents and returns the number of bytes written. |
732 | * Writing starts at position pos in the BLOB value; len bytes from the given |
733 | * byte array are written. |
734 | * |
735 | * @param pos - the position in the BLOB object at which to start writing |
736 | * @param bytes - the array of bytes to be written to the BLOB value that this |
737 | * Blob object represents |
738 | * @param offset - the offset into the array bytes at which to start reading |
739 | * the bytes to be set |
740 | * @param len - the number of bytes to be written to the BLOB value from the |
741 | * array of bytes bytes |
742 | * @return the number of bytes written |
743 | * @exception SQLException Feature not implemented for now. |
744 | */ |
745 | public int setBytes(long pos, |
746 | byte[] bytes, int offset, |
747 | int len) |
748 | throws SQLException |
749 | { |
750 | throw Util.notImplemented(); |
751 | } |
752 | |
753 | /** |
754 | * JDBC 3.0 |
755 | * |
756 | * Retrieves a stream that can be used to write to the BLOB value that this |
757 | * Blob object represents. The stream begins at position pos. |
758 | * |
759 | * @param pos - the position in the BLOB object at which to start writing |
760 | * @return a java.io.OutputStream object to which data can be written |
761 | * @exception SQLException Feature not implemented for now. |
762 | */ |
763 | public java.io.OutputStream setBinaryStream(long pos) |
764 | throws SQLException |
765 | { |
766 | throw Util.notImplemented(); |
767 | } |
768 | |
769 | /** |
770 | * JDBC 3.0 |
771 | * |
772 | * Truncates the BLOB value that this Blob object represents to be len bytes |
773 | * in length. |
774 | * |
775 | * @param len - the length, in bytes, to which the BLOB value that this Blob |
776 | * object represents should be truncated |
777 | * @exception SQLException Feature not implemented for now. |
778 | */ |
779 | public void truncate(long len) |
780 | throws SQLException |
781 | { |
782 | throw Util.notImplemented(); |
783 | } |
784 | |
785 | ///////////////////////////////////////////////////////////////////////// |
786 | // |
787 | // JDBC 4.0 - New public methods |
788 | // |
789 | ///////////////////////////////////////////////////////////////////////// |
790 | /** |
791 | * This method frees the <code>Blob</code> object and releases the resources that |
792 | * it holds. The object is invalid once the <code>free</code> |
793 | * method is called. If <code>free</code> is called multiple times, the subsequent |
794 | * calls to <code>free</code> are treated as a no-op. |
795 | * |
796 | * @throws SQLException if an error occurs releasing |
797 | * the Blob's resources |
798 | */ |
799 | public void free() |
800 | throws SQLException { |
801 | //calling free() on a already freed object is treated as a no-op |
802 | if (!isValid) return; |
803 | |
804 | //now that free has been called the Blob object is no longer |
805 | //valid |
806 | isValid = false; |
807 | |
808 | //initialialize length to default value -1 |
809 | myLength = -1; |
810 | |
811 | //if it is a stream then close it. |
812 | //if a array of bytes then initialize it to null |
813 | //to free up space |
814 | if (!isBytes) |
815 | ((Resetable)myStream).closeStream(); |
816 | else |
817 | myBytes = null; |
818 | } |
819 | |
820 | /** |
821 | * Returns an <code>InputStream</code> object that contains a partial |
822 | * <code>Blob</code> value, starting with the byte specified by pos, |
823 | * which is length bytes in length. |
824 | * |
825 | * @param pos the offset to the first byte of the partial value to be |
826 | * retrieved. The first byte in the <code>Blob</code> is at |
827 | * position 1 |
828 | * @param length the length in bytes of the partial value to be retrieved |
829 | * @return through which the partial <code>Blob</code> value can be read. |
830 | * @throws SQLException if pos is less than 1 or if pos is greater than |
831 | * the number of bytes in the <code>Blob</code> or if pos + length is |
832 | * greater than the number of bytes in the <code>Blob</code> |
833 | */ |
834 | public InputStream getBinaryStream(long pos, long length) |
835 | throws SQLException { |
836 | throw Util.notImplemented(); |
837 | } |
838 | |
839 | /* |
840 | * Checks is isValid is true. If it is not true throws |
841 | * a SQLException stating that a method has been called on |
842 | * an invalid LOB object |
843 | * |
844 | * throws SQLException if isvalid is not true. |
845 | */ |
846 | private void checkValidity() throws SQLException{ |
847 | if(!isValid) |
848 | throw newSQLException(SQLState.LOB_OBJECT_INVALID); |
849 | } |
850 | } |