You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by kr...@apache.org on 2009/01/13 09:01:50 UTC

svn commit: r734065 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/types/ engine/org/apache/derby/impl/jdbc/ testing/org/apache/derbyTesting/functionTests/tests/jdbc4/ testing/org/apache/derbyTesting/unitTests/junit/

Author: kristwaa
Date: Tue Jan 13 00:01:46 2009
New Revision: 734065

URL: http://svn.apache.org/viewvc?rev=734065&view=rev
Log:
DERBY-3907 (partial): Save useful length information for Clobs in store.
Added the framework required to handle multiple stream header formats for
stream sources. Note that handling of the new header format is not yet added,
so the code should behave as before, using only the old header format.
A description of the changes:
 o EmbedResultSet and EmbedPreparedStatement
   Started using the new ReaderToUTF8Stream constructor, where the stream
   header is passed in. Also started to treat the DataValueDescriptor as a
   StringDataValue, which should always be the case at this point in the code.

 o ReaderToUTF8Stream
   Added field 'header', which holds a StreamHeaderHolder coming from a
   StringDataValue object. Updated the constructors with a new argument. The
   first execution of fillBuffer now uses the header holder to obtain the
   header length, and the header holder object is consulted when checking if
   the header can be updated with the length after the application stream has
   been drained. Note that updating the header with a character count is not
   yet supported.

 o StringDataValue
   Added new method generateStreamHeader.

 o SQLChar
   Implemented generateStreamHeader, which always return a header for a stream
   with unknown length (see the constant). 

 o SQLClob
   Added a constant for a 10.5 stream header holder representing a stream with
   unknown character length. Also updated the use of the ReaderToUTF8Stream
   constructor.

 o StreamHeaderHolder (new file)
   Holder object for a stream header, containing the header itself and the
   following additional information; "instructions" on how to update the header
   with a new length, if the length is expected to be in number of bytes or
   characters, and if an EOF marker is expected to be appended to the stream.

 o UTF8UTilTest
   Updated usage of the ReaderToUTF8Stream constructor, and replaced the
   hardcoded byte count to skip with a call to the header holder object.

 o jdbc4.ClobTest
   Added some simple tests inserting and fetching Clobs to test the basics of
   stream header handling.

 o StreamTruncationTest (new file)
   New test testing truncation of string data values when they are inserted as
   streams.

Patch file: derby-3907-2c-header_write_preparation.diff

Added:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StreamHeaderHolder.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/StreamTruncationTest.java
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/ReaderToUTF8Stream.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLChar.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLClob.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StringDataValue.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedPreparedStatement.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedResultSet.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/ClobTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/UTF8UtilTest.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/ReaderToUTF8Stream.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/ReaderToUTF8Stream.java?rev=734065&r1=734064&r2=734065&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/ReaderToUTF8Stream.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/ReaderToUTF8Stream.java Tue Jan 13 00:01:46 2009
@@ -58,6 +58,19 @@
 	private boolean eof;
     /** Tells if the stream content is/was larger than the buffer size. */
 	private boolean multipleBuffer;
+    /**
+     * The stream header to use for this stream.
+     * <p>
+     * The holder object is immutable, and the header should not have to be
+     * changed, but we may replace it as an optimizataion. If the length of
+     * the stream is unknown at the start of the insertion and the whole stream
+     * content fits into the buffer, the header is updated with the length
+     * after the source stream has been drained. This means that even though
+     * the object is immutable and the reference final, another header may be
+     * written to the stream.
+     * @see #checkSufficientData()
+     */
+    private final StreamHeaderHolder header;
     
     /**
      * Number of characters to truncate from this stream.
@@ -98,12 +111,14 @@
     public ReaderToUTF8Stream(Reader appReader,
                               int valueLength,
                               int numCharsToTruncate,
-                              String typeName) {
+                              String typeName,
+                              StreamHeaderHolder headerHolder) {
         this.reader = new LimitReader(appReader);
         reader.setLimit(valueLength);
         this.charsToTruncate = numCharsToTruncate;
         this.valueLength = valueLength;
         this.typeName = typeName;
+        this.header = headerHolder;
         if (SanityManager.DEBUG) {
             // Check the type name
             // The national types (i.e. NVARCHAR) are not used/supported.
@@ -134,8 +149,9 @@
      */
     public ReaderToUTF8Stream(Reader appReader,
                               int maximumLength,
-                              String typeName) {
-        this(appReader, -1 * maximumLength, 0, typeName);
+                              String typeName,
+                              StreamHeaderHolder headerHolder) {
+        this(appReader, -1 * maximumLength, 0, typeName, headerHolder);
         if (maximumLength < 0) {
             throw new IllegalArgumentException("Maximum length for a capped " +
                     "stream cannot be negative: " + maximumLength);
@@ -167,7 +183,7 @@
         
 		// first read
 		if (blen < 0)
-			fillBuffer(2);
+            fillBuffer(header.copyInto(buffer, 0));
 
 		while (boff == blen)
 		{
@@ -214,7 +230,7 @@
 
         // first read
 		if (blen < 0)
-			fillBuffer(2);
+            fillBuffer(header.copyInto(buffer, 0));
 
 		int readCount = 0;
 
@@ -369,23 +385,42 @@
                 }
             }
         }
