db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mi...@apache.org
Subject svn commit: r961049 - in /db/derby/code/branches/10.5: ./ java/engine/org/apache/derby/impl/store/raw/data/ java/engine/org/apache/derby/loc/ java/shared/org/apache/derby/shared/common/reference/ java/testing/org/apache/derbyTesting/functionTests/tests...
Date Wed, 07 Jul 2010 00:29:50 GMT
Author: mikem
Date: Wed Jul  7 00:29:49 2010
New Revision: 961049

URL: http://svn.apache.org/viewvc?rev=961049&view=rev
Log:
DERBY-2992: getBinaryStream returns incorrect result (truncated value) if underlying blob
is deleted

backporting change #908586 from trunk to 10.5 branch.

Made OverflowInputStream throw exception if the underlying value is deleted/modified.
(i.e. if the next overflow page cannot be found/accessed).
Added a new error message (D015).
Incorporated repro (originally contributed by Kathey Marsden, kmarsdenderby at sbcglobal dot
net).

Patch file: DERBY-2992-1b-throw_exception_on_eof.diff 


Modified:
    db/derby/code/branches/10.5/   (props changed)
    db/derby/code/branches/10.5/java/engine/org/apache/derby/impl/store/raw/data/OverflowInputStream.java
    db/derby/code/branches/10.5/java/engine/org/apache/derby/loc/messages.xml
    db/derby/code/branches/10.5/java/shared/org/apache/derby/shared/common/reference/MessageId.java
    db/derby/code/branches/10.5/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/BLOBTest.java

Propchange: db/derby/code/branches/10.5/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Wed Jul  7 00:29:49 2010
@@ -1,2 +1,2 @@
 /db/derby/code/branches/10.6:957000
-/db/derby/code/trunk:757811,769596,769602,769606,769962,772090,772337,772449,772534,774281,777105,779681,782991,785131,785139,785163,785570,785662,788369,788670,788674,788968,789264,790218,791027,792434,793089,793588,794106,794303,794955,795166,795459,796020,796027,796316,796372,797147,798347,798742,800523,803548,803948,805696,808494,808850,809643,810860,812669,816531,816536,819006,822289,823659,824694,829022,829410,831304,831319,832379,833430,835286,881074,881444,882732,884163,885421,885659,887246,888311,892912,897161,898635,901165,901648,901760,903108,908418,909176,911315,915733,916075,916897,918359,921028,927430,928065,942286,942476,942480,942587,946794,948045,948069,951346,954748,955001,955634,956075,956445,956659,958163,959550
+/db/derby/code/trunk:757811,769596,769602,769606,769962,772090,772337,772449,772534,774281,777105,779681,782991,785131,785139,785163,785570,785662,788369,788670,788674,788968,789264,790218,791027,792434,793089,793588,794106,794303,794955,795166,795459,796020,796027,796316,796372,797147,798347,798742,800523,803548,803948,805696,808494,808850,809643,810860,812669,816531,816536,819006,822289,823659,824694,829022,829410,831304,831319,832379,833430,835286,881074,881444,882732,884163,885421,885659,887246,888311,892912,897161,898635,901165,901648,901760,903108,908418,908586,909176,911315,915733,916075,916897,918359,921028,927430,928065,942286,942476,942480,942587,946794,948045,948069,951346,954748,955001,955634,956075,956445,956659,958163,959550

Modified: db/derby/code/branches/10.5/java/engine/org/apache/derby/impl/store/raw/data/OverflowInputStream.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.5/java/engine/org/apache/derby/impl/store/raw/data/OverflowInputStream.java?rev=961049&r1=961048&r2=961049&view=diff
==============================================================================
--- db/derby/code/branches/10.5/java/engine/org/apache/derby/impl/store/raw/data/OverflowInputStream.java
(original)
+++ db/derby/code/branches/10.5/java/engine/org/apache/derby/impl/store/raw/data/OverflowInputStream.java
Wed Jul  7 00:29:49 2010
@@ -24,12 +24,18 @@ package org.apache.derby.impl.store.raw.
 import org.apache.derby.iapi.error.StandardException;
 import org.apache.derby.iapi.reference.SQLState;
 
