Source code: org/apache/derby/impl/jdbc/EmbedBlob.java
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 // clob is either bytes or stream
74 private boolean isBytes;
75 private InputStream myStream;
76 private byte[] myBytes;
77 // note: cannot control position of the stream since user can do a getBinaryStream
78 private long pos;
79 // this stream sits on top of myStream
80 private BinaryToRawStream biStream;
81
82 // buffer for reading in blobs from a stream (long column)
83 // and trashing them (to set the position of the stream etc.)
84 private static int BLOB_BUF_SIZE = 4096;
85 private byte buf[];
86
87 /*
88 This constructor should only be called by EmbedResultSet.getBlob
89 */
90 protected EmbedBlob(DataValueDescriptor dvd, EmbedConnection con)
91 throws StandardException
92 {
93 super(con);
94 // if the underlying column is null, ResultSet.getBlob will return null,
95 // never should get this far
96 if (SanityManager.DEBUG)
97 SanityManager.ASSERT(!dvd.isNull(), "blob is created on top of a null column");
98
99 myStream = dvd.getStream();
100 if (myStream == null)
101 {
102 isBytes = true;
103 // copy bytes into memory so that blob can live after result set
104 // is closed
105 byte[] dvdBytes = dvd.getBytes();
106
107 if (SanityManager.DEBUG)
108 SanityManager.ASSERT(dvdBytes != null,"blob has a null value underneath");
109
110 myBytes = new byte[dvdBytes.length];
111 System.arraycopy(dvdBytes, 0, myBytes, 0, dvdBytes.length);
112 }
113 else
114 {
115 isBytes = false;
116
117 /*
118 We are expecting this stream to be a FormatIdInputStream with an
119 OverflowInputStream inside. FormatIdInputStream implements
120 Resetable. This should be the case when retrieving
121 data from a long column. However, SQLBit, which is the class
122 implementing the getStream() method for dvd.getStream(), does not
123 guarantee this for us
124 */
125 if (SanityManager.DEBUG)
126 SanityManager.ASSERT(myStream instanceof Resetable);
127
128 try {
129 ((Resetable) myStream).initStream();
130 } catch (StandardException se) {
131 if (se.getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED)) {
132 throw StandardException
133 .newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);
134 }
135 }
136 // set up the buffer for trashing the bytes to set the position of
137 // the
138 // stream, only need a buffer when we have a long column
139 buf = new byte[BLOB_BUF_SIZE];
140 }
141 pos = 0;
142 }
143
144
145 /*
146 Sets the position of the stream to position newPos, where position 0 is
147 the beginning of the stream.
148
149 @param newPos the position to set to
150 @exception StandardException (BLOB_SETPOSITION_FAILED) throws this if
151 the stream runs out before we get to newPos
152 */
153 private void setPosition(long newPos)
154 throws StandardException, IOException
155 {
156 if (SanityManager.DEBUG)
157 SanityManager.ASSERT(newPos >= 0);
158 if (isBytes)
159 pos = newPos;
160 else
161 {
162 // Always resets the stream to the beginning first, because user can
163 // influence the state of the stream without letting us know.
164 ((Resetable)myStream).resetStream();
165 // PT could try to save creating a new object each time
166 biStream = new BinaryToRawStream(myStream, this);
167 pos = 0;
168 while (pos < newPos)
169 {
170 int size = biStream.read(
171 buf,0,(int) Math.min((newPos-pos), (long) BLOB_BUF_SIZE));
172 if (size <= 0) // ran out of stream
173 throw StandardException.newException(SQLState.BLOB_SETPOSITION_FAILED);
174 pos += size;
175 }
176 }
177 }
178
179
180 /*
181 Reads one byte, either from the byte array or else from the stream.
182 */
183 private int read()
184 throws IOException
185 {
186 int c;
187 if (isBytes)
188 {
189 if (pos >= myBytes.length)
190 return -1;
191 else
192 c = myBytes[(int) pos];
193 }
194 else
195 c = biStream.read();
196 pos++;
197 return c;
198 }
199
200 /**
201 * Returns the number of bytes in the <code>BLOB</code> value
202 * designated by this <code>Blob</code> object.
203 * @return length of the <code>BLOB</code> in bytes
204 * @exception SQLException if there is an error accessing the
205 * length of the <code>BLOB</code>
206 */
207 // PT stream part may get pushed to store
208 public long length()
209 throws SQLException
210 {
211 boolean pushStack = false;
212 try
213 {
214 if (isBytes)
215 return myBytes.length;
216 // we have a stream
217 synchronized (getConnectionSynchronization())
218 {
219 pushStack = !getEmbedConnection().isClosed();
220 if (pushStack)
221 setupContextStack();
222
223 setPosition(0);
224 for (;;)
225 {
226 int size = biStream.read(buf);
227 if (size == -1)
228 break;
229 pos += size;
230 }
231 return pos;
232 }
233 }
234 catch (Throwable t)
235 {
236 throw handleMyExceptions(t);
237 }
238 finally
239 {
240 if (pushStack)
241 restoreContextStack();
242 }
243 }
244
245
246 /**
247 * Returns as an array of bytes part or all of the <code>BLOB</code>
248 * value that this <code>Blob</code> object designates. The byte
249 * array contains up to <code>length</code> consecutive bytes
250 * starting at position <code>pos</code>.
251 * @param pos the ordinal position of the first byte in the
252 * <code>BLOB</code> value to be extracted; the first byte is at
253 * position 1
254 * @param length is the number of consecutive bytes to be copied
255 * @return a byte array containing up to <code>length</code>
256 * consecutive bytes from the <code>BLOB</code> value designated
257 * by this <code>Blob</code> object, starting with the
258 * byte at position <code>pos</code>.
259 * @exception SQLException if there is an error accessing the
260 * <code>BLOB</code>
261 NOTE: return new byte[0] if startPos is too large
262 */
263 // PT stream part may get pushed to store
264
265 public byte[] getBytes(long startPos, int length)
266 throws SQLException
267 {
268 boolean pushStack = false;
269 try
270 {
271 if (startPos < 1)
272 throw StandardException.newException(
273 SQLState.BLOB_BAD_POSITION, new Long(startPos));
274 if (length <= 0)
275 throw StandardException.newException(
276 SQLState.BLOB_NONPOSITIVE_LENGTH, new Integer(length));
277
278 byte[] result;
279 // if we have a byte array, not a stream
280 if (isBytes)
281 {
282 // if blob length is less than pos bytes, raise an exception
283 if (myBytes.length < startPos)
284 throw StandardException.newException(
285 SQLState.BLOB_POSITION_TOO_LARGE, new Long(startPos));
286 // cannot go over length of array
287 int lengthFromPos = myBytes.length - (int) startPos + 1;
288 int actualLength = length > lengthFromPos ? lengthFromPos : length;
289 result = new byte[actualLength];
290 System.arraycopy(myBytes, ((int) startPos) - 1, result, 0, actualLength);
291 }
292 else // we have a stream
293 {
294 synchronized (getConnectionSynchronization())
295 {
296 pushStack = !getEmbedConnection().isClosed();
297 if (pushStack)
298 setupContextStack();
299
300 setPosition(startPos-1);
301 // read length bytes into a string
302 result = new byte[length];
303 int n = InputStreamUtil.readLoop(biStream,result,0,length);
304 pos += n;
305 /*
306 According to the spec, if there are only n < length bytes
307 to return, we should just return these bytes. Rather than
308 return them in an array of size length, where the trailing
309 bytes are not initialized, and the user cannot tell how
310 many bytes were actually returned, we should return an
311 array of n bytes.
312 */
313 if (n < length)
314 {
315 byte[] result2 = new byte[n];
316 System.arraycopy(result,0,result2,0,n);
317 return result2;
318 }
319 }
320 }
321 return result;
322 }
323 catch (StandardException e)
324 { // if this is a setPosition exception then we ran out of Blob
325 if (e.getMessageId().equals(SQLState.BLOB_SETPOSITION_FAILED))
326 e = StandardException.newException(
327 SQLState.BLOB_POSITION_TOO_LARGE, new Long(startPos));
328 throw handleMyExceptions(e);
329 }
330 catch (Throwable t)
331 {
332 throw handleMyExceptions(t);
333 }
334 finally
335 {
336 if (pushStack)
337 restoreContextStack();
338 }
339
340 }
341
342
343 /**
344 * Retrieves the <code>BLOB</code> designated by this
345 * <code>Blob</code> instance as a stream.
346 * @return a stream containing the <code>BLOB</code> data
347 * @exception SQLException if there is an error accessing the
348 * <code>BLOB</code>
349 */
350 public java.io.InputStream getBinaryStream()
351 throws SQLException
352 {
353 boolean pushStack = false;
354 try
355 {
356 // if we have byte array, not a stream
357 if (isBytes)
358 {
359 return new NewByteArrayInputStream(myBytes);
360 }
361 else
362 {
363 // have a stream
364
365 synchronized (getConnectionSynchronization())
366 {
367 pushStack = !getEmbedConnection().isClosed();
368 if (pushStack)
369 setupContextStack();
370
371 setPosition(0);
372 return biStream;
373 }
374 }
375 }
376 catch (Throwable t)
377 {
378 throw handleMyExceptions(t);
379 }
380 finally
381 {
382 if (pushStack)
383 restoreContextStack();
384 }
385 }
386
387
388 /**
389 * Determines the byte position at which the specified byte
390 * <code>pattern</code> begins within the <code>BLOB</code>
391 * value that this <code>Blob</code> object represents. The
392 * search for <code>pattern</code. begins at position
393 * <code>start</code>
394 * @param pattern the byte array for which to search
395 * @param start the position at which to begin searching; the
396 * first position is 1
397 * @return the position at which the pattern appears, else -1.
398 * @exception SQLException if there is an error accessing the
399 * <code>BLOB</code>
400 */
401 public long position(byte[] pattern, long start)
402 throws SQLException
403 {
404 boolean pushStack = false;
405 try
406 {
407 if (start < 1)
408 throw StandardException.newException(
409 SQLState.BLOB_BAD_POSITION, new Long(start));
410 if (pattern == null)
411 throw StandardException.newException(SQLState.BLOB_NULL_PATTERN);
412 if (pattern.length == 0)
413 return start; // match DB2's SQL LOCATE function
414
415 synchronized (getConnectionSynchronization())
416 {
417 pushStack = !getEmbedConnection().isClosed();
418 if (pushStack)
419 setupContextStack();
420
421 setPosition(start-1);
422 // look for first character
423 int lookFor = pattern[0];
424 long curPos;
425 int c;
426 while (true)
427 {
428 c = read();
429 if (c == -1) // run out of stream
430 return -1;
431 if (c == lookFor)
432 {
433 curPos = pos;
434 if (checkMatch(pattern))
435 return curPos;
436 else
437 setPosition(curPos);
438 }
439 }
440 }
441 }
442 catch (StandardException e)
443 { // if this is a setPosition exception then not found
444 if (e.getMessageId().equals(SQLState.BLOB_SETPOSITION_FAILED))
445 return -1;
446 else
447 throw handleMyExceptions(e);
448 }
449 catch (Throwable t)
450 {
451 throw handleMyExceptions(t);
452 }
453 finally
454 {
455 if (pushStack)
456 restoreContextStack();
457 }
458
459 }
460
461
462 /*
463 check whether pattern (starting from the second byte) appears inside
464 posStream (at the current position)
465 @param posStream the stream to search inside
466 @param pattern the byte array passed in by the user to search with
467 @return true if match, false otherwise
468 */
469 private boolean checkMatch(byte[] pattern)
470 throws IOException
471 {
472 // check whether rest matches
473 // might improve performance by reading more
474 for (int i = 1; i < pattern.length; i++)
475 {
476 int b = read();
477 if ((b < 0) || (b != pattern[i])) // mismatch or stream runs out
478 return false;
479 }
480 return true;
481 }
482
483 /**
484 * Determines the byte position in the <code>BLOB</code> value
485 * designated by this <code>Blob</code> object at which
486 * <code>pattern</code> begins. The search begins at position
487 * <code>start</code>.
488 * @param pattern the <code>Blob</code> object designating
489 * the <code>BLOB</code> value for which to search
490 * @param start the position in the <code>BLOB</code> value
491 * at which to begin searching; the first position is 1
492 * @return the position at which the pattern begins, else -1
493 * @exception SQLException if there is an error accessing the
494 * <code>BLOB</code>
495 */
496 public long position(Blob pattern, long start)
497 throws SQLException
498 {
499 boolean pushStack = false;
500 try
501 {
502 if (start < 1)
503 throw StandardException.newException(
504 SQLState.BLOB_BAD_POSITION, new Long(start));
505 if (pattern == null)
506 throw StandardException.newException(SQLState.BLOB_NULL_PATTERN);
507 synchronized (getConnectionSynchronization())
508 {
509 pushStack = !getEmbedConnection().isClosed();
510 if (pushStack)
511 setupContextStack();
512
513 setPosition(start-1);
514 // look for first character
515 byte[] b;
516 try
517 { // pattern is not necessarily a cloudscape Blob
518 b = pattern.getBytes(1,1);
519 }
520 catch (SQLException e)
521 {
522 throw StandardException.newException(SQLState.BLOB_UNABLE_TO_READ_PATTERN);
523 }
524 if (b == null || b.length < 1) // the 'empty' blob
525 return start; // match DB2's SQL LOCATE function
526 int lookFor = b[0];
527 int c;
528 long curPos;
529 while (true)
530 {
531 c = read();
532 if (c == -1) // run out of stream
533 return -1;
534 if (c == lookFor)
535 {
536 curPos = pos;
537 if (checkMatch(pattern))
538 return curPos;
539 else
540 setPosition(curPos);
541 }
542 }
543 }
544 }
545 catch (StandardException e)
546 { // if this is a setPosition exception then not found
547 if (e.getMessageId().equals(SQLState.BLOB_SETPOSITION_FAILED))
548 return -1;
549 else
550 throw handleMyExceptions(e);
551 }
552 catch (Throwable t)
553 {
554 throw handleMyExceptions(t);
555 }
556 finally
557 {
558 if (pushStack)
559 restoreContextStack();
560 }
561
562 }
563
564
565 /*
566 check whether pattern (starting from the second byte) appears inside
567 posStream (at the current position)
568 @param posStream the stream to search inside
569 @param pattern the blob passed in by the user to search with
570 @return true if match, false otherwise
571 */
572 private boolean checkMatch(Blob pattern)
573 throws IOException
574 {
575 // check whether rest matches
576 // might improve performance by reading buffer at a time
577 InputStream pStream;
578 try
579 {
580 pStream = pattern.getBinaryStream();
581 }
582 catch (SQLException e)
583 {
584 return false;
585 }
586 if (pStream == null)
587 return false;
588 // throw away first character since we already read it in the calling
589 // method
590 int b1 = pStream.read();
591 if (b1 < 0)
592 return false;
593 while (true)
594 {
595 b1 = pStream.read();
596 if (b1 < 0) // search blob runs out
597 return true;
598 int b2 = read();
599 if ((b1 != b2) || (b2 < 0)) // mismatch or stream runs out
600 return false;
601 }
602 }
603
604 /*
605 Convert exceptions where needed before calling handleException to convert
606 them to SQLExceptions.
607 */
608 private SQLException handleMyExceptions(Throwable t)
609 throws SQLException
610 {
611 if (t instanceof StandardException)
612 {
613 // container closed means the blob or clob was accessed after commit
614 if (((StandardException) t).getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED))
615 {
616 t = StandardException.newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);
617 }
618 }
619 return handleException(t);
620 }
621
622
623 /*
624 If we have a stream, release the resources associated with it.
625 */
626 protected void finalize()
627 {
628 if (!isBytes)
629 ((Resetable)myStream).closeStream();
630 }
631
632 /**
633 Following methods are for the new JDBC 3.0 methods in java.sql.Blob
634 (see the JDBC 3.0 spec). We have the JDBC 3.0 methods in Local20
635 package, so we don't have to have a new class in Local30.
636 The new JDBC 3.0 methods don't make use of any new JDBC3.0 classes and
637 so this will work fine in jdbc2.0 configuration.
638 */
639
640 /////////////////////////////////////////////////////////////////////////
641 //
642 // JDBC 3.0 - New public methods
643 //
644 /////////////////////////////////////////////////////////////////////////
645
646 /**
647 * JDBC 3.0
648 *
649 * Writes the given array of bytes to the BLOB value that this Blob object
650 * represents, starting at position pos, and returns the number of bytes written.
651 *
652 * @param pos - the position in the BLOB object at which to start writing
653 * @param bytes - the array of bytes to be written to the BLOB value that this
654 * Blob object represents
655 * @return the number of bytes written
656 * @exception SQLException Feature not implemented for now.
657 */
658 public int setBytes(long pos,
659 byte[] bytes)
660 throws SQLException
661 {
662 throw Util.notImplemented();
663 }
664
665 /**
666 * JDBC 3.0
667 *
668 * Writes all or part of the given array of byte array to the BLOB value that
669 * this Blob object represents and returns the number of bytes written.
670 * Writing starts at position pos in the BLOB value; len bytes from the given
671 * byte array are written.
672 *
673 * @param pos - the position in the BLOB object at which to start writing
674 * @param bytes - the array of bytes to be written to the BLOB value that this
675 * Blob object represents
676 * @param offset - the offset into the array bytes at which to start reading
677 * the bytes to be set
678 * @param len - the number of bytes to be written to the BLOB value from the
679 * array of bytes bytes
680 * @return the number of bytes written
681 * @exception SQLException Feature not implemented for now.
682 */
683 public int setBytes(long pos,
684 byte[] bytes, int offset,
685 int len)
686 throws SQLException
687 {
688 throw Util.notImplemented();
689 }
690
691 /**
692 * JDBC 3.0
693 *
694 * Retrieves a stream that can be used to write to the BLOB value that this
695 * Blob object represents. The stream begins at position pos.
696 *
697 * @param pos - the position in the BLOB object at which to start writing
698 * @return a java.io.OutputStream object to which data can be written
699 * @exception SQLException Feature not implemented for now.
700 */
701 public java.io.OutputStream setBinaryStream(long pos)
702 throws SQLException
703 {
704 throw Util.notImplemented();
705 }
706
707 /**
708 * JDBC 3.0
709 *
710 * Truncates the BLOB value that this Blob object represents to be len bytes
711 * in length.
712 *
713 * @param len - the length, in bytes, to which the BLOB value that this Blob
714 * object represents should be truncated
715 * @exception SQLException Feature not implemented for now.
716 */
717 public void truncate(long len)
718 throws SQLException
719 {
720 throw Util.notImplemented();
721 }
722
723 }
724
725
726