db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From krist...@apache.org
Subject svn commit: r749235 - in /db/derby/code/trunk/java: client/org/apache/derby/client/am/ engine/org/apache/derby/impl/jdbc/ testing/org/apache/derbyTesting/functionTests/tests/jdbc4/ testing/org/apache/derbyTesting/functionTests/util/streams/
Date Mon, 02 Mar 2009 08:47:53 GMT
Author: kristwaa
Date: Mon Mar  2 08:47:52 2009
New Revision: 749235

URL: http://svn.apache.org/viewvc?rev=749235&view=rev
Log:
DERBY-4060: Blob.getBinaryStream(long,long) is off by one for the pos+len check.
Changed the pos/length checks to allow obtaining a stream reading the last
byte/char from the LOB.
The JavaDoc for Blob.getBinaryStream(long,long) and
Clob.getCharacterStream(long,long) (JDBC 4.0) incorrectly states that the
position plus the requested length of the stream cannot be larger than the
length of the LOB. Since positions in JDBC are 1-based, this makes it impossible
to read the last byte/char in the LOB. Derby adhered to the spec.

The changes to CharAlphabet/LoopingAlphabetReader were done to allow passing
an alphabet object around for constructing streams.
Patch file: derby-4060-1b-sub_stream_fix.diff


Modified:
    db/derby/code/trunk/java/client/org/apache/derby/client/am/Lob.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedBlob.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedClob.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/BlobTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/ClobTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/CharAlphabet.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/LoopingAlphabetReader.java

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/am/Lob.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/am/Lob.java?rev=749235&r1=749234&r2=749235&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/am/Lob.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/am/Lob.java Mon Mar  2 08:47:52
2009
@@ -317,7 +317,7 @@
      *         a) pos <= 0
      *         b) pos > (length of LOB)
      *         c) length < 0
-     *         d) pos + length > (length of LOB)
+     *         d) (pos -1) + length > (length of LOB)
      */
     protected void checkPosAndLength(long pos, long length)
     throws SQLException {
@@ -331,7 +331,7 @@
                 new ClientMessageId(SQLState.BLOB_NONPOSITIVE_LENGTH),
                 new Integer((int)length)).getSQLException();
         }
-        if (length > (this.length() - pos)) {
+        if (length > (this.length() - (pos -1))) {
             throw new SqlException(agent_.logWriter_,
                 new ClientMessageId(SQLState.POS_AND_LENGTH_GREATER_THAN_LOB),
                 new Long(pos), new Long(length)).getSQLException();

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedBlob.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedBlob.java?rev=749235&r1=749234&r2=749235&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedBlob.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedBlob.java Mon Mar  2 08:47:52
2009
@@ -996,7 +996,7 @@
                     SQLState.BLOB_NONPOSITIVE_LENGTH,
                     new Long(length));
         }
-        if (length > (this.length() - pos)) {
+        if (length > (this.length() - (pos -1))) {
             throw Util.generateCsSQLException(
                     SQLState.POS_AND_LENGTH_GREATER_THAN_LOB,
                     new Long(pos), new Long(length));

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedClob.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedClob.java?rev=749235&r1=749234&r2=749235&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedClob.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedClob.java Mon Mar  2 08:47:52
2009
@@ -707,7 +707,7 @@
                     SQLState.BLOB_NONPOSITIVE_LENGTH,
                     new Long(length));
         }
