Source code: org/apache/derby/impl/jdbc/EmbedClob.java
1 /*
2
3 Derby - Class org.apache.derby.impl.jdbc.EmbedClob
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.impl.jdbc.UTF8Reader;
33 import org.apache.derby.impl.jdbc.ReaderToAscii;
34
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.io.StringReader;
38 import java.io.Reader;
39 import java.io.IOException;
40 import java.io.EOFException;
41 import java.sql.SQLException;
42 import java.sql.Clob;
43
44 /**
45 Implements java.sql.Clob (see the JDBC 2.0 spec).
46 A clob sits on top of a CHAR, VARCHAR or LONG VARCHAR column.
47 If its data is small (less than 1 page) it is a byte array taken from
48 the SQLChar class. If it is large (more than 1 page) it is a long column
49 in the database. The long column is accessed as a stream, and is implemented
50 in store as an OverflowInputStream. The Resetable interface allows sending
51 messages to that stream to initialize itself (reopen its container and
52 lock the corresponding row) and to reset itself to the beginning.
53
54 NOTE: In the case that the data is large, it is represented as a stream.
55 This stream can be returned to the user in the getAsciiStream() method.
56 This means that we have limited control over the state of the stream,
57 since the user can read bytes from it at any time. Thus all methods
58 here reset the stream to the beginning before doing any work.
59 CAVEAT: The methods may not behave correctly if a user sets up
60 multiple threads and sucks data from the stream (returned from
61 getAsciiStream()) at the same time as calling the Clob methods.
62
63 <P><B>Supports</B>
64 <UL>
65 <LI> JSR169 - no subsetting for java.sql.Clob
66 <LI> JDBC 2.0
67 <LI> JDBC 3.0 - no new dependencies on new JDBC 3.0 or JDK 1.4 classes,
68 new update methods can safely be added into implementation.
69 </UL>
70 */
71 final class EmbedClob extends ConnectionChild implements Clob
72 {
73 // clob is either a string or stream
74 private boolean isString;
75 private InputStream myStream;
76 private String myString;
77
78 /*
79 This constructor should only be called by EmbedResultSet.getClob
80 */
81 protected EmbedClob(DataValueDescriptor dvd, EmbedConnection con)
82 throws StandardException
83 {
84 super(con);
85 // if the underlying column is null, ResultSet.getClob will return null,
86 // never should get this far
87 if (SanityManager.DEBUG)
88 SanityManager.ASSERT(!dvd.isNull(), "clob is created on top of a null column");
89
90 myStream = dvd.getStream();
91 if (myStream == null)
92 {
93 isString = true;
94 myString = dvd.getString();
95 if (SanityManager.DEBUG)
96 SanityManager.ASSERT(myString != null,"clob has a null value underneath");
97 }
98 else
99 {
100 /*
101 We are expecting this stream to be a FormatIdInputStream with an
102 OverflowInputStream inside. FormatIdInputStream implements
103 Resetable, as does OverflowInputStream. This should be the case
104 when retrieving data from a long column. However, SQLChar, which is
105 the class implementing the getStream() method for dvd.getStream(),
106 does not guarantee this for us. In particular, the logging system
107 (see StoredPage.logColumn) calls setStream with an argument that
108 is sometimes a RememberBytesInputStream on a SQLChar object
109 (e.g. see test repStreaming.sql). However, such a SQLChar
110 object is going to the log buffer, NOT back to the user, so it
111 should not break the ASSERT below.
112 */
113 if (SanityManager.DEBUG)
114 SanityManager.ASSERT(myStream instanceof Resetable);
115
116 try {
117 ((Resetable) myStream).initStream();
118 } catch (StandardException se) {
119 if (se.getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED)) {
120 throw StandardException
121 .newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);
122 }
123 }
124 }
125 }
126
127
128 /**
129 * Returns the number of characters
130 * in the <code>CLOB</code> value
131 * designated by this <code>Clob</code> object.
132 * @return length of the <code>CLOB</code> in characters
133 * @exception SQLException if there is an error accessing the
134 * length of the <code>CLOB</code>
135 */
136
137 public long length() throws SQLException
138 {
139 // if we have a string, not a stream
140 if (isString)
141 return myString.length();
142
143
144 Object synchronization = getConnectionSynchronization();
145 synchronized (synchronization)
146 {
147 Reader clobReader = null;
148 setupContextStack();
149 try {
150
151 clobReader = getCharacterStream();
152 long clobLength = 0;
153 for (;;)
154 {
155 long size = clobReader.skip(32 * 1024);
156 if (size == -1)
157 break;
158 clobLength += size;
159 }
160 clobReader.close();
161 clobReader = null;
162
163 return clobLength;
164 }
165 catch (Throwable t)
166 {
167 throw noStateChangeLOB(t);
168 }
169 finally
170 {
171 if (clobReader != null) {
172 try {
173 clobReader.close();
174 } catch (IOException ioe) {
175 }
176 }
177 restoreContextStack();
178 }
179 }
180 }
181
182 /**
183 * Returns a copy of the specified substring
184 * in the <code>CLOB</code> value
185 * designated by this <code>Clob</code> object.
186 * The substring begins at position
187 * <code>pos</code> and has up to <code>length</code> consecutive
188 * characters.
189 * @param pos the first character of the substring to be extracted.
190 * The first character is at position 1.
191 * @param length the number of consecutive characters to be copied
192 * @return a <code>String</code> that is the specified substring in
193 * the <code>CLOB</code> value designated by this <code>Clob</code> object
194 * @exception SQLException if there is an error accessing the
195 * <code>CLOB</code>
196
197 NOTE: return the empty string if pos is too large
198 */
199
200 public String getSubString(long pos, int length) throws SQLException
201 {
202 if (pos < 1)
203 throw Util.generateCsSQLException(
204 SQLState.BLOB_BAD_POSITION, new Long(pos));
205 if (length <= 0)
206 throw Util.generateCsSQLException(
207 SQLState.BLOB_NONPOSITIVE_LENGTH, new Integer(length));
208
209 // if we have a string, not a stream
210 if (isString)
211 {
212 int sLength = myString.length();
213 if (sLength < pos)
214 throw Util.generateCsSQLException(
215 SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos));
216 int endIndex = ((int) pos) + length - 1;
217 // cannot go over length of string, or we get an exception
218 return myString.substring(((int) pos) - 1, (sLength > endIndex ? endIndex : sLength));
219 }
220
221 Object synchronization = getConnectionSynchronization();
222 synchronized (synchronization)
223 {
224 setupContextStack();
225
226 UTF8Reader clobReader = null;
227 try {
228
229 clobReader = getCharacterStreamAtPos(pos, synchronization);
230 if (clobReader == null)
231 throw StandardException.newException(SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos));
232
233 StringBuffer sb = new StringBuffer(length);
234 int remainToRead = length;
235 while (remainToRead > 0) {
236
237 int read = clobReader.readInto(sb, remainToRead);
238 if (read == -1)
239 break;
240
241 remainToRead -= read;
242 }
243 clobReader.close();
244 clobReader = null;
245
246 return sb.toString();
247 }
248 catch (Throwable t)
249 {
250 throw noStateChangeLOB(t);
251 }
252 finally
253 {
254 if (clobReader != null)
255 clobReader.close();
256 restoreContextStack();
257 }
258 }
259 }
260
261
262 /**
263 * Gets the <code>Clob</code> contents as a Unicode stream.
264 * @return a Unicode stream containing the <code>CLOB</code> data
265 * @exception SQLException if there is an error accessing the
266 * <code>CLOB</code>
267 */
268
269 public java.io.Reader getCharacterStream() throws SQLException
270 {
271
272 // if we have a string, not a stream
273 if (isString)
274 {
275 return new StringReader(myString);
276 }
277
278
279 Object synchronization = getConnectionSynchronization();
280 synchronized (synchronization)
281 {
282 setupContextStack();
283
284 try {
285 return getCharacterStreamAtPos(1, synchronization);
286 }
287 catch (Throwable t)
288 {
289 throw noStateChangeLOB(t);
290 }
291 finally
292 {
293 restoreContextStack();
294 }
295 }
296 }
297
298
299 /**
300 * Gets the <code>CLOB</code> value designated by this <code>Clob</code>
301 * object as a stream of Ascii bytes.
302 * @return an ascii stream containing the <code>CLOB</code> data
303 * @exception SQLException if there is an error accessing the
304 * <code>CLOB</code> value
305 */
306
307 public java.io.InputStream getAsciiStream() throws SQLException
308 {
309 return new ReaderToAscii(getCharacterStream());
310 }
311
312 private UTF8Reader getCharacterStreamAtPos(long position, Object synchronization)
313 throws IOException, StandardException
314 {
315 ((Resetable)myStream).resetStream();
316 UTF8Reader clobReader = new UTF8Reader(myStream, 0, this, synchronization);
317
318 // skip to the correct position (pos is one based)
319 long remainToSkip = position - 1;
320 while (remainToSkip > 0) {
321 long skipBy = clobReader.skip(remainToSkip);
322 if (skipBy == -1)
323 return null;
324
325 remainToSkip -= skipBy;
326 }
327
328 return clobReader;
329 }
330
331
332 /**
333 * Determines the character position at which the specified substring
334 * <code>searchstr</code> appears in the <code>CLOB</code>. The search
335 * begins at position <code>start</code>.
336 * @param searchstr the substring for which to search
337 * @param start the position at which to begin searching; the first position
338 * is 1
339 * @return the position at which the substring appears, else -1; the first
340 * position is 1
341 * @exception SQLException if there is an error accessing the
342 * <code>CLOB</code> value
343 */
344 public long position(String searchStr, long start)
345 throws SQLException
346 {
347 boolean pushStack = false;
348 try
349 {
350 if (start < 1)
351 throw StandardException.newException(
352 SQLState.BLOB_BAD_POSITION, new Long(start));
353 if (searchStr == null)
354 throw StandardException.newException(SQLState.BLOB_NULL_PATTERN);
355 if (searchStr == "")
356 return start; // match DB2's SQL LOCATE function
357
358 // if we have a string, not a stream
359 if (isString)
360 {
361 // avoid truncation errors in the cast of start to an int.
362 if (start > myString.length())
363 return -1;
364
365 int result = myString.indexOf(searchStr, (int) start-1);
366 return result < 0 ? -1 : result + 1;
367 }
368 else // we have a stream
369 {
370 Object synchronization = getConnectionSynchronization();
371 synchronized (synchronization)
372 {
373 pushStack = !getEmbedConnection().isClosed();
374 if (pushStack)
375 setupContextStack();
376
377 char[] tmpClob = new char[256];
378 int patternLength = searchStr.length();
379
380 restartPattern:
381 for (;;) {
382
383 //System.out.println("RESET " + start);
384 UTF8Reader clobReader = getCharacterStreamAtPos(start, synchronization);
385 if (clobReader == null)
386 return -1;
387
388
389
390 // start of any match of the complete pattern.
391
392 int patternIndex = 0;
393 char[] tmpPattern = null;
394 boolean needPattern = true;
395
396 // how many characters of the patter segment we have matched
397 int matchCount = 0;
398
399 long currentPosition = start;
400 int clobOffset = -1;
401 int read = -1;
402
403 // absolute position of a possible match
404 long matchPosition = -1;
405
406
407 // absolute position of the next possible match
408 long nextBestMatchPosition = -1;
409 //System.out.println("restartPattern: " + start);
410
411
412 search:
413 for (;;)
414 {
415 //System.out.println("search: " + needPattern + " -- " + clobOffset);
416 if (needPattern) {
417
418 String tmpPatternS;
419 if ((patternLength - patternIndex) > 256)
420 tmpPatternS = searchStr.substring(patternIndex, 256);
421 else
422 tmpPatternS = searchStr;
423
424 tmpPattern = tmpPatternS.toCharArray();
425 needPattern = false;
426 matchCount = 0;
427
428 }
429
430 if (clobOffset == -1) {
431
432 read = clobReader.read(tmpClob, 0, tmpClob.length);
433 //System.out.println("MORE DATA " + read);
434 if (read == -1)
435 return -1;
436
437 if (read == 0)
438 continue search;
439
440 clobOffset = 0;
441 }
442
443
444 // find matches within our two temp arrays.
445 compareArrays:
446 for (; clobOffset < read; clobOffset++) {
447
448 //System.out.println("compareArrays " + clobOffset);
449
450 char clobC = tmpClob[clobOffset];
451
452
453 if (clobC == tmpPattern[matchCount])
454 {
455 if (matchPosition == -1) {
456 matchPosition = currentPosition + clobOffset;
457 }
458
459 matchCount++;
460
461 // have we matched the entire pattern segment
462 if (matchCount == tmpPattern.length)
463 {
464 // move onto the next segment.
465 patternIndex += tmpPattern.length;
466 if (patternIndex == patternLength) {
467 // complete match !!
468 clobReader.close();
469 //System.out.println("COMPLETE@" + matchPosition);
470 return matchPosition;
471 }
472
473 needPattern = true;
474 continue search;
475
476 }
477
478 if (clobC == tmpPattern[0]) {
479
480 // save the next best start position.
481
482 // must be the first character of the actual pattern
483 if (patternIndex == 0) {
484
485 // must not be just a repeat of the match of the first character
486 if (matchCount != 1) {
487
488 // must not have a previous next best.
489
490 if (nextBestMatchPosition == -1) {
491 nextBestMatchPosition = currentPosition + clobOffset;
492 }
493
494 }
495
496 }
497 }
498
499 continue compareArrays;
500 }
501 else
502 {
503 // not a match
504 //
505 //
506 if (matchPosition != -1) {
507 // failed after we matched some amount of the pattern
508 matchPosition = -1;
509
510 // See if we found a next best match
511 if (nextBestMatchPosition == -1)
512 {
513 // NO - just continue on, re-starting at this character
514
515 if (patternIndex != 0) {
516 needPattern = true;
517 continue search;
518 }
519 }
520 else if (nextBestMatchPosition >= currentPosition)
521 {
522 // restart in the current array
523 clobOffset = (int) (nextBestMatchPosition - currentPosition);
524 nextBestMatchPosition = -1;
525
526 if (patternIndex != 0) {
527 needPattern = true;
528 continue search;
529 }
530 }
531 else
532 {
533 clobReader.close();
534 start = nextBestMatchPosition;
535 continue restartPattern;
536 }
537
538 clobOffset--; // since the continue will increment it
539 matchCount = 0;
540 continue compareArrays;
541 }
542
543 // no current match, just continue
544 }
545 }
546
547 currentPosition += read;
548
549 // indicates we need to read more data
550 clobOffset = -1;
551 }
552 }
553 }
554 }
555 }
556 catch (Throwable t)
557 {
558 throw noStateChangeLOB(t);
559 }
560 finally
561 {
562 if (pushStack)
563 restoreContextStack();
564 }
565
566 }
567
568
569 /**
570 * Determines the character position at which the specified
571 * <code>Clob</code> object <code>searchstr</code> appears in this
572 * <code>Clob</code> object. The search begins at position
573 * <code>start</code>.
574 * @param searchstr the <code>Clob</code> object for which to search
575 * @param start the position at which to begin searching; the first
576 * position is 1
577 * @return the position at which the <code>Clob</code> object appears,
578 * else -1; the first position is 1
579 * @exception SQLException if there is an error accessing the
580 * <code>CLOB</code> value
581 */
582
583 public long position(Clob searchClob, long start)
584 throws SQLException
585 {
586 boolean pushStack = false;
587 try
588 {
589 if (start < 1)
590 throw StandardException.newException(
591 SQLState.BLOB_BAD_POSITION, new Long(start));
592 if (searchClob == null)
593 throw StandardException.newException(SQLState.BLOB_NULL_PATTERN);
594
595 synchronized (getConnectionSynchronization())
596 {
597 char[] subPatternChar = new char[256];
598
599 boolean seenOneCharacter = false;
600
601 //System.out.println("BEGIN CLOB SEARCH @ " + start);
602
603 restartScan:
604 for (;;) {
605
606 long firstPosition = -1;
607
608 Reader patternReader = searchClob.getCharacterStream();
609
610 //System.out.println("RESTART CLOB SEARCH @ " + start);
611
612 try {
613
614 for (;;) {
615
616 int read = patternReader.read(subPatternChar, 0, subPatternChar.length);
617 if (read == -1) {
618 //empty pattern
619 if (!seenOneCharacter)
620 return start; // matches DB2 SQL LOCATE function
621
622 return firstPosition;
623 }
624 if (read == 0) {
625 //System.out.println("STUCK IN READ 0 HELL");
626 continue;
627 }
628
629 seenOneCharacter = true;
630
631 String subPattern = new String(subPatternChar, 0, read);
632 //System.out.println("START CLOB SEARCH @ " + start + " -- " + subPattern);
633 long position = position(subPattern, start);
634 //System.out.println("DONE SUB CLOB SEARCH @ " + start + " -- " + position);
635 if (position == -1) {
636 // never seen any match
637 if (firstPosition == -1)
638 return -1;
639
640 start = firstPosition + 1;
641 continue restartScan;
642 }
643
644 if (firstPosition == -1)
645 firstPosition = position;
646 else if (position != start) {
647 // must match at the first character of the segment
648 start = firstPosition + 1;
649 continue restartScan;
650 }
651
652 // read is the length of the subPattern string
653 start = position + read;
654 }
655 } finally {
656 patternReader.close();
657 }
658 }
659 }
660 }
661 catch (Throwable t)
662 {
663 throw noStateChangeLOB(t);
664 }
665 finally
666 {
667 if (pushStack)
668 restoreContextStack();
669 }
670
671 }
672
673
674 /*
675 If we have a stream, release the resources associated with it.
676 */
677 protected void finalize()
678 {
679 // System.out.println("finalizer called");
680 if (!isString)
681 ((Resetable)myStream).closeStream();
682 }
683
684
685 /**
686 Following methods are for the new JDBC 3.0 methods in java.sql.Clob
687 (see the JDBC 3.0 spec). We have the JDBC 3.0 methods in Local20
688 package, so we don't have to have a new class in Local30.
689 The new JDBC 3.0 methods don't make use of any new JDBC3.0 classes and
690 so this will work fine in jdbc2.0 configuration.
691 */
692
693 /////////////////////////////////////////////////////////////////////////
694 //
695 // JDBC 3.0 - New public methods
696 //
697 /////////////////////////////////////////////////////////////////////////
698
699 /**
700 * JDBC 3.0
701 *
702 * Writes the given Java String to the CLOB value that this Clob object designates
703 * at the position pos.
704 *
705 * @param pos - the position at which to start writing to the CLOB value that
706 * this Clob object represents
707 * @param str - the string to be written to the CLOB value that this Clob designates
708 * @return the number of characters written
709 * @exception SQLException Feature not implemented for now.
710 */
711 public int setString(long pos, String parameterName)
712 throws SQLException
713 {
714 throw Util.notImplemented();
715 }
716
717 /**
718 * JDBC 3.0
719 *
720 * Writes len characters of str, starting at character offset, to the CLOB value
721 * that this Clob represents.
722 *
723 * @param pos - the position at which to start writing to this Clob object
724 * @param str - the string to be written to the CLOB value that this Clob designates
725 * @param offset - the offset into str to start reading the characters to be written
726 * @param len - the number of characters to be written
727 * @return the number of characters written
728 * @exception SQLException Feature not implemented for now.
729 */
730 public int setString(long pos, String str, int offset, int len)
731 throws SQLException
732 {
733 throw Util.notImplemented();
734 }
735
736 /**
737 * JDBC 3.0
738 *
739 * Retrieves a stream to be used to write Ascii characters to the CLOB
740 * value that this Clob object represents, starting at position pos.
741 *
742 * @param pos - the position at which to start writing to this Clob object
743 * @return the stream to which ASCII encoded characters can be written
744 * @exception SQLException Feature not implemented for now.
745 */
746 public java.io.OutputStream setAsciiStream(long pos)
747 throws SQLException
748 {
749 throw Util.notImplemented();
750 }
751
752 /**
753 * JDBC 3.0
754 *
755 * Retrieves a stream to be used to write a stream of Unicode characters to the
756 * CLOB value that this Clob object represents, starting at position pos.
757 *
758 * @param pos - the position at which to start writing to this Clob object
759 * @return the stream to which Unicode encoded characters can be written
760 * @exception SQLException Feature not implemented for now.
761 */
762 public java.io.Writer setCharacterStream(long pos)
763 throws SQLException
764 {
765 throw Util.notImplemented();
766 }
767
768 /**
769 * JDBC 3.0
770 *
771 * Truncates the CLOB value that this Clob designates to have a length of len characters
772 *
773 * @param len - the length, in bytes, to which the CLOB value that this Blob
774 * value should be truncated
775 * @exception SQLException Feature not implemented for now.
776 */
777 public void truncate(long len)
778 throws SQLException
779 {
780 throw Util.notImplemented();
781 }
782
783
784 /*
785 **
786 */
787
788 static SQLException noStateChangeLOB(Throwable t) {
789 if (t instanceof StandardException)
790 {
791 // container closed means the blob or clob was accessed after commit
792 if (((StandardException) t).getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED))
793 {
794 t = StandardException.newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);
795 }
796 }
797 return org.apache.derby.impl.jdbc.EmbedResultSet.noStateChangeException(t);
798 }
799
800 }