-import org.apache.derby.iapi.store.raw.RecordHandle;
+import org.apache.derby.iapi.services.i18n.MessageService;
 
 import org.apache.derby.iapi.types.Resetable;
-import org.apache.derby.iapi.store.raw.LockingPolicy;
+
 import org.apache.derby.iapi.store.access.TransactionController;
+import org.apache.derby.iapi.store.raw.LockingPolicy;
+import org.apache.derby.iapi.store.raw.RecordHandle;
+
+import org.apache.derby.shared.common.reference.MessageId;
 
+import java.io.EOFException;
+import java.io.InputStream;
 import java.io.IOException;
 
 /**
@@ -151,6 +157,11 @@ implements Resetable
                     columnOverflowPage.restorePortionLongColumn(this);
                     columnOverflowPage.unlatch();
                     columnOverflowPage = null;
+                } else {
+                    // An overflow page was specified, but we failed to get it.
+                    // Probably the value got deleted under our feet.
+                    throw new EOFException(MessageService.getTextMessage(
+                            MessageId.STORE_STREAM_OVERFLOW_PAGE_NOT_FOUND));
                 }
 
             }

Modified: db/derby/code/branches/10.5/java/engine/org/apache/derby/loc/messages.xml
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.5/java/engine/org/apache/derby/loc/messages.xml?rev=961049&r1=961048&r2=961049&view=diff
==============================================================================
--- db/derby/code/branches/10.5/java/engine/org/apache/derby/loc/messages.xml (original)
+++ db/derby/code/branches/10.5/java/engine/org/apache/derby/loc/messages.xml Wed Jul  7 00:29:49
2010
@@ -7381,6 +7381,11 @@ Shutting down instance {0} with class lo
                 <text>in READ ONLY mode</text>
             </msg>
 
+            <msg>
+                <name>D015</name>
+                <text>Could not read further because another transaction has modified
the value.</text>
+            </msg>
+
         </family>
 
 

Modified: db/derby/code/branches/10.5/java/shared/org/apache/derby/shared/common/reference/MessageId.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.5/java/shared/org/apache/derby/shared/common/reference/MessageId.java?rev=961049&r1=961048&r2=961049&view=diff
==============================================================================
--- db/derby/code/branches/10.5/java/shared/org/apache/derby/shared/common/reference/MessageId.java
(original)
+++ db/derby/code/branches/10.5/java/shared/org/apache/derby/shared/common/reference/MessageId.java
Wed Jul  7 00:29:49 2010
@@ -89,7 +89,11 @@ public interface MessageId {
     String STORE_DURABILITY_TESTMODE_NO_SYNC = "D013"; // for derby.system.durability is

                                                        // set to test
     String STORE_BOOT_READONLY_MSG          = "D014";
-
+    /**
+     * When the specified overflow page isn't found while streaming from a
+     * page overflow chain (large data values, typically BLOB or CLOB).
+     */
+    String STORE_STREAM_OVERFLOW_PAGE_NOT_FOUND = "D015";
 
 	/*
 	** ClassManager

Modified: db/derby/code/branches/10.5/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/BLOBTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.5/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/BLOBTest.java?rev=961049&r1=961048&r2=961049&view=diff
==============================================================================
--- db/derby/code/branches/10.5/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/BLOBTest.java
(original)
+++ db/derby/code/branches/10.5/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/BLOBTest.java
Wed Jul  7 00:29:49 2010
@@ -18,7 +18,12 @@
  * language governing permissions and limitations under the License.
  */
 package org.apache.derbyTesting.functionTests.tests.jdbcapi;
+
 import org.apache.derbyTesting.functionTests.util.TestInputStream;