-        if (length > (this.length() - pos)) {
+        if (length > (this.length() - (pos -1))) {
             throw Util.generateCsSQLException(
                     SQLState.POS_AND_LENGTH_GREATER_THAN_LOB,
                     new Long(pos), new Long(length));

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/BlobTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/BlobTest.java?rev=749235&r1=749234&r2=749235&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/BlobTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbc4/BlobTest.java
Mon Mar  2 08:47:52 2009
@@ -431,6 +431,69 @@
     }
     
     /**
+     * Obtains a binary stream and tries to drain it to read the last byte in
+     * the Blob.
+     * <p>
+     * See DERBY-4060.
+     *
+     * @throws IOException if reading from a stream fails
+     * @throws SQLException if something goes wrong
+     */
+    public void testGetBinaryStreamLongLastByte()
+            throws IOException, SQLException {
+        int length = 5000;
+        // Insert a Blob
+        PreparedStatement ps = prepareStatement(
+            "insert into BLOBCLOB(ID, BLOBDATA) values(?,?)");
+        int id = BlobClobTestSetup.getID();
+        ps.setInt(1, id);
+        ps.setBinaryStream(2, new LoopingAlphabetStream(length), length);
+        ps.execute();
+        ps.close();
+
+        // Get last byte from the source stream.
+        InputStream cmpIs = new LoopingAlphabetStream(length);
+        cmpIs.skip(length -1);
+        int srcLastByte = cmpIs.read();
+        assertTrue(cmpIs.read() == -1);
+
+        // Read everything first.
+        int bytesToRead = 5000;
+        ps = prepareStatement("select BLOBDATA from BLOBCLOB where ID=?");
+        ps.setInt(1, id);
+        ResultSet rs = ps.executeQuery();
+        rs.next();
+        InputStream is = rs.getBlob(1).getBinaryStream(
+                                        length - bytesToRead +1, bytesToRead);
+
+        // Drain the stream, and make sure we are able to read the last byte.
+        int lastByteRead = getLastByteInStream(is, bytesToRead);
+        assertEquals(srcLastByte, lastByteRead);
+        is.close();
+        rs.close();
+
+        // Read a portion of the stream.
+        bytesToRead = 2000;
+        rs = ps.executeQuery();
+        rs.next();
+        is = rs.getBlob(1).getBinaryStream(
+                                        length - bytesToRead +1, bytesToRead);
+        assertEquals(srcLastByte, lastByteRead);
+        is.close();
+        rs.close();
+
+        // Read a very small portion of the stream.
+        bytesToRead = 1;
+        rs = ps.executeQuery();
+        rs.next();
+        is = rs.getBlob(1).getBinaryStream(
+                                        length - bytesToRead +1, bytesToRead);
+        assertEquals(srcLastByte, lastByteRead);
+        is.close();
+        rs.close();
+    }
+
+    /**
      * Tests the exceptions thrown by the getBinaryStream
      * (long pos, long length) for the following conditions
      * a) pos <= 0
@@ -845,6 +908,42 @@
     }
     
     /**
+     * Drains the stream and returns the last byte read from the stream.
+     *
+     * @param is stream to drain
+     * @param expectedCount expected number of bytes (remaining) in the stream
+     * @return The last byte read.
+     * @throws AssertionError if there are too many/few bytes in the stream
+     * @throws IOException if reading from the stream fails
+     */
+    public static int getLastByteInStream(InputStream is, int expectedCount)
+            throws IOException {
+        int read = 0;
+        byte[] buf = new byte[256];
+        assertTrue(buf.length > 0); // Do not allow an infinite loop here.
+        while (true) {
+            int readThisTime = is.read(buf, 0, buf.length);
+            // -1 is expected, but catch all cases with a negative return value.
+            if (readThisTime < 0) {
+                assertTrue("Invalid return value from stream",
+                        readThisTime == -1);
+                fail("Reached EOF prematurely, expected " + expectedCount +
+                        ", got " + read);
+            } else if (readThisTime == 0) {
+                // Another special case that should not happen.
+                fail("Stream breaks contract, read zero bytes " + is);
+            }
+            read += readThisTime;
+            if (read == expectedCount) {
+                return buf[readThisTime -1];
+            } else if (read > expectedCount) {
+                fail("Too many bytes in stream, expected " + expectedCount +
+                        "have " + read + "(EOF not reached/confirmed)");
+            }
+        }
+    }
+
+    /**
      * Create test suite for this test.
      */
     public static Test suite()

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=749235&r1=749234&r2=749235&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
Mon Mar  2 08:47:52 2009
@@ -43,6 +43,7 @@
  * d) Whether the method is exempted in the NetworkClient
  *
  */