-		
-		// can put the correct length into the stream.
-		if (!multipleBuffer)
-		{
-			int utflen = blen - 2;
 
-			buffer[0] = (byte) ((utflen >>> 8) & 0xFF);
-			buffer[1] = (byte) ((utflen >>> 0) & 0xFF);
-
-		}
-		else
-		{
-			buffer[blen++] = (byte) 0xE0;
-			buffer[blen++] = (byte) 0x00;
-			buffer[blen++] = (byte) 0x00;
-		}
-	}
+        // can put the correct length into the stream.
+        if (!multipleBuffer) {
+            StreamHeaderHolder tmpHeader = header;
+            if (header.expectsCharLength()) {
+                if (SanityManager.DEBUG) {
+                    SanityManager.THROWASSERT("Header update with character " +
+                            "length is not yet supported");
+                }
+            } else {
+                int utflen = blen - header.headerLength(); // Length in bytes
+                tmpHeader = header.updateLength(utflen, false);
+                // Update the header we have already written to our buffer,
+                // still at postition zero.
+                tmpHeader.copyInto(buffer, 0);
+                if (SanityManager.DEBUG) {
+                    // Check that we didn't overwrite any of the user data.
+                    SanityManager.ASSERT(
+                            header.headerLength() == tmpHeader.headerLength());
+                }
+            }
+            // The if below is temporary, it won't be necessary when support
+            // for writing the new header has been added.
+            if (tmpHeader.writeEOF()) {
+                // Write the end-of-stream marker.
+                buffer[blen++] = (byte) 0xE0;
+                buffer[blen++] = (byte) 0x00;
+                buffer[blen++] = (byte) 0x00;
+            }
+        } else if (header.writeEOF()) {
+            // Write the end-of-stream marker.
+            buffer[blen++] = (byte) 0xE0;
+            buffer[blen++] = (byte) 0x00;
+            buffer[blen++] = (byte) 0x00;
+        }
+    }
 
     /**
      * Determine if trailing blank truncation is allowed.
@@ -452,4 +487,4 @@
        // on this stream
        return (BUFSIZE > remainingBytes ? remainingBytes : BUFSIZE);
     }
-  }
+}

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLChar.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLChar.java?rev=734065&r1=734064&r2=734065&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLChar.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLChar.java Tue Jan 13 00:01:46 2009
@@ -144,6 +144,17 @@
         }
     }
 
+    /**
+     * Static stream header holder with the header used for a 10.4 (and earlier)
+     * stream with unknown byte length. This header will be used with 10.4 or
+     * earlier databases, and sometimes also in newer databases for the other
+     * string data types beside of Clob. The expected EOF marker is
+     * '0xE0 0x00 0x00'.
+     */
+    protected static final StreamHeaderHolder UNKNOWN_LEN_10_4_HEADER_HOLDER =
+            new StreamHeaderHolder(
+                    new byte[] {0x00, 0x00}, new byte[] {8, 0}, false, true);
+
     /**************************************************************************
      * Fields of the class
      **************************************************************************
@@ -2843,4 +2854,26 @@
 
         return (toString());
     }
+
+    /**
+     * Generates the stream header for a stream with the given character length.
+     *
+     * @param charLength the character length of the stream, or {@code -1} if
+     *      unknown. If unknown, it is expected that a specifiec end-of-stream
+     *      byte sequence is appended to the stream.
+     * @return A holder object with the stream header. A holder object is used
+     *      because more information than the raw header itself is required,
+     *      for instance whether the stream should be ended with a Derby-
+     *      specific end-of-stream marker
+     */
+    public StreamHeaderHolder generateStreamHeader(long charLength) {
+        // Support for old (pre 10.5) deprecated format, which expects the
+        // header to contain the number of bytes in the value.
+        // We don't know that (due to the varying number of bytes per char), so
+        // say we don't know and instruct that the stream must be ended with a
+        // Derby-specific end-of-stream marker.
+        // Note that there are other code paths were the byte length is known
+        // and can be written to the stream.
+        return UNKNOWN_LEN_10_4_HEADER_HOLDER;
+    }
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLClob.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLClob.java?rev=734065&r1=734064&r2=734065&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLClob.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/SQLClob.java Tue Jan 13 00:01:46 2009
@@ -47,6 +47,17 @@
 public class SQLClob
 	extends SQLVarchar
 {
+
+    /**
+     * Static stream header holder with the header used for a 10.5
+     * stream with unknown char length. This header will be used with 10.5, and
+     * possibly later databases. The expected EOF marker is '0xE0 0x00 0x00'.
+     */
+    protected static final StreamHeaderHolder UNKNOWN_LEN_10_5_HEADER_HOLDER =
+            new StreamHeaderHolder(
+                    new byte[] {0x00, 0x00, (byte)0xF0, 0x00, 0x00},
+                    new byte[] {24, 16, -1, 8, 0}, true, true);
+
 	/*
 	 * DataValueDescriptor interface.
 	 *
@@ -342,10 +353,11 @@
             long vcl = vc.length();
             if (vcl < 0L || vcl > Integer.MAX_VALUE)
                 throw this.outOfRange();
-            
-            setValue(new ReaderToUTF8Stream(vc.getCharacterStream(),
-                    (int) vcl, 0, TypeId.CLOB_NAME), (int) vcl);
-            
+
+            ReaderToUTF8Stream utfIn = new ReaderToUTF8Stream(
+                    vc.getCharacterStream(), (int) vcl, 0, TypeId.CLOB_NAME,
+                    generateStreamHeader(vcl));
+            setValue(utfIn, (int) vcl);
         } catch (SQLException e) {
             throw dataTypeConversion("DAN-438-tmp");
        }

Added: db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StreamHeaderHolder.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StreamHeaderHolder.java?rev=734065&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StreamHeaderHolder.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StreamHeaderHolder.java Tue Jan 13 00:01:46 2009
@@ -0,0 +1,161 @@
+/*
+
+   Derby - Class org.apache.derby.iapi.types.StreamHeaderHolder
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+package org.apache.derby.iapi.types;
+
+import org.apache.derby.iapi.services.sanity.SanityManager;
+
+/**
+ * A holder class for a stream header.
+ * <p>
+ * A stream header is used to store meta information about stream, typically
+ * length information.
+ */
+//@Immutable
+public final class StreamHeaderHolder {
+
+    /** The header bytes. */
+    private final byte[] hdr;
+    /**
+     * Describes if and how the header can be updated with a new length.
+     * <p>
+     * If {@code null}, updating the length is not allowed, and an exception
+     * will be thrown if the update method is called. If allowed, the update
+     * is described by the numbers of bits to right-shift at each position of
+     * the header. Positions with a "negative shift" are skipped. Example:
+     * <pre>
+     * current hdr  shift       updated hdr
+     * 0x00         24          (byte)(length >>> 24)
+     * 0x00         16          (byte)(length >>> 16)
+     * 0xF0         -1          0xF0
+     * 0x00          8          (byte)(length >>> 8)
+     * 0x00          0          (byte)(length >>> 0)
+     * </pre>
+     * <p>
+     * Needless to say, this mechanism is rather simple, but sufficient for the
+     * current header formats.
+     */
+    private final byte[] shifts;
+    /**
+     * Tells if the header encodes the character or byte length of the stream.
+     */
+    private final boolean lengthIsCharCount;
+    /**
+     * Whether a Derby-specific end-of-stream marker is required or not.
+     * It is expected that the same EOF marker is used for all headers:
+     * {@code 0xE0 0x00 0x00}.
+     */
+    private final boolean writeEOF;
+
+    /**
+     * Creates a new stream header holder object.
+     *
+     * @param hdr the stream header bytes
+     * @param shifts describes how to update the header with a new length, or
+     *      {@code null} if updating the header is forbidden
+     * @param lengthIsCharCount whether the length is in characters
+     *      ({@code true}) or bytes ({@code false})
+     * @param writeEOF whether a Derby-specific EOF marker is required
+     */
+    public StreamHeaderHolder(byte[] hdr, byte[] shifts,
+                       boolean lengthIsCharCount, boolean writeEOF) {
+        this.hdr = hdr;
+        this.shifts = shifts;
+        this.lengthIsCharCount = lengthIsCharCount;
+        this.writeEOF = writeEOF;
+        if (SanityManager.DEBUG) {
+            SanityManager.ASSERT(shifts == null || hdr.length == shifts.length);
+        }
+    }
+
+    /**
+     * Copies the header bytes into the specified buffer at the given offset.
+     *
+     * @param buf target byte array
+     * @param offset offset in the target byte array
+     * @return The number of bytes written (the header length).
+     */
+    public int copyInto(byte[] buf, int offset) {
+        System.arraycopy(hdr, 0, buf, offset, hdr.length);
+        return hdr.length;
+    }
+
+    /**
+     * Returns the header length.
+     *
+     * @return The header length in bytes.
+     */
+    public int headerLength() {
+        return hdr.length;
+    }
+
+    /**
+     * Tells if the header encodes the character or the byte length of the
+     * stream.
+     *
+     * @return {@code true} if the character length is expected, {@code false}
+     *      if the byte length is expected.
+     */
+    public boolean expectsCharLength() {
+        return lengthIsCharCount;
+    }
+
+    /**
+     * Tells if a Derby-specific end-of-stream marker should be appended to the
+     * stream associated with this header.
+     *
+     * @return {@code true} if EOF marker required, {@code false} if not.
+     */
+    public boolean writeEOF() {
+        return writeEOF;
+    }
+
+    /**
+     * Creates a new holder object with a header updated for the new length.
+     * <p>
+     * <em>NOTE</em>: This method does not update the header in the stream
+     * itself. It must be updated explicitly using {@linkplain #copyInto}.
+     *<p>
+     * <em>Implementation note</em>: This update mechanism is very simple and
+     * may not be sufficient for later header formats. It is based purely on
+     * shifting of the bits in the new length.
+     *
+     * @param length the new length to encode into the header
+     * @param writeEOF whether the new header requires an EOF marker or not
+     * @return A new stream header holder for the new length.
+     * @throws IllegalStateException if updating the header is disallowed
+     */
+    public StreamHeaderHolder updateLength(int length, boolean writeEOF) {
+        if (shifts == null) {
+            throw new IllegalStateException(
+                    "Updating the header has been disallowed");
+        }
+        byte[] newHdr = new byte[hdr.length];
+        for (int i=0; i < hdr.length; i++) {
+            if (shifts[i] >= 0) {
+                newHdr[i] = (byte)(length >>> shifts[i]);
+            } else {
+                newHdr[i] = hdr[i];
+            }
+        }
+        return new StreamHeaderHolder(
+                newHdr, shifts, lengthIsCharCount, writeEOF);
+    }
+}

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StringDataValue.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StringDataValue.java?rev=734065&r1=734064&r2=734065&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StringDataValue.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/types/StringDataValue.java Tue Jan 13 00:01:46 2009
@@ -194,4 +194,17 @@
 	 * subcalss.
 	 */
 	public StringDataValue getValue(RuleBasedCollator collatorForComparison);
+
+    /**
+     * Generates the stream header for a stream with the given character length.
+     *
+     * @param charLength the character length of the stream, or {@code -1} if
+     *      unknown. If unknown, it is expected that an end-of-stream byte
+     *      sequence is appended to the stream.
+     * @return A holder object with the stream header. A holder object is used
+     *      because more information than the raw header itself is required,
+     *      for instance whether the stream should be ended with a Derby-
+     *      specific end-of-stream marker.
+     */
+    public StreamHeaderHolder generateStreamHeader(long charLength);
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedPreparedStatement.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedPreparedStatement.java?rev=734065&r1=734064&r2=734065&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedPreparedStatement.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedPreparedStatement.java Tue Jan 13 00:01:46 2009
@@ -25,11 +25,8 @@
 
 import org.apache.derby.iapi.types.VariableSizeDataValue;
 
-import org.apache.derby.iapi.sql.dictionary.DataDictionary;
-import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
 import org.apache.derby.iapi.sql.PreparedStatement;
 import org.apache.derby.iapi.sql.execute.ExecPreparedStatement;
-import org.apache.derby.iapi.sql.ResultSet;
 import org.apache.derby.iapi.sql.Activation;
 import org.apache.derby.iapi.sql.ParameterValueSet;
 import org.apache.derby.iapi.sql.ResultDescription;
@@ -40,8 +37,6 @@
 
 import org.apache.derby.iapi.error.StandardException;
 
-import org.apache.derby.iapi.services.io.LimitReader;
-
 import org.apache.derby.iapi.reference.SQLState;
 import org.apache.derby.iapi.reference.JDBC40Translation;
 
@@ -63,15 +58,13 @@
 import java.sql.Blob;
 
 import java.io.InputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.io.EOFException;
 import java.io.Reader;
 import java.sql.Types;
 
 import org.apache.derby.iapi.jdbc.BrokeredConnectionControl;
 import org.apache.derby.iapi.jdbc.EngineParameterMetaData;
 import org.apache.derby.iapi.jdbc.EnginePreparedStatement;
+import org.apache.derby.iapi.types.StringDataValue;
 
 /**
  *
@@ -740,7 +733,8 @@
 
         try {
             ReaderToUTF8Stream utfIn;
-            ParameterValueSet pvs = getParms();
+            final StringDataValue dvd = (StringDataValue)
+                    getParms().getParameter(parameterIndex -1);
             // Need column width to figure out if truncation is needed
             DataTypeDescriptor dtd[] = preparedStatement
                     .getParameterTypes();
@@ -787,12 +781,14 @@
                 }
                 // Create a stream with truncation.
                 utfIn = new ReaderToUTF8Stream(reader, usableLength,
-                        truncationLength, getParameterSQLType(parameterIndex));
+                        truncationLength, getParameterSQLType(parameterIndex),
+                        dvd.generateStreamHeader(length));
             } else {
                 // Create a stream without exactness checks,
                 // but with a maximum limit.
                 utfIn = new ReaderToUTF8Stream(reader, colWidth,
-                                getParameterSQLType(parameterIndex));
+                                getParameterSQLType(parameterIndex),
+                                dvd.generateStreamHeader(-1));
             }
 
             // JDBC is one-based, DBMS is zero-based.
@@ -800,8 +796,8 @@
             // the maximum length for the column. 
             // This is okay, based on the observation that
             // setValue does not use the value for anything at all.
-            pvs.getParameterForSet(
-                parameterIndex - 1).setValue(utfIn, usableLength);
+            getParms().getParameterForSet(parameterIndex - 1).
+                    setValue(utfIn, usableLength);
 
 		} catch (StandardException t) {
 			throw EmbedResultSet.noStateChangeException(t);

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedResultSet.java?rev=734065&r1=734064&r2=734065&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedResultSet.java Tue Jan 13 00:01:46 2009
@@ -75,6 +75,7 @@
 
 import java.util.Arrays;
 import java.util.Calendar;
+import org.apache.derby.iapi.types.StringDataValue;
 
 /**
  * A EmbedResultSet for results from the EmbedStatement family. 
@@ -2918,6 +2919,8 @@
                 return;
             }
             
+            final StringDataValue dvd = (StringDataValue)
+                    getDVDforColumnToBeUpdated(columnIndex, updateMethodName);
             ReaderToUTF8Stream utfIn;
             int usableLength = -1;
             if (!lengthLess) {
@@ -2967,17 +2970,18 @@
                 }
 
                 utfIn = new ReaderToUTF8Stream(reader, usableLength,
-                        truncationLength, getColumnSQLType(columnIndex));
+                        truncationLength, getColumnSQLType(columnIndex),
+                        dvd.generateStreamHeader(length));
             } else {
                 int colWidth = getMaxColumnWidth(columnIndex);
                 utfIn = new ReaderToUTF8Stream(
-                            reader, colWidth, getColumnSQLType(columnIndex));
+                            reader, colWidth, getColumnSQLType(columnIndex),
+                            dvd.generateStreamHeader(-1));
             }
 
             // NOTE: The length argument to setValue is not used. If that
             //       changes, the value might also have to change.
-            getDVDforColumnToBeUpdated(columnIndex, updateMethodName).setValue(
-                    utfIn, (int) usableLength);
+            dvd.setValue(utfIn, usableLength);
         } catch (StandardException t) {
             throw noStateChangeException(t);
         }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/ClobTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/ClobTest.java?rev=734065&r1=734064&r2=734065&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/ClobTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/ClobTest.java Tue Jan 13 00:01:46 2009
@@ -763,9 +763,100 @@
         commit();
     }
 
+    /** Inserts, fetches and checks the length of a Clob using a stream. */
+    public void testInsertAndFetchZeroLength()
+            throws IOException, SQLException {
+        insertAndFetchTest(0);
+    }
+
+    /** Inserts, fetches and checks the length of a Clob using a stream. */
+    public void testInsertAndFetchVerySmall()
+            throws IOException, SQLException {
+        insertAndFetchTest(7);
+    }
+
+    /** Inserts, fetches and checks the length of a Clob using a stream. */
+    public void testInsertAndFetchSmall()
+            throws IOException, SQLException {
+        insertAndFetchTest(1587);
+    }
+
+    /** Inserts, fetches and checks the length of a Clob using a stream. */
+    public void testInsertAndFetchMedium()
+            throws IOException, SQLException {
+        insertAndFetchTest(32000);
+    }
+
+    /** Inserts, fetches and checks the length of a Clob using a stream. */
+    public void testInsertAndFetchMediumPlus()
+            throws IOException, SQLException {
+        insertAndFetchTest(64000);
+    }
+
+    /** Inserts, fetches and checks the length of a Clob using a stream. */
+    public void testInsertAndFetchLarge()
+            throws IOException, SQLException {
+        insertAndFetchTest(128022);
+    }
+
+    /** Inserts, fetches and checks the length of a Clob using a stream. */
+    public void testInsertAndFetchLarger()
+            throws IOException, SQLException {
+        insertAndFetchTest(3*1024*1024);
+    }
+
+    /**
+     * Inserts a Clob with the specified length, using a stream source, then
+     * fetches it from the database and checks the length.
+     *
+     * @param length number of characters in the Clob
+     * @throws IOException if reading from the source fails
+     * @throws SQLException if something goes wrong
+     */
+    private void insertAndFetchTest(long length)
+            throws IOException, SQLException {
+        PreparedStatement ps = prepareStatement(
+                "insert into BLOBCLOB(ID, CLOBDATA) values(?,?)");
+        int id = BlobClobTestSetup.getID();
+        ps.setInt(1, id);
+        ps.setCharacterStream(2, new LoopingAlphabetReader(length), length);
+        long tsStart = System.currentTimeMillis();
+        ps.execute();
+        println("Inserted " + length + " chars (length specified) in " +
+                (System.currentTimeMillis() - tsStart) + " ms");
+        Statement stmt = createStatement();
+        tsStart = System.currentTimeMillis();
+        ResultSet rs = stmt.executeQuery(
+                "select CLOBDATA from BLOBCLOB where id = " + id);
+        assertTrue("Clob not inserted", rs.next());
+        Clob aClob = rs.getClob(1);
+        assertEquals("Invalid length", length, aClob.length());
+        println("Fetched length (" + length + ") in " +
+                (System.currentTimeMillis() - tsStart) + " ms");
+        rs.close();
+
+        // Insert same Clob again, using the lengthless override.
+        id = BlobClobTestSetup.getID();
+        ps.setInt(1, id);
+        ps.setCharacterStream(2, new LoopingAlphabetReader(length));
+        tsStart = System.currentTimeMillis();
+        ps.executeUpdate();
+        println("Inserted " + length + " chars (length unspecified) in " +
+                (System.currentTimeMillis() - tsStart) + " ms");
+        rs = stmt.executeQuery(
+                "select CLOBDATA from BLOBCLOB where id = " + id);
+        assertTrue("Clob not inserted", rs.next());
+        aClob = rs.getClob(1);
+        assertEquals("Invalid length", length, aClob.length());
+        println("Fetched length (" + length + ") in " +
+                (System.currentTimeMillis() - tsStart) + " ms");
+        rs.close();
+
+        rollback();
+    }
 
     /**
-     * Insert a row with a large clob into the test table.  Read the row from
+     * Insert a row with a large clob into the test table.  Read the row from 
      * the database and assign the clob value to <code>clob</code>.
      * @return The id of the row that was inserted
      * @throws java.sql.SQLException

Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/StreamTruncationTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/StreamTruncationTest.java?rev=734065&view=auto
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/StreamTruncationTest.java (added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/StreamTruncationTest.java Tue Jan 13 00:01:46 2009
@@ -0,0 +1,432 @@
+/*
+
+   Derby - Class org.apache.derbyTesting.functionTests.tests.jdbc4.StreamTruncationTest
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+package org.apache.derbyTesting.functionTests.tests.jdbc4;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.concurrent.atomic.AtomicInteger;
+import junit.framework.Test;
+import org.apache.derbyTesting.functionTests.util.streams.CharAlphabet;
+import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetReader;
+import org.apache.derbyTesting.junit.BaseJDBCTestCase;
+import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
+import org.apache.derbyTesting.junit.TestConfiguration;
+
+/**
+ * Tests string data value truncation when the data is inserted with a stream.
+ * <p>
+ * Tests that truncation does indeed happen for CHAR, VARCHAR and CLOB.
+ * Truncation is not allowed for LONG VARCHAR columns.
+ * <p>
+ * There are two aspects to consider; specified length vs unspecified length
+ * (lengthless override), and small vs large. In this regard, small is when the
+ * stream content fits into the internal buffer of the conversion reader
+ * ({@code ReaderToUTF8Stream}). In this test, the buffer is assumed to be
+ * approximately 32KB.
+ */
+public class StreamTruncationTest
+    extends BaseJDBCTestCase {
+
+    /**
+     * Assumed conversion buffer size.
+     * @see org.apache.derby.iapi.types.ReaderToUTF8Stream
+     */
+    public static final int CONV_BUFFER_SIZE = 32*1024;
+
+    // Column index constants
+    public static final int CLOB = 2;
+    public static final int VARCHAR = 3;
+    public static final int LONGVARCHAR = 4;
+    public static final int CHAR = 5;
+
+    /** Name of table with the "small" columns. */
+    public static final String TABLE_SMALL = "TRUNCATE_SMALL";
+    /** Name of table with the "large" columns. */
+    public static final String TABLE_LARGE = "TRUNCATE_LARGE";
+    /** Small size (smaller than the conversion buffer). */
+    public static final int SMALL_SIZE = CONV_BUFFER_SIZE / 2;
+    /** Large size (larger than the conversion buffer). */
+    public static final int LARGE_SIZE = CONV_BUFFER_SIZE * 2;
+    /* Size used for the large VARCHAR column. */
+    public static final int LARGE_VARCHAR_SIZE = 32672;
+    /* Size used for the CHAR column. */
+    public static final int CHAR_SIZE = 138;
+
+    /** Id/counter to use for the inserted rows. */
+    private static AtomicInteger ID = new AtomicInteger(1);
+
+    public StreamTruncationTest(String name) {
+        super(name);
+    }
+
+    public void setUp()
+            throws SQLException {
+        setAutoCommit(false);
+    }
+
+    public void testCharWithLength()
+            throws IOException, SQLException {
+        charSmall(false);
+    }
+
+    public void testCharWithoutLength()
+            throws IOException, SQLException {
+        charSmall(true);
+    }
+
+    public void testSmallVarcharWithLength()
+            throws IOException, SQLException {
+        generalTypeSmall(VARCHAR, false);
+    }
+
+    public void testSmallVarcharWithoutLength()
+            throws IOException, SQLException {
+        generalTypeSmall(VARCHAR, true);
+    }
+
+    public void testLargeVarcharWithLength()
+            throws IOException, SQLException {
+        generalTypeLarge(VARCHAR, false);
+    }
+
+    public void testLargeVarcharWithoutLength()
+            throws IOException, SQLException {
+        generalTypeLarge(VARCHAR, true);
+    }
+
+    public void testLongVarcharWithLength()
+            throws IOException, SQLException {
+        generalTypeSmall(LONGVARCHAR, false);
+    }
+
+    public void testLongVarcharWithoutLength()
+            throws IOException, SQLException {
+        generalTypeSmall(LONGVARCHAR, true);
+    }
+
+    public void testSmallClobWithLength()
+            throws IOException, SQLException {
+        generalTypeSmall(CLOB, false);
+    }
+
+    public void testSmallClobWithoutLength()
+            throws IOException, SQLException {
+        generalTypeSmall(CLOB, true);
+    }
+
+    public void testLargeClobWithLength()
+            throws IOException, SQLException {
+        generalTypeLarge(CLOB, false);
+    }
+
+    public void testLargeClobWithoutLength()
+            throws IOException, SQLException {
+        generalTypeLarge(CLOB, true);
+    }
+
+    /**
+     * Executes a set of insertions into the larger of the columns.
+     *
+     * @param colIndex column index to insert into, which also determines the
+     *      type to test
+     * @param lengthless {@code true} if a lengthless override should be used,
+     *      {@code false} if the length of the stream shall be specified when
+     *      inserted
+     * @throws IOException if reading from the source stream fails
+     * @throws SQLException if something goes wrong
+     */
+    private void generalTypeLarge(int colIndex, boolean lengthless)
+            throws IOException, SQLException {
+        insertLarge(colIndex, lengthless, LARGE_SIZE, 0); // Fits
+        insertLarge(colIndex, lengthless, LARGE_SIZE -99, 15); // Fits
+        insertLarge(colIndex, lengthless, LARGE_SIZE + 189, 189); // Truncate
+        insertLarge(colIndex, lengthless, LARGE_SIZE, 250); // Fits
+        insertLarge(colIndex, lengthless, LARGE_SIZE + 180, 0); // Should fail
+        insertLarge(colIndex, lengthless, LARGE_SIZE + 180, 17); // Should fail
+    }
+
+    /**
+     * Executes a set of insertions into the smaller of the columns.
+     *
+     * @param colIndex column index to insert into, which also determines the
+     *      type to test
+     * @param lengthless {@code true} if a lengthless override should be used,
+     *      {@code false} if the length of the stream shall be specified when
+     *      inserted
+     * @throws IOException if reading from the source stream fails
+     * @throws SQLException if something goes wrong
+     */
+    private void generalTypeSmall(int colIndex, boolean lengthless)
+            throws IOException, SQLException {
+        insertSmall(colIndex, lengthless, SMALL_SIZE, 0); // Fits
+        insertSmall(colIndex, lengthless, SMALL_SIZE -99, 15); // Fits
+        insertSmall(colIndex, lengthless, SMALL_SIZE + 189, 189); // Truncate
+        insertSmall(colIndex, lengthless, SMALL_SIZE, 250); // Fits
+        insertSmall(colIndex, lengthless, SMALL_SIZE + 180, 0); // Should fail
+        insertSmall(colIndex, lengthless, SMALL_SIZE + 180, 17); // Should fail
+    }
+
+    /**
+     * Executes a set of insertions into the CHAR column.
+     *
+     * @param lengthless {@code true} if a lengthless override should be used,
+     *      {@code false} if the length of the stream shall be specified when
+     *      inserted
+     * @throws IOException if reading from the source stream fails
+     * @throws SQLException if something goes wrong
+     */
+    private void charSmall(boolean lengthless)
+            throws IOException, SQLException {
+        insertSmall(CHAR, lengthless, CHAR_SIZE, 0); // Fits
+        insertSmall(CHAR, lengthless, CHAR_SIZE -10, 4); // Fits
+        insertSmall(CHAR, lengthless, CHAR_SIZE + 189, 189); // Should truncate
+        insertSmall(CHAR, lengthless, CHAR_SIZE, 20); // Fits
+        insertSmall(CHAR, lengthless, CHAR_SIZE + 180, 0); // Should fail
+        insertSmall(CHAR, lengthless, CHAR_SIZE + 180, 17); // Should fail
+    }
+
+    /**
+     * Inserts a small (smaller than internal conversion buffer) string value.
+     *
+     * @param colIndex column to insert into (see constants)
+     * @param lengthless whether the length of the stream should be specified
+     *      or not on insertion
+     * @param totalLength the total character length of the stream to insert
+     * @param blanks number of trailing blanks in the stream
+     * @return The id of the row inserted.
+     *
+     * @throws IOException if reading from the source stream fails
+     * @throws SQLException if something goes wrong, or the test fails
+     */
+    private int insertSmall(int colIndex, boolean lengthless,
+                            int totalLength, int blanks)
+            throws IOException, SQLException {
+        int id = ID.getAndAdd(1);
+        PreparedStatement ps = prepareStatement(
+                "insert into " + TABLE_SMALL + " values (?,?,?,?,?)");
+        ps.setInt(1, id);
+        ps.setNull(2, Types.CLOB);
+        ps.setNull(3, Types.VARCHAR);
+        ps.setNull(4, Types.LONGVARCHAR);
+        ps.setNull(5, Types.CHAR);
+
+        int colWidth = SMALL_SIZE;
+        if (colIndex == LONGVARCHAR) {
+            colWidth = 32700;
+        }
+        int expectedLength = Math.min(totalLength, colWidth);
+        // Length of CHAR is always the defined length due to padding.
+        if (colIndex == CHAR) {
+            colWidth = expectedLength = CHAR_SIZE;
+        }
+        println("totalLength=" + totalLength + ", blanks=" + blanks +
+                ", colWidth=" + colWidth + ", expectedLength=" +
+                expectedLength);
+        Reader source = new LoopingAlphabetReader(totalLength,
+                CharAlphabet.modernLatinLowercase(), blanks);
+        // Now set what we are going to test.
+        if (lengthless) {
+            ps.setCharacterStream(colIndex, source);
+        } else {
+            ps.setCharacterStream(colIndex, source, totalLength);
+        }
+        try {
+            // Exceute the insert.
+            assertEquals(1, ps.executeUpdate());
+            if (totalLength > expectedLength) {
+                assertTrue(totalLength - blanks <= expectedLength);
+            }
+
+            // Fetch the value.
+            assertEquals(expectedLength,
+                    getStreamLength(TABLE_SMALL, colIndex, id));
+        } catch (SQLException sqle) {
+            // Sanity check of the length.
+            if (colIndex == LONGVARCHAR) {
+                // Truncation is not allowed.
+                assertTrue(totalLength > expectedLength);
+            } else {
+                // Total length minus blanks must still be larger then the
+                // expected length.
+                assertTrue(totalLength - blanks > expectedLength);
+            }
+            // The error handling here is very fuzzy...
+            // This will hopefully be fixed, such that the exception thrown
+            // will always be 22001. Today this is currently wrapped by several
+            // other exceptions.
+            String expectedState = "XSDA4";
+            if (colIndex == CHAR || colIndex == VARCHAR) {
+                if (lengthless) {
+                    expectedState = "XJ001";
+                } else {
+                    if (!usingEmbedded()) {
+                        expectedState = "XJ001";
+                    } else {
+                        expectedState = "22001";
+                    }
+                }
+            }
+            assertSQLState(expectedState, sqle);
+        }
+        return id;
+    }
+
+    /**
+     * Inserts a large (largerer than internal conversion buffer) string value.
+     *
+     * @param colIndex column to insert into (see constants)
+     * @param lengthless whether the length of the stream should be specified
+     *      or not on insertion
+     * @param totalLength the total character length of the stream to insert
+     * @param blanks number of trailing blanks in the stream
+     * @return The id of the row inserted.
+     *
+     * @throws IOException if reading from the source stream fails
+     * @throws SQLException if something goes wrong, or the test fails
+     */
+    private int insertLarge(int colIndex, boolean lengthless,
+                            int totalLength, int blanks)
+            throws IOException, SQLException {
+        // Not used here, see insertSmall.
+        assertTrue(colIndex != CHAR && colIndex != LONGVARCHAR);
+
+        int id = ID.getAndAdd(1);
+        PreparedStatement ps = prepareStatement(
+                "insert into " + TABLE_LARGE + " values (?,?,?)");
+        ps.setInt(1, id);
+        ps.setNull(2, Types.CLOB);
+        ps.setNull(3, Types.VARCHAR);
+
+        int colWidth = (colIndex == VARCHAR ? LARGE_VARCHAR_SIZE : LARGE_SIZE);
+        int expectedLength = Math.min(totalLength, colWidth);
+        println("totalLength=" + totalLength + ", blanks=" + blanks +
+                ", colWidth=" + colWidth + ", expectedLength=" +
+                expectedLength);
+        Reader source = new LoopingAlphabetReader(totalLength,
+                CharAlphabet.modernLatinLowercase(), blanks);
+        // Now set what we are going to test.
+        if (lengthless) {
+            ps.setCharacterStream(colIndex, source);
+        } else {
+            ps.setCharacterStream(colIndex, source, totalLength);
+        }
+        try {
+            // Exceute the insert.
+            assertEquals(1, ps.executeUpdate());
+            if (totalLength > expectedLength) {
+                assertTrue(totalLength - blanks <= expectedLength);
+            }
+
+            // Fetch the value.
+            assertEquals(expectedLength,
+                    getStreamLength(TABLE_LARGE, colIndex, id));
+        } catch (SQLException sqle) {
+            // Sanity check of the length.
+            // Total length minus blanks must still be larger then the
+            // expected length.
+            assertTrue(totalLength - blanks > expectedLength);
+            // The error handling here is very fuzzy...
+            // This will hopefully be fixed, such that the exception thrown
+            // will always be 22001. Today this is currently wrapped by several
+            // other exceptions.
+            String expectedState = "XSDA4";
+            if (colIndex == VARCHAR) {
+                if (lengthless) {
+                    expectedState = "XJ001";
+                } else {
+                    if (!usingEmbedded()) {
+                        expectedState = "XJ001";
+                    } else {
+                        expectedState = "22001";
+                    }
+                }
+            }
+            assertSQLState(expectedState, sqle);
+        }
+        return id;
+    }
+
+    /**
+     * Obtains the length of the data value stored in the specified table,
+     * column index and id (primary key).
+     *
+     * @param table table name
+     * @param colIndex column index
+     * @param id id of the row to fetch
+     * @return The length in characters of the string data value fetched.
+     * @throws IOException if reading the stream fails
+     * @throws SQLException if something goes wrong
+     */
+    private int getStreamLength(String table, int colIndex, int id)
+            throws IOException, SQLException {
+        Statement sFetch =  createStatement();
+        ResultSet rs = sFetch.executeQuery("select * from " + table +
+                " where id = " + id);
+        assertTrue(rs.next());
+        Reader dbSource = rs.getCharacterStream(colIndex);
+        int observedLen = 0;
+        char[] buf = new char[1024];
+        while (true) {
+            int read = dbSource.read(buf);
+            if (read == -1) {
+                break;
+            }
+            observedLen += read;
+        }
+        rs.close();
+        return observedLen;
+    }
+
+    /**
+     * Returns the suite of tests.
+     * <p>
+     * Two tables are created for the test.
+     *
+     * @return A suite of tests.
+     */
+    public static Test suite() {
+        return new CleanDatabaseTestSetup(TestConfiguration.defaultSuite(
+                StreamTruncationTest.class, false)) {
+                    protected void decorateSQL(Statement stmt)
+                            throws SQLException {
+                        stmt.executeUpdate(
+                                "create table " + TABLE_SMALL + " (" +
+                                "ID int primary key, " +
+                                "CLOBDATA clob(" + SMALL_SIZE + ")," +
+                                "VCHARDATA varchar(" + SMALL_SIZE + ")," +
+                                "LVCHARDATA long varchar," +
+                                "CHARDATA char(" + CHAR_SIZE + "))");
+                        stmt.executeUpdate(
+                                "create table " + TABLE_LARGE + " (" +
+                                "ID int primary key, " +
+                                "CLOBDATA clob(" + LARGE_SIZE + ")," +
+                                "VCHARDATA varchar(" + LARGE_VARCHAR_SIZE +
+                                "))");
+                        stmt.close();
+                    }
+            };
+    }
+}

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/UTF8UtilTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/UTF8UtilTest.java?rev=734065&r1=734064&r2=734065&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/UTF8UtilTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/UTF8UtilTest.java Tue Jan 13 00:01:46 2009
@@ -33,6 +33,7 @@
 import java.io.UTFDataFormatException;
 
 import org.apache.derby.iapi.types.ReaderToUTF8Stream;
+import org.apache.derby.iapi.types.StreamHeaderHolder;
 import org.apache.derby.iapi.util.UTF8Util;
 
 import org.apache.derbyTesting.functionTests.util.streams.CharAlphabet;
@@ -62,6 +63,11 @@
     /** Type name passed to {@code ReaderToUTF8Stream}. */
     private static final String TYPENAME = "VARCHAR";
 
+    /** Default header for stream with unknown length. */
+    private static final StreamHeaderHolder HDR = new StreamHeaderHolder(
+            new byte[] {0x00, 0x00}, new byte[] {8, 0}, false, true);
+    private static final int HEADER_LENGTH = HDR.headerLength();
+
     /**
      * Creates a test of the specified name.
      */
@@ -81,8 +87,8 @@
         InputStream ascii = new LoopingAlphabetStream(length);
         InputStream modUTF8 = new ReaderToUTF8Stream(
                                     new LoopingAlphabetReader(length),
-                                    length, 0, TYPENAME);
-        modUTF8.skip(2L); // Skip encoded length added by ReaderToUTF8Stream.
+                                    length, 0, TYPENAME, HDR);
+        modUTF8.skip(HEADER_LENGTH); // Skip encoded length added by ReaderToUTF8Stream.
         assertEquals(ascii, modUTF8);
     }
 
