Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 }