+import org.apache.derbyTesting.functionTests.util.streams.CharAlphabet;
 import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetReader;
 import org.apache.derbyTesting.junit.DatabasePropertyTestSetup;
 class ExemptClobMD {
@@ -413,6 +414,111 @@
     }
 
     /**
+     * Obtains streams from the Clob reading portions of the content, always
+     * including the last character in the Clob.
+     * <p>
+     * This case fills the Clob with latin lowercase characters.
+     */
+    public void testGetCharacterStreamLongLastCharLatin()
+            throws IOException, SQLException {
+        CharAlphabet alphabet = CharAlphabet.modernLatinLowercase();
+        // Insert a Clob
+        int length = 5000;
+        PreparedStatement ps = prepareStatement(
+            "insert into BLOBCLOB(ID, CLOBDATA) values(?,?)");
+        int id = BlobClobTestSetup.getID();
+        ps.setInt(1, id);
+        ps.setCharacterStream(2,
+                new LoopingAlphabetReader(length, alphabet), length);
+        ps.execute();
+        ps.close();
+        // Perform the actual test.
+        getCharacterStreamLongLastChar(id, length, alphabet);
+    }
+
+    /**
+     * Obtains streams from the Clob reading portions of the content, always
+     * including the last character in the Clob.
+     * <p>
+     * This case fills the Clob with Chinese/Japanese/Korean characters.
+     */
+    public void testGetCharacterStreamLongLastCharCJK()
+            throws IOException, SQLException {
+        CharAlphabet alphabet = CharAlphabet.cjkSubset();
+        // Insert a Clob
+        int length = 9001;
+        PreparedStatement ps = prepareStatement(
+            "insert into BLOBCLOB(ID, CLOBDATA) values(?,?)");
+        int id = BlobClobTestSetup.getID();
+        ps.setInt(1, id);
+        ps.setCharacterStream(2,
+                new LoopingAlphabetReader(length, alphabet), length);
+        ps.execute();
+        ps.close();
+        // Perform the actual test.
+        getCharacterStreamLongLastChar(id, length, alphabet);
+    }
+
+    /**
+     * Obtains streams from the Clob and makes sure we can always read the
+     * last char in the Clob.
+     * <p>
+     * See DERBY-4060.
+     *
+     * @param id id of the Clob to use
+     * @param length the length of the Clob
+     * @param alphabet the alphabet used to create the content
+     * @throws IOException if reading from a stream fails
+     * @throws SQLException if something goes wrong
+     */
+    private void getCharacterStreamLongLastChar(int id, int length,
+                                                CharAlphabet alphabet)
+            throws IOException, SQLException {
+        // Get last char from the source stream.
+        Reader cmpReader = new LoopingAlphabetReader(length, alphabet);
+        cmpReader.skip(length -1);
+        char srcLastChar = (char)cmpReader.read();
+        assertTrue(cmpReader.read() == -1);
+
+        PreparedStatement ps = prepareStatement(
+                "select CLOBDATA from BLOBCLOB where ID=?");
+        ps.setInt(1, id);
+        // Read everything first.
+        int charsToRead = length;
+        ResultSet rs = ps.executeQuery();
+        rs.next();
+        Reader reader = rs.getClob(1).getCharacterStream(
+                                        length - charsToRead +1, charsToRead);
+        // Drain the stream, and make sure we are able to read the last char.
+        char lastCharRead = getLastCharInStream(reader, charsToRead);
+        assertEquals(srcLastChar, lastCharRead);
+        reader.close();
+        rs.close();
+
+        // Read a portion of the stream.
+        charsToRead = length / 4;
+        rs = ps.executeQuery();
+        rs.next();
+        reader = rs.getClob(1).getCharacterStream(
+                                        length - charsToRead +1, charsToRead);
+        lastCharRead = getLastCharInStream(reader, charsToRead);
+        assertEquals(srcLastChar, lastCharRead);
+        reader.close();
+        rs.close();
+
+        // Read a very small portion of the stream.
+        charsToRead = 1;
+        rs = ps.executeQuery();
+        rs.next();
+        reader = rs.getClob(1).getCharacterStream(
+                                        length - charsToRead +1, charsToRead);
+        lastCharRead = getLastCharInStream(reader, charsToRead);
+        assertEquals(srcLastChar, lastCharRead);
+        reader.close();
+        rs.close();
+    }
+
+    /**
      * Test that <code>Clob.getCharacterStream(long,long)</code> works on CLOBs
      * that are streamed from store. (DERBY-2891)
      */
@@ -923,6 +1029,42 @@
     }
 
     /**
+     * Drains the stream and returns the last char read from the stream.
+     *
+     * @param reader stream to drain
+     * @param expectedCount expected number of chars (remaining) in the stream
+     * @return The last char read.
+     * @throws AssertionError if there are too many/few chars in the stream
+     * @throws IOException if reading from the stream fails
+     */
+    public static char getLastCharInStream(Reader reader, int expectedCount)
+            throws IOException {
+        int read = 0;
+        final char[] buf = new char[256];
+        assertTrue(buf.length > 0); // Do not allow an infinite loop here.
+        while (true) {
+            int readThisTime = reader.read(buf, 0, buf.length);
+            // -1 is expected, but catch all cases with a negative return value.
+            if (readThisTime < 0) {
+                assertEquals("Invalid return value from stream",
+                        -1, readThisTime);
+                fail("Reached EOF prematurely, expected " + expectedCount +
+                        ", got " + read);
+            } else if (readThisTime == 0) {
+                // Another special case that should not happen.
+                fail("Stream breaks contract, read zero chars: " + reader);
+            }
+            read += readThisTime;
+            if (read == expectedCount) {
+                return buf[readThisTime -1];
+            } else if (read > expectedCount) {
+                fail("Too many chars in stream, expected " + expectedCount +
+                        "have " + read + "(EOF not reached/confirmed)");
+            }
+        }
+    }
+
+    /**
      * Create test suite for this test.
      */
     public static Test suite()

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/CharAlphabet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/CharAlphabet.java?rev=749235&r1=749234&r2=749235&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/CharAlphabet.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/CharAlphabet.java
Mon Mar  2 08:47:52 2009
@@ -173,4 +173,21 @@
     public void reset() {
         off = 0;
     }
+
+    /**
+     * Returns a clone of the alphabet.
+     *
+     * @return A clone.
+     */
+    public CharAlphabet getClone() {
+        return new CharAlphabet(name, chars);
+    }
+
+    /**
+     * Returns a friendlier textual representation of the alphabet.
+     */
+    public String toString() {
+        return (name + "@" + hashCode() + "(charCount=" + charCount + ")");
+    }
+
 } // Enc class CharAlphabet

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/LoopingAlphabetReader.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/LoopingAlphabetReader.java?rev=749235&r1=749234&r2=749235&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/LoopingAlphabetReader.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/util/streams/LoopingAlphabetReader.java
Mon Mar  2 08:47:52 2009
@@ -112,7 +112,7 @@
         this.trailingBlanks = trailingBlanks;
         this.remainingNonBlanks = length - trailingBlanks;
         this.remainingBlanks = trailingBlanks;
-        this.alphabet = alphabet;
+        this.alphabet = alphabet.getClone();
         fillBuffer(alphabet.charCount());
     }
 



Mime
View raw message