@@ -101,8 +107,8 @@
         final int charLength = 5;
         InputStream in = new ReaderToUTF8Stream(
                 new LoopingAlphabetReader(charLength, CharAlphabet.cjkSubset()),
-                charLength, 0, TYPENAME);
-        in.skip(2L); // Skip encoded length added by ReaderToUTF8Stream.
+                charLength, 0, TYPENAME, HDR);
+        in.skip(HEADER_LENGTH); // Skip encoded length added by ReaderToUTF8Stream.
         assertEquals(charLength, UTF8Util.skipUntilEOF(in));
     }
 
@@ -117,8 +123,8 @@
         final int charLength = 127019;
         InputStream in = new ReaderToUTF8Stream(
                 new LoopingAlphabetReader(charLength, CharAlphabet.cjkSubset()),
-                charLength, 0, TYPENAME);
-        in.skip(2L); // Skip encoded length added by ReaderToUTF8Stream.
+                charLength, 0, TYPENAME, HDR);
+        in.skip(HEADER_LENGTH); // Skip encoded length added by ReaderToUTF8Stream.
         assertEquals(charLength, UTF8Util.skipUntilEOF(in));
     }
 
@@ -133,8 +139,8 @@
         final int charLength = 161019;
         InputStream in = new ReaderToUTF8Stream(
                 new LoopingAlphabetReader(charLength, CharAlphabet.cjkSubset()),
-                charLength, 0, TYPENAME);
-        in.skip(2L); // Skip encoded length added by ReaderToUTF8Stream.
+                charLength, 0, TYPENAME, HDR);
+        in.skip(HEADER_LENGTH); // Skip encoded length added by ReaderToUTF8Stream.
         // Returns count in bytes, we are using CJK chars so multiply length
         // with 3 to get expected number of bytes.
         assertEquals(charLength *3, UTF8Util.skipFully(in, charLength));