+import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetStream;
+import org.apache.derbyTesting.functionTests.util.streams.ReadOnceByteArrayInputStream;
+import org.apache.derbyTesting.functionTests.util.streams.StringReaderWithLength;
+import org.apache.derbyTesting.junit.JDBC;
 import org.apache.derbyTesting.junit.BaseJDBCTestCase;
 
 import junit.framework.Test;
@@ -29,8 +34,12 @@ import java.sql.Statement;
 import java.sql.Blob;
 import java.sql.Connection;
 import java.sql.SQLException;
+import java.sql.Types;
+import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Random;
+
 import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetStream;
 
 /**
@@ -380,6 +389,300 @@ final public class BLOBTest extends Base
     }
 
     /**
+     * Tests that a lob can be safely occur multiple times in a SQL select.
+     * <p/>
+     * See DERBY-4477.
+     * <p/>
+     * @see org.apache.derbyTesting.functionTests.tests.memory.BlobMemTest#testDerby4477_3645_3646_Repro_lowmem
+     * @see org.apache.derbyTesting.functionTests.tests.memory.ClobMemTest#testDerby4477_3645_3646_Repro_lowmem_clob
+     */
+    public void testDerby4477_3645_3646_Repro() throws SQLException, IOException {
+        setAutoCommit(false);
+        Statement s = createStatement();
+
+        s.executeUpdate(
+            "CREATE TABLE T_MAIN(" +
+            "ID INT  GENERATED ALWAYS AS IDENTITY PRIMARY KEY, " +
+            "V BLOB(590473235) )");
+
+        PreparedStatement ps = prepareStatement(
+            "INSERT INTO T_MAIN(V) VALUES (?)");
+
+        byte[] bytes = new byte[35000];
+
+        for (int i = 0; i < 35000; i++) {
+            bytes[i] = (byte)i;
+        }
+
+        ps.setBytes(1, bytes);
+        ps.executeUpdate();
+        ps.close();
+
+        s.executeUpdate("CREATE TABLE T_COPY ( V1 BLOB(2M), V2 BLOB(2M))");
+
+        // This failed in the repro for DERBY-3645 solved as part of
+        // DERBY-4477:
+        s.executeUpdate("INSERT INTO T_COPY SELECT  V, V FROM T_MAIN");
+
+        // Check that the two results are identical:
+        ResultSet rs = s.executeQuery("SELECT * FROM T_COPY");
+        rs.next();
+        String v1 = rs.getString(1);
+        String v2 = rs.getString(2);
+        assertEquals(v1.length(), v2.length());
+
+        for (int i=0; i < v1.length(); i++) {
+            assertEquals(v1.charAt(i), v2.charAt(i));
+        }
+
+        // Verify against a single select too (both above could be wrong..)
+        rs = s.executeQuery("SELECT V from T_MAIN");
+        rs.next();
+        String v3 = rs.getString(1);
+        assertEquals(v1.length(), v3.length());
+
+        for (int i=0; i < v1.length(); i++) {
+            assertEquals(v1.charAt(i), v3.charAt(i));
+        }
+
+        // This failed in the repro for DERBY-3646 solved as part of
+        // DERBY-4477 (repro slightly rewoked here):
+        rs = s.executeQuery("SELECT 'I', V, ID, V from T_MAIN");
+        rs.next();
+
+        InputStream s1 = rs.getBinaryStream(2);
+
+        // JDBC says that the next getBinaryStream will close the s1 stream so
+        // verify it now. Cf. DERBY-4521.
+
+        for (int i = 0; i < 35000; i++) {
+            assertEquals((byte)i, (byte)s1.read());
+        }
+
+        assertEquals(-1, s1.read());
+        s1.close();
+
+        InputStream s2 = rs.getBinaryStream(4);
+
+        for (int i = 0; i < 35000; i++) {
+            assertEquals((byte)i, (byte)s2.read());
+        }
+
+        assertEquals(-1, s2.read());
+        s2.close();
+
+        rs.close();
+
+        rollback();
+    }
+
+
+    /**
+     * Tests that a lob can be safely occur multiple times in a SQL select in
+     * a trigger context.
+     * <p/>
+     * See DERBY-4477.
+     */
+    public void testDerby4477_2349_Repro() throws SQLException, IOException {
+
+        setAutoCommit(false);
+
+        Statement s = createStatement();
+
+        s.executeUpdate("CREATE TABLE T_MAIN(" +
+                "ID INT  GENERATED ALWAYS AS IDENTITY PRIMARY KEY, " +
+                "V BLOB(590473235) )");
+        s.executeUpdate("CREATE TABLE T_ACTION_ROW(ID INT, A CHAR(1), " +
+                "V1 BLOB(590473235), V2 BLOB(590473235) )");
+        s.executeUpdate("CREATE TABLE T_ACTION_STATEMENT(ID INT, A CHAR(1), " +
+                "V1 BLOB(590473235), V2 BLOB(590473235) )");
+
+        // ON INSERT copy the typed value V into the action table.
+        // Use V twice to ensure there are no issues with values
+        // that can be streamed.
+        // Two identical actions,  per row and per statement.
+        s.executeUpdate(
+            "CREATE TRIGGER AIR AFTER INSERT ON T_MAIN " +
+            "    REFERENCING NEW AS N FOR EACH ROW " +
+            "    INSERT INTO T_ACTION_ROW(A, V1, ID, V2) " +
+            "        VALUES ('I', N.V, N.ID, N.V)");
+
+        s.executeUpdate(
+            "CREATE TRIGGER AIS AFTER INSERT ON T_MAIN " +
+            "    REFERENCING NEW_TABLE AS N FOR EACH STATEMENT " +
+            "    INSERT INTO T_ACTION_STATEMENT(A, V1, ID, V2) " +
+            "        SELECT 'I', V, ID, V FROM N");
+
+        s.executeUpdate("INSERT INTO T_MAIN(V) VALUES NULL");
+
+        s.close();
+        actionTypesCompareMainToAction(1);
+
+        int jdbcType = Types.BLOB;
+        int precision = 590473235;
+
+        Random r = new Random();
+
+        String ins1 = "INSERT INTO T_MAIN(V) VALUES (?)";
+        String ins3 = "INSERT INTO T_MAIN(V) VALUES (?), (?), (?)";
+
+        PreparedStatement ps;
+        ps = prepareStatement(ins1);
+        setRandomValue(r, ps, 1, jdbcType, precision);
+        ps.executeUpdate();
+        ps.close();
+
+        actionTypesCompareMainToAction(2);
+
+        ps = prepareStatement(ins3);
+        setRandomValue(r, ps, 1, jdbcType, precision);
+        setRandomValue(r, ps, 2, jdbcType, precision);
+        setRandomValue(r, ps, 3, jdbcType, precision);
+        ps.executeUpdate();
+        ps.close();
+
+        actionTypesCompareMainToAction(5);
+
+        rollback();
+    }
+
+    /**
+     * Checks that Derby fails with an exception when a transaction using
+     * READ_UNCOMMITTED obtains a stream from a BLOB (reads one byte) and at
+     * the same time another connection deletes the BLOB.
+     * <p>
+     * Earlier only parts of the BLOB was returned, without errors. It was
+     * impossible to tell for the user that only parts of the value was
+     * retrieved.
+     * <p>
+     * See DERBY-2992.
+     */
+    public void testDerby2992_Repro()
+            throws IOException, SQLException {
+        // Autocommit doesn't seem to be enabled here in all cases.
+        setAutoCommit(true);
+
+        final String TBL = "D2992BLOB";
+        // Switch to READ UNCOMMITTED.
+        getConnection().setTransactionIsolation(
+                Connection.TRANSACTION_READ_UNCOMMITTED);
+        Statement stmt = createStatement();
+        dropTable(TBL);
+        stmt.executeUpdate("create table " + TBL + " (b blob)");
+        stmt.close();
+
+        PreparedStatement ps = prepareStatement("insert into " + TBL +
+                " values (?)");
+        int length = 65*1024*1024; // 65K
+        ps.setBinaryStream(1, new LoopingAlphabetStream(length), length);
+        ps.executeUpdate();
+        ps.close();
+
+        stmt = createStatement();
+        ResultSet rs = stmt.executeQuery("select B from " + TBL);
+        assertTrue(rs.next());
+
+        // Read one byte, keep the stream / rs open.
+        InputStream is = rs.getBinaryStream(1);
+        int i = is.read();
+        assertTrue(i != -1);
+
+        // Open a second connection and delete the BLOB.
+        Connection secondCon = openUserConnection("APP");
+        Statement secondStmt = secondCon.createStatement();
+        assertEquals(1, secondStmt.executeUpdate("delete from " + TBL));
+        secondCon.close();
+
+        // Continue reading the BLOB through the stream.
+        // The stream has now probably read one page of data, and as we progress
+        // it will have to fetch the next page. However, the next page has been
+        // deleted.
+        byte[] buf = new byte[4096];
+        try {
+            // Drain the stream.
+            while (is.read(buf) != -1) { }
+            // Expect the read call above to fail at some point.
+            fail("The read should have failed, value has been deleted");
+        } catch (EOFException eofe) {
+            // As we expected, everything's fine.
+        }
+    }
+
+    public static void setRandomValue(
+        Random r,
+        PreparedStatement ps,
+        int column,
+        int jdbcType,
+        int precision) throws SQLException, IOException {
+
+        Object val = getRandomValue(r, jdbcType, precision);
+
+        if (val instanceof StringReaderWithLength) {
+            StringReaderWithLength rd = (StringReaderWithLength) val;
+            ps.setCharacterStream(column, rd, rd.getLength());
+        } else if (val instanceof InputStream) {
+            InputStream in = (InputStream) val;
+            ps.setBinaryStream(column, in, in.available());
+        } else {
+            ps.setObject(column, val, jdbcType);
+        }
+    }
+
+    public static Object getRandomValue(
+        Random r,
+        int jdbcType,
+        int precision) throws IOException {
+
+        switch (jdbcType) {
+        case Types.BLOB:
+            if (precision > 256*1024)
+                precision = 256*1024;
+            return new ReadOnceByteArrayInputStream(
+                    randomBinary(r, r.nextInt(precision)));
+        }
+
+        fail("unexpected JDBC Type " + jdbcType);
+        return null;
+    }
+
+    private static byte[] randomBinary(Random r, int len) {
+        byte[] bb = new byte[len];
+        for (int i = 0; i < bb.length; i++)
+            bb[i] = (byte) r.nextInt();
+        return bb;
+     }
+
+    private void actionTypesCompareMainToAction(
+        int actionCount) throws SQLException, IOException {
+
+        Statement s1 = createStatement();
+        Statement s2 = createStatement();
+
+        String sqlMain =
+            "SELECT ID, V, V FROM T_MAIN ORDER BY 1";
+        String sqlActionRow =
+            "SELECT ID, V1, V2 FROM T_ACTION_ROW ORDER BY 1";
+        String sqlActionStatement =
+            "SELECT ID, V1, V2 FROM T_ACTION_STATEMENT ORDER BY 1";
+
+        ResultSet rsMain = s1.executeQuery(sqlMain);
+        ResultSet rsAction = s2.executeQuery(sqlActionRow);
+        JDBC.assertSameContents(rsMain, rsAction);
+
+        rsMain = s1.executeQuery(sqlMain);
+        rsAction = s2.executeQuery(sqlActionStatement);
+        JDBC.assertSameContents(rsMain, rsAction);
+
+
+        assertTableRowCount("T_ACTION_ROW", actionCount);
+        assertTableRowCount("T_ACTION_STATEMENT", actionCount);
+
+        s1.close();
+        s2.close();
+    }
+
+
+    /**
      * Verifies that the table has row with column val=newVal
      * and that it its data and size columns are consistent.
      * @param newVal value expected to be found in the val column of a row



Mime
View raw message