Index: java/engine/org/apache/derby/impl/sql/execute/DMLWriteResultSet.java
===================================================================
--- java/engine/org/apache/derby/impl/sql/execute/DMLWriteResultSet.java (revision 591113)
+++ java/engine/org/apache/derby/impl/sql/execute/DMLWriteResultSet.java (working copy)
@@ -20,6 +20,8 @@
package org.apache.derby.impl.sql.execute;
+import java.io.InputStream;
+
import org.apache.derby.iapi.types.DataValueDescriptor;
import org.apache.derby.iapi.sql.execute.NoPutResultSet;
import org.apache.derby.iapi.services.io.StreamStorable;
@@ -147,10 +149,29 @@
heapIx :
baseRowReadMap[heapIx];
+
DataValueDescriptor col = row.getColumn(readIx+1);
+ InputStream stream = ((StreamStorable)col).returnStream();
((StreamStorable)col).loadStream();
+ // DERBY-3238
+ // fix up any duplicate streams, for instance in the case of an update with a trigger,
+ // all the columns are read as update columns even if they are not updated, so
+ // the update column will still have a reference to the original stream.
+ // If we knew from this context that this was an update and we knew the number
+ // of columns in the base table we would be able to calculate exactly the offset to
+ // check, but we don't have that information from this context.
+ // If DERBY-1482 is fixed, perhaps this code can be removed.
+
+ if (stream != null)
+ for (int i = 1; i <= row.nColumns(); i++)
+ {
+ DataValueDescriptor c = row.getColumn(i);
+ if (c instanceof StreamStorable)
+ if (((StreamStorable)c).returnStream() == stream)
+ row.setColumn(i, col.getClone());
+ }
+ }
}
- }
}
/**
Index: java/engine/org/apache/derby/iapi/types/SQLBlob.java
===================================================================
--- java/engine/org/apache/derby/iapi/types/SQLBlob.java (revision 591113)
+++ java/engine/org/apache/derby/iapi/types/SQLBlob.java (working copy)
@@ -110,6 +110,16 @@
setWidth(desiredType.getMaximumWidth(), 0, true);
}
+ /**
+ * Tell if this blob is length less.
+ *
+ * @return true if the length of the blob is not known,
+ * false otherwise
+ */
+ private final boolean isLengthLess() {
+ return (stream != null && streamLength < 0);
+ }
+
// The method setWidth is only(?) used to adopt the value
// to the casted domain/size. BLOBs behave different
// from the BIT types in that a (CAST (X'01' TO BLOB(1024)))
@@ -127,8 +137,10 @@
if (isNull())
return this;
+ if (isLengthLess())
+ return this;
- int sourceWidth = getLength();
+ int sourceWidth = getLength();
// need to truncate?
if (sourceWidth > desiredWidth) {
Index: java/testing/org/apache/derbyTesting/functionTests/tests/lang/bug3238.java
===================================================================
--- java/testing/org/apache/derbyTesting/functionTests/tests/lang/bug3238.java (revision 0)
+++ java/testing/org/apache/derbyTesting/functionTests/tests/lang/bug3238.java (revision 0)
@@ -0,0 +1,254 @@
+/*
+
+ Derby - Class org.apache.derbyTesting.functionTests.tests.lang.bug3238
+
+ Copyright 2007 The Apache Software Foundation or its licensors, as applicable.
+
+ Licensed 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.lang;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.PreparedStatement;
+import java.sql.Statement;
+import java.sql.SQLException;
+import org.apache.derby.tools.ij;
+import org.apache.derby.tools.JDBCDisplayUtil;
+import java.io.ByteArrayInputStream;
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Test fix for triggers on tables with lobs
+ */
+public class bug3238 {
+
+ public static void main (String args[])
+ {
+ try {
+ /* Load the JDBC Driver class */
+ // use the ij utility to read the property file and
+ // make the initial connection.
+ ij.getPropertyArg(args);
+ Connection conn = ij.startJBMS();
+ testBlobInTriggerTable(conn,1024);
+ testBlobInTriggerTable(conn,16384);
+ testBlobInTriggerTable(conn,1024 *32 -1);
+ testBlobInTriggerTable(conn,1024 *32);
+ testBlobInTriggerTable(conn,1024 *32+1);
+ testBlobInTriggerTable(conn,1024 *64 -1);
+ testBlobInTriggerTable(conn,1024 *64);
+ testBlobInTriggerTable(conn,1024 *64+1);
+
+ testClobInTriggerTable(conn,1024);
+ testClobInTriggerTable(conn,16384);
+ testClobInTriggerTable(conn,1024 *32 -1);
+ testClobInTriggerTable(conn,1024 *32);
+ testClobInTriggerTable(conn,1024 *32+1);
+ testClobInTriggerTable(conn,1024 *64 -1);
+ testClobInTriggerTable(conn,1024 *64);
+ testClobInTriggerTable(conn,1024 *64+1);
+ testUpdateTriggerOnClobColumn(conn);
+
+ // no exception. we passed
+ System.out.println("PASSED");
+ conn.close();
+ } catch (Exception e) {
+ System.out.println("FAIL -- unexpected exception "+e);
+ JDBCDisplayUtil.ShowException(System.out, e);
+ e.printStackTrace();
+ }
+ }
+ /**
+ * Create a table with after update trigger on non-lob column.
+ * Insert two blobs of size blobSize into table and perform update
+ * on str1 column to fire trigger. Helper method called from
+ * testBlobInTriggerTable
+ *
+ * @param conn connection to use
+ * @param blobSize size of blob to test.
+ * @throws SQLException
+ * @throws IOException
+ */
+ private static void testBlobInTriggerTable(Connection conn, int blobSize) throws SQLException, IOException {
+
+
+ String trig = " create trigger t_lob1 after update of str1 on lob1 ";
+ trig = trig + " REFERENCING OLD AS old NEW AS new FOR EACH ROW MODE DB2SQL ";
+ trig = trig + " insert into t_lob1_log(oldvalue, newvalue) values (old.str1, new.str1)";
+
+ Statement s = conn.createStatement();
+
+ s.executeUpdate("create table LOB1 (str1 Varchar(80), b_lob BLOB(50M), b_lob2 BLOB(50M))");
+ s.executeUpdate("create table t_lob1_log(oldvalue varchar(80), newvalue varchar(80), chng_time timestamp default current_timestamp)");
+ s.executeUpdate(trig);
+ conn.commit();
+
+ // --- add a blob
+ PreparedStatement ps = conn.prepareStatement("INSERT INTO LOB1 VALUES (?, ?, ?)");
+
+ ps.setString(1, blobSize +"");
+
+
+ byte[] arr = new byte[blobSize];
+ for (int i = 0; i < arr.length; i++)
+ arr[i] = (byte)8;
+
+ // - set the value of the input parameter to the input stream
+ // use a couple blobs so we are sure it works with multiple lobs
+ ps.setBinaryStream(2, new ByteArrayInputStream(arr) , blobSize);
+ ps.setBinaryStream(3, new ByteArrayInputStream(arr) , blobSize);
+ ps.execute();
+
+ conn.commit();
+ // Now executing update to fire trigger
+ s.executeUpdate("update LOB1 set str1 = str1 || ' '");
+ s.executeUpdate("drop table lob1");
+ s.executeUpdate("drop table t_lob1_log");
+
+ }
+
+ /**
+ * Create a table with after update trigger on non-lob column.
+ * Insert clob of size clobSize into table and perform update
+ * on str1 column to fire trigger. Helper method called from
+ * testClobInTriggerTable
+ * @param conn Connection
+ * @param clobSize size of clob to test
+ * @throws SQLException
+ * @throws IOException
+ */
+ private static void testClobInTriggerTable(Connection conn,int clobSize) throws SQLException, IOException {
+
+ // --- add a clob
+ String trig = " create trigger t_lob1 after update of str1 on lob1 ";
+ trig = trig + " REFERENCING OLD AS old NEW AS new FOR EACH ROW MODE DB2SQL ";
+ trig = trig + " insert into t_lob1_log(oldvalue, newvalue) values (old.str1, new.str1)";
+
+ Statement s = conn.createStatement();
+
+ s.executeUpdate("create table LOB1 (str1 Varchar(80), c_lob CLOB(50M))");
+ s.executeUpdate("create table t_lob1_log(oldvalue varchar(80), newvalue varchar(80), chng_time timestamp default current_timestamp)");
+ s.executeUpdate(trig);
+ conn.commit();
+
+ PreparedStatement ps = conn.prepareStatement("INSERT INTO LOB1 VALUES (?, ?)");
+
+ ps.setString(1, clobSize +"");
+
+
+ char[] arr = new char[clobSize];
+ for (int i = 0; i < arr.length; i++)
+ arr[i] = 'a';
+
+ // - set the value of the input parameter to the input stream
+ ps.setCharacterStream(2, new CharArrayReader(arr) , clobSize);
+ ps.execute();
+ conn.commit();
+
+ // Now executing update to fire trigger
+ s.executeUpdate("update LOB1 set str1 = str1 || ' '");
+ s.executeUpdate("drop table lob1");
+ s.executeUpdate("drop table t_lob1_log");
+ }
+
+
+ /*
+ * Test an update trigger on a Clob column
+ *
+ * @param conn Connection to use
+ */
+ private static void testUpdateTriggerOnClobColumn(Connection conn) throws SQLException, IOException, Exception
+ {
+ Statement s = conn.createStatement();
+ String trig = " create trigger t_lob1 after update of str1 on lob1 ";
+ trig = trig + " REFERENCING OLD AS old NEW AS new FOR EACH ROW MODE DB2SQL ";
+ trig = trig + " insert into t_lob1_log(oldvalue, newvalue) values (old.str1, new.str1)";
+ s.executeUpdate("create table LOB1 (str1 Varchar(80), C_lob CLOB(50M))");
+ s.executeUpdate("create table t_lob1_log(oldvalue varchar(80), newvalue varchar(80), chng_time timestamp default current_timestamp)");
+ s.executeUpdate(trig);
+ conn.commit();
+ PreparedStatement ps = conn.prepareStatement("INSERT INTO LOB1 VALUES (?, ?)");
+ int clobSize = 1024*64+1;
+ ps.setString(1, clobSize +"");
+
+
+ // - set the value of the input parameter to the input stream
+ ps.setCharacterStream(2, makeCharArrayReader('a', clobSize), clobSize);
+ ps.execute();
+ conn.commit();
+
+
+ PreparedStatement ps2 = conn.prepareStatement("update LOB1 set c_lob = ? where str1 = '" + clobSize + "'");
+ ps2.setCharacterStream(1,makeCharArrayReader('b',clobSize), clobSize);
+ ps2.executeUpdate();
+ conn.commit();
+ // --- reading the clob make sure it was updated
+ ResultSet rs = s.executeQuery("SELECT * FROM LOB1 where str1 = '" + clobSize + "'");
+ rs.next();
+
+ Reader r = rs.getCharacterStream(2);
+ int count = 0;
+ int c;
+ do {
+ c = r.read();
+ if (c!= -1)
+ {
+ count++;
+ assertEquals('b',c);
+ }
+ } while (c != -1);
+
+ assertEquals(clobSize,count);
+ rs.close();
+ s.executeUpdate("drop table lob1");
+ s.executeUpdate("drop table t_lob1_log");
+
+
+ }
+
+ /**
+ * Make a CharArrayReader
+ * @param c character to repeat
+ * @param size size of array
+ * @return CharArrayReader of specified character repeating the specified character
+ */
+ private static CharArrayReader makeCharArrayReader(char c, int size)
+ {
+ char[] arr = new char[size];
+ for (int i = 0; i < arr.length; i++)
+ arr[i] = c;
+ return new CharArrayReader(arr);
+ }
+
+
+ private static void assertEquals(int expected, int val) throws Exception
+ {
+ if (expected != val)
+ throw new Exception("value:" + val + " does not equal expected value:"+ expected);
+ }
+
+private void assertEquals(char expected, char val) throws Exception
+ {
+ if (expected != val)
+ throw new Exception("value:" + val + " does not equal expected value:"+ expected);
+ }
+
+
+}
+
Property changes on: java\testing\org\apache\derbyTesting\functionTests\tests\lang\bug3238.java
___________________________________________________________________
Name: svn:eol-style
+ native
Index: java/testing/org/apache/derbyTesting/functionTests/master/bug3238.out
===================================================================
--- java/testing/org/apache/derbyTesting/functionTests/master/bug3238.out (revision 0)
+++ java/testing/org/apache/derbyTesting/functionTests/master/bug3238.out (revision 0)
@@ -0,0 +1 @@
+PASSED
Property changes on: java\testing\org\apache\derbyTesting\functionTests\master\bug3238.out
___________________________________________________________________
Name: svn:eol-style
+ native
Index: java/testing/org/apache/derbyTesting/functionTests/suites/derbylang.runall
===================================================================
--- java/testing/org/apache/derbyTesting/functionTests/suites/derbylang.runall (revision 591113)
+++ java/testing/org/apache/derbyTesting/functionTests/suites/derbylang.runall (working copy)
@@ -19,6 +19,7 @@
lang/bug4356.java
lang/bug5052rts.java
lang/bug5054.java
+lang/bug3238.java
lang/case.sql
lang/cast.sql
lang/casting.java