@@ -151,8 +157,8 @@
         final int charLength = 161019;
         InputStream in = new ReaderToUTF8Stream(
                 new LoopingAlphabetReader(charLength, CharAlphabet.cjkSubset()),
-                charLength, 0, TYPENAME);
-        in.skip(2L); // Skip encoded length added by ReaderToUTF8Stream.
+                charLength, 0, TYPENAME, HDR);
+        in.skip(HEADER_LENGTH); // Skip encoded length added by ReaderToUTF8Stream.
         try {
             UTF8Util.skipFully(in, charLength + 100);
             fail("Should have failed because of too short stream.");
@@ -172,8 +178,8 @@
         final int charLength = 10;
         InputStream in = new ReaderToUTF8Stream(
                 new LoopingAlphabetReader(charLength, CharAlphabet.cjkSubset()),
-                charLength, 0, TYPENAME);
-        in.skip(2L); // Skip encoded length added by ReaderToUTF8Stream.
+                charLength, 0, TYPENAME, HDR);
+        in.skip(HEADER_LENGTH); // Skip encoded length added by ReaderToUTF8Stream.
         in.skip(1L); // Skip one more byte to trigger a UTF error.
         try {
             UTF8Util.skipFully(in, charLength);
@@ -191,12 +197,12 @@
         final int charLength = 161019;
         InputStream in = new ReaderToUTF8Stream(
                 new LoopingAlphabetReader(charLength, CharAlphabet.tamil()),
-                charLength, 0, TYPENAME);
-        in.skip(2L); // Skip encoded length added by ReaderToUTF8Stream.
+                charLength, 0, TYPENAME, HDR);
+        // Skip encoded length added by ReaderToUTF8Stream.
+        in.skip(HEADER_LENGTH);
         int firstSkip = 10078;
         assertEquals(firstSkip*3, UTF8Util.skipFully(in, firstSkip));
         assertEquals(charLength - firstSkip, UTF8Util.skipUntilEOF(in));
-        assertEquals(0, UTF8Util.skipUntilEOF(in)); // Nothing left here.
         try {
             UTF8Util.skipFully(in, 1L);
             fail("Should have failed because the stream has been drained.");