Author: kristwaa Date: Tue Jun 5 04:08:37 2007 New Revision: 544445 URL: http://svn.apache.org/viewvc?view=rev&rev=544445 Log: DERBY-2646: Cleanup of Clob control/support structures. Added more tests. Patch file: derby-2646-05b-tests.diff Added: db/derby/code/trunk/java/testing/org/apache/derby/impl/ db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/ db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerStoreStreamClobTest.java (with props) db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerTemporaryClobTest.java (with props) db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/InternalClobTest.java (with props) db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallStoreStreamClobTest.java (with props) db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallTemporaryClobTest.java (with props) db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/_Suite.java (with props) db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/ClobTest.java (with props) Added: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerStoreStreamClobTest.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerStoreStreamClobTest.java?view=auto&rev=544445 ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerStoreStreamClobTest.java (added) +++ db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerStoreStreamClobTest.java Tue Jun 5 04:08:37 2007 @@ -0,0 +1,63 @@ +/* + + Derby - Class org.apache.derby.impl.jdbc.BiggerStoreStreamTest + + 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.impl.jdbc; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Tests basic operations on a bigger read-only Clob from the store module. + */ +public class BiggerStoreStreamClobTest + extends InternalClobTest { + + private static final long CLOBLENGTH = 67*1024+19; // ~97 KB + private static final long BYTES_PER_CHAR = 1; // All modern Latin + + public BiggerStoreStreamClobTest(String name) { + super(name); + } + + public void setUp() + throws Exception { + super.initialCharLength = CLOBLENGTH; + super.initialByteLength = CLOBLENGTH; // The fake stream uses ascii. + super.bytesPerChar = BYTES_PER_CHAR; + EmbedStatement embStmt = (EmbedStatement)createStatement(); + iClob = new StoreStreamClob(new FakeStoreStream(CLOBLENGTH), embStmt); + assertEquals(CLOBLENGTH, iClob.getCharLength()); + } + + public void tearDown() + throws Exception { + this.iClob.release(); + this.iClob = null; + super.tearDown(); + } + + public static Test suite() { + TestSuite suite = new TestSuite(BiggerStoreStreamClobTest.class, + "BiggerStoreStreamClobTest suite"); + return suite; + } +} // End class BiggerStoreStreamClobTest Propchange: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerStoreStreamClobTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerTemporaryClobTest.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerTemporaryClobTest.java?view=auto&rev=544445 ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerTemporaryClobTest.java (added) +++ db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerTemporaryClobTest.java Tue Jun 5 04:08:37 2007 @@ -0,0 +1,77 @@ +/* + + Derby - Class org.apache.derby.impl.jdbc.BiggerTemporaryClobTest + + 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.impl.jdbc; + +import org.apache.derbyTesting.functionTests.util.streams.CharAlphabet; +import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetReader; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Test basic operations on a read-wrote Clob that is kept on disk. + */ +public class BiggerTemporaryClobTest + extends InternalClobTest { + + private static final long CLOBLENGTH = 287*1024-115; //~287 KB + private static final long BYTES_PER_CHAR = 3; // Only Tamil characters. + + public BiggerTemporaryClobTest(String name) { + super(name); + } + + /** + * Creates a bigger read-write Clob that is being kept on disk due to its + * size. + */ + public void setUp() + throws Exception { + super.initialCharLength = CLOBLENGTH; + super.initialByteLength = CLOBLENGTH *3; // Only Tamil characters. + super.bytesPerChar = BYTES_PER_CHAR; + EmbedStatement embStmt = (EmbedStatement)createStatement(); + EmbedConnection embCon =(EmbedConnection)getConnection(); + iClob = new ClobStreamControl(embCon.getDBName(), embStmt); + transferData( + new LoopingAlphabetReader(CLOBLENGTH, CharAlphabet.cjkSubset()), + iClob.getWriter(1L), + CLOBLENGTH); + assertEquals(CLOBLENGTH, iClob.getCharLength()); + } + + public void tearDown() + throws Exception { + this.iClob.release(); + this.iClob = null; + super.tearDown(); + } + + public static Test suite() + throws Exception { + Class theClass = BiggerTemporaryClobTest.class; + TestSuite suite = new TestSuite(theClass, "BiggerTemporaryClobTest suite"); + suite.addTest(addModifyingTests(theClass)); + return suite; + } +} // End class BiggerTemporaryClobTest Propchange: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/BiggerTemporaryClobTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/InternalClobTest.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/InternalClobTest.java?view=auto&rev=544445 ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/InternalClobTest.java (added) +++ db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/InternalClobTest.java Tue Jun 5 04:08:37 2007 @@ -0,0 +1,479 @@ +package org.apache.derby.impl.jdbc; + +import java.io.EOFException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.sql.Blob; +import java.util.ArrayList; +import java.util.List; +import org.apache.derby.iapi.error.StandardException; +import org.apache.derby.iapi.types.Resetable; +import org.apache.derbyTesting.functionTests.util.streams.ByteAlphabet; +import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetStream; +import org.apache.derbyTesting.junit.BaseJDBCTestCase; + +import java.io.IOException; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.InputStream; +import java.io.Reader; +import java.io.Writer; + +import java.sql.SQLException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * A set of tests for the {@link org.apache.derby.impl.jdbc.InternalClob} + * interface. + *

+ * The tests are split into two categories; read-only and modifying tests. The + * latter should only be run if @{link InternalClob#isWritable} is true. + *

+ * Implementation notes: To implement a test by subclassing, a few + * things must be done. First of all, many of the tests require that the number + * of bytes per character is fixed. The following variables must be initialized + * by the subclass: + *

+ */ +public abstract class InternalClobTest + extends BaseJDBCTestCase { + + /** Buffer used for reading/skipping from streams. */ + final static int BUFFER_SIZE = 4096; + + /** + * The InternalClob used by the tests, the concrete implementation is + * chosen by the subclasses. + */ + protected InternalClob iClob = null; + protected long initialByteLength = Long.MIN_VALUE; + protected long initialCharLength = Long.MIN_VALUE; + protected long bytesPerChar = Long.MIN_VALUE; + + InternalClobTest(String name) { + super(name); + } + + public void tearDown() + throws Exception { + super.tearDown(); + } + + protected static Test addModifyingTests(Class theClass) + throws Exception { + TestSuite suite = new TestSuite("Modifying InternalClob test suite"); + Method[] methods = theClass.getMethods(); + List testMethods = new ArrayList(); + for (int i=0; i < methods.length; i++) { + Method m = methods[i]; + if (m.getReturnType().equals(Void.TYPE) && + m.getName().startsWith("modTest") && + m.getParameterTypes().length == 0) { + testMethods.add(m.getName()); + } + } + Constructor ctor = theClass.getConstructor(new Class[] {String.class}); + for (int i=0; i < testMethods.size(); i++) { + suite.addTest((Test)ctor.newInstance( + new Object[] {(String)testMethods.get(i)})); + } + return suite; + } + + /** + * This test just ensures the initial variables are set in a sane way. + * As can be seen, these tests require the number of bytes per character to + * be fixed, thus only certain parts of Unicode can be used with these tests + * when using UTF-8 as encoding. + */ + public void testSanity() { + assertEquals(initialByteLength, initialCharLength * bytesPerChar); + assertTrue(initialCharLength > 25); + } + + /* All the XXXAfterRelease tests just checks that an exception is thrown if + * the method is invoked after the Clob has been released. Note that an + * UnsupportedOperationException is allowed for read-only Clobs. + */ + + public void testGetByteLengthAfterRelease() + throws IOException, SQLException { + iClob.release(); + try { + iClob.getByteLength(); + fail("Exception should have been raised, but was not"); + } catch (IllegalStateException ise) { + // This is as expected. + } + } + + public void testGetBytePositionAfterRelease() + throws IOException, SQLException { + iClob.release(); + try { + iClob.getBytePosition(1L); + fail("Exception should have been raised, but was not"); + } catch (IllegalStateException ise) { + // This is as expected. + } + } + public void testGetCharLengthAfterRelease() + throws IOException, SQLException { + iClob.release(); + try { + iClob.getCharLength(); + fail("Exception should have been raised, but was not"); + } catch (IllegalStateException ise) { + // This is as expected. + } + } + + public void testGetRawByteStreamAfterRelease() + throws IOException, SQLException { + iClob.release(); + try { + iClob.getRawByteStream(); + fail("Exception should have been raised, but was not"); + } catch (IllegalStateException ise) { + // This is as expected. + } + } + + public void testGetReaderAfterRelease() + throws IOException, SQLException { + iClob.release(); + try { + iClob.getReader(1L); + fail("Exception should have been raised, but was not"); + } catch (IllegalStateException ise) { + // This is as expected. + } + } + + public void testReleaseAfterRelase() + throws IOException, SQLException { + iClob.release(); + // This one should be a no-op and not fail. + iClob.release(); + } + + public void testGetWriterAfterRelease() + throws IOException, SQLException { + iClob.release(); + try { + iClob.getWriter(1L); + fail("Exception should have been raised, but was not"); + } catch (IllegalStateException ise) { + // This is as expected. + } catch (UnsupportedOperationException uoe) { + assertFalse("Must support getWriter if the Clob is writable", + iClob.isWritable()); + } + } + + public void testInsertStringAfterRelease() + throws IOException, SQLException { + iClob.release(); + try { + iClob.insertString("What a nice sunny day :)", 1L); + fail("Exception should have been raised, but was not"); + } catch (IllegalStateException ise) { + // This is as expected. + } catch (UnsupportedOperationException uoe) { + assertFalse("Must support insertString if the Clob is writable", + iClob.isWritable()); + } + } + + public void testTruncateAfterRelease() + throws IOException, SQLException { + iClob.release(); + try { + iClob.truncate(1L); + fail("Exception should have been raised, but was not"); + } catch (IllegalStateException ise) { + // This is as expected. + } catch (UnsupportedOperationException uoe) { + assertFalse("Must support trucate if the Clob is writable", + iClob.isWritable()); + } + } + + /* End of XXXAfterRelease tests. */ + + public void testGetByteLength() + throws IOException, SQLException { + assertEquals(this.initialByteLength, iClob.getByteLength()); + } + + public void testGetBytePosition_first() + throws IOException, SQLException { + assertEquals(0L, iClob.getBytePosition(1L)); + } + + public void testGetBytePosition_second() + throws IOException, SQLException { + assertEquals(bytesPerChar, iClob.getBytePosition(2L)); + } + + public void testGetBytePosition_last() + throws IOException, SQLException { + assertEquals(initialByteLength - bytesPerChar, + iClob.getBytePosition(this.initialCharLength)); + } + + public void testGetBytePosition_lastPlussOne() + throws IOException, SQLException { + assertEquals(initialByteLength, + iClob.getBytePosition(this.initialCharLength +1)); + } + + public void testGetBytePosition_lastPlussTwo() + throws IOException, SQLException { + try { + long pos = iClob.getBytePosition(this.initialCharLength +2); + fail("Length +2 should have no valid byte position, got " + pos); + } catch (EOFException ioe) { + // As expected for Derby. + } + } + + public void testGetBytePosition_lastPlussThousand() + throws IOException, SQLException { + try { + long pos = iClob.getBytePosition(this.initialCharLength +1000); + fail("Length +1000 should have no valid byte position, got " + pos); + } catch (EOFException ioe) { + // As expected for Derby. + } + } + + public void testGetCharLength() + throws IOException, SQLException { + assertEquals(this.initialCharLength, iClob.getCharLength()); + } + + public void testGetReaderAtStartPos() + throws IOException, SQLException { + Reader reader = iClob.getReader(1L); + assertEquals(initialCharLength, + readFromStream(reader, initialCharLength)); + assertEquals(-1, reader.read()); + reader.close(); + } + + public void testGetReaderAtSecondPos() + throws IOException, SQLException { + Reader reader = iClob.getReader(2L); + assertEquals(initialCharLength -1, + readFromStream(reader, initialCharLength -1)); + assertEquals(-1, reader.read()); + reader.close(); + } + + public void testGetReaderAtEndPos() + throws IOException, SQLException { + Reader reader = iClob.getReader(initialCharLength); + assertTrue(reader.read() != -1); + assertEquals(-1, reader.read()); + reader.close(); + } + + public void testGetReaderAfterEndPos() + throws IOException, SQLException { + Reader reader = iClob.getReader(initialCharLength +1); + assertEquals(-1, reader.read()); + reader.close(); + try { + reader = iClob.getReader(initialCharLength +767); + fail("Got a reader at a position greater than the Clob"); + } catch (EOFException eofe) { + // As expected + } + } + + public void modTestInsertString_append_small() + throws IOException, SQLException { + long cLength = iClob.getCharLength(); + iClob.insertString("END", cLength +1); + assertEquals(cLength + 3, iClob.getCharLength()); + assertEquals("END", subString(iClob, cLength +1, 3)); + } + + /** + * Replaces a piece of the Clob. + */ + public void modTestInsertString_replace_small() + throws IOException, SQLException { + String replacement = "MIDDLE"; + iClob.insertString(replacement, 15L); + assertEquals(initialCharLength, iClob.getCharLength()); + assertEquals(replacement, + this.subString(iClob, 15L, replacement.length())); + } + + /** + * Replaces the last part of the Clob and then adds a little more, all in + * one operation. + */ + public void modTestInsertString_replace_and_append_small() + throws IOException, SQLException { + String replacement = "REPLACING_AND_APPENDING!"; + assertTrue("Length of replacement text must be even", + replacement.length() % 2 == 0); + int halfLength = replacement.length() / 2; + iClob.insertString(replacement, + initialCharLength - halfLength +1); + assertEquals("Wrong length after replace and append", + initialCharLength + halfLength, iClob.getCharLength()); + assertEquals("Corresponding substring does not match replacement", + replacement, + this.subString(iClob, initialCharLength - halfLength +1, + replacement.length())); + } + + /** + * Extracts a substring from the Clob. + * + * @param clob the clob to extract from + * @param pos the starting position in the Clob + * @param count the number of characters to extract. Note that the actual + * number of characters extracted might be smaller if there are not + * enough characters in the Clob. + * @return A substring up to count characters long. + */ + protected static String subString(InternalClob clob, long pos, int count) + throws IOException, SQLException { + Reader reader = clob.getReader(pos); + char[] sub = new char[count]; + int offset = 0; + while (offset < count) { + long read = reader.read(sub, offset, count - offset); + if (read == -1) { + break; + } + offset += read; + } + return String.copyValueOf(sub); + } + + /** + * Transfers data from the source to the destination. + */ + public static long transferData(Reader src, Writer dest, long charsToCopy) + throws IOException { + BufferedReader in = new BufferedReader(src); + BufferedWriter out = new BufferedWriter(dest, BUFFER_SIZE); + char[] bridge = new char[BUFFER_SIZE]; + long charsLeft = charsToCopy; + int read; + while ((read = in.read(bridge, 0, (int)Math.min(charsLeft, BUFFER_SIZE))) > 0) { + out.write(bridge, 0, read); + charsLeft -= read; + } + in.close(); + // Don't close the stream, in case it will be written to again. + out.flush(); + return charsToCopy - charsLeft; + } + + /** + * Attemps to read the specified number of characters from the stream. + */ + public static final long readFromStream(Reader in, long characterCount) + throws IOException { + char[] buf = new char[BUFFER_SIZE]; + long leftToRead = characterCount; + while (leftToRead > 0) { + long read = + in.read(buf, 0,(int)Math.min(leftToRead, (long)BUFFER_SIZE)); + if (read == 0) { + break; + } + leftToRead -= read; + } + return characterCount - leftToRead; + } + + /** + * A fake store stream passed in to StoreStreamClob. + *

+ * Note that it is made such that init must be called before using the + * stream, or after close, or else a NPE will be thrown. + */ + static class FakeStoreStream + extends InputStream + implements Resetable { + + private static final ByteAlphabet ALPHABET = + ByteAlphabet.modernLatinLowercase(); + private LoopingAlphabetStream stream = null; + private final long length; + private int encodedLengthRemaining = 2; + private int eofMarkerRemaining = 3; + + public FakeStoreStream(long length) { + super(); + this.length = length; + } + + public int read(byte[] b, int off, int len) + throws IOException { + int count = 0; + while (count < len) { + int ret = read(); + if (ret == -1) { + if (count == 0) { + // Inform about EOF. + return -1; + } else { + // Return what we got. + break; + } + } + b[off+count++] = (byte)ret; + } + return count; + } + + public int read() throws IOException { + if (this.encodedLengthRemaining > 0) { + this.encodedLengthRemaining--; + return 0; + } + int b = this.stream.read(); + if (b == -1 && this.eofMarkerRemaining > 0) { + if (this.eofMarkerRemaining == 3) { + b = 0xe0; + } else { + b = 0x00; + } + this.eofMarkerRemaining--; + } + return b; + } + + public void resetStream() throws IOException, StandardException { + this.stream = new LoopingAlphabetStream(length, ALPHABET); + this.encodedLengthRemaining = 2; + this.eofMarkerRemaining = 3; + } + + public void initStream() throws StandardException { + this.stream = new LoopingAlphabetStream(length, ALPHABET); + this.encodedLengthRemaining = 2; + this.eofMarkerRemaining = 3; + } + + public void closeStream() { + this.stream = null; + } + + } // End private static class FakeStoreStream +} // End abstract class InternalClobTest Propchange: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/InternalClobTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallStoreStreamClobTest.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallStoreStreamClobTest.java?view=auto&rev=544445 ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallStoreStreamClobTest.java (added) +++ db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallStoreStreamClobTest.java Tue Jun 5 04:08:37 2007 @@ -0,0 +1,63 @@ +/* + + Derby - Class org.apache.derby.impl.jdbc.SmallStoreStreamTest + + 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.impl.jdbc; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Tests basic operations on a small read-only Clob from the store module. + */ +public class SmallStoreStreamClobTest + extends InternalClobTest { + + private static final long CLOBLENGTH = 1197; + private static final long BYTES_PER_CHAR = 1; // All modern Latin + + public SmallStoreStreamClobTest(String name) { + super(name); + } + + public void setUp() + throws Exception { + super.initialCharLength = CLOBLENGTH; + super.initialByteLength = CLOBLENGTH; // The fake stream uses ascii. + super.bytesPerChar = BYTES_PER_CHAR; + EmbedStatement embStmt = (EmbedStatement)createStatement(); + iClob = new StoreStreamClob(new FakeStoreStream(CLOBLENGTH), embStmt); + assertEquals(CLOBLENGTH, iClob.getCharLength()); + } + + public void tearDown() + throws Exception { + this.iClob.release(); + this.iClob = null; + super.tearDown(); + } + + public static Test suite() { + TestSuite suite = new TestSuite(SmallStoreStreamClobTest.class, + "SmallStoreStreamClobTest suite"); + return suite; + } +} // End class SmallStoreStreamClobTest Propchange: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallStoreStreamClobTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallTemporaryClobTest.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallTemporaryClobTest.java?view=auto&rev=544445 ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallTemporaryClobTest.java (added) +++ db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallTemporaryClobTest.java Tue Jun 5 04:08:37 2007 @@ -0,0 +1,80 @@ +/* + + Derby - Class org.apache.derby.impl.jdbc.SmallTemporaryClobTest + + 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.impl.jdbc; + +import org.apache.derbyTesting.functionTests.util.streams.CharAlphabet; +import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetReader; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Test basic operations on a small temporary Clob. + *

+ * The test is intended to use sizes that makes the Clob stay in memory (i.e. + * it is not being pushed to disk due to size). + */ +public class SmallTemporaryClobTest + extends InternalClobTest { + + private static final long CLOBLENGTH = 1027; + private static final long BYTES_PER_CHAR = 3; + + public SmallTemporaryClobTest(String name) { + super(name); + } + + /** + * Creates a small read-write Clob that is kept in memory. + */ + public void setUp() + throws Exception { + super.initialCharLength = CLOBLENGTH; + super.initialByteLength = CLOBLENGTH *3; // All tamil letters. + super.bytesPerChar = BYTES_PER_CHAR; + EmbedStatement embStmt = (EmbedStatement)createStatement(); + EmbedConnection embCon =(EmbedConnection)getConnection(); + iClob = new ClobStreamControl(embCon.getDBName(), embStmt); + transferData( + new LoopingAlphabetReader(CLOBLENGTH, CharAlphabet.tamil()), + iClob.getWriter(1L), + CLOBLENGTH); + assertEquals(CLOBLENGTH, iClob.getCharLength()); + } + + public void tearDown() + throws Exception { + this.iClob.release(); + this.iClob = null; + super.tearDown(); + } + + public static Test suite() + throws Exception { + Class theClass = SmallTemporaryClobTest.class; + TestSuite suite = new TestSuite(theClass, "SmallTemporaryClobTest suite"); + suite.addTest(addModifyingTests(theClass)); + return suite; + } + +} // End class SmallTemporaryClobTest Propchange: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/SmallTemporaryClobTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/_Suite.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/_Suite.java?view=auto&rev=544445 ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/_Suite.java (added) +++ db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/_Suite.java Tue Jun 5 04:08:37 2007 @@ -0,0 +1,52 @@ +/* + + Derby - Class org.apache.derby.impl.jdbc._Suite + + 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.impl.jdbc; + +import org.apache.derbyTesting.junit.BaseTestCase; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class _Suite + extends BaseTestCase { + + /** + * Use suite method instead. + */ + private _Suite(String name) { + super(name); + } + + public static Test suite() + throws Exception { + + TestSuite suite = new TestSuite("jdbc.impl package-private"); + + suite.addTest(SmallTemporaryClobTest.suite()); + suite.addTest(BiggerTemporaryClobTest.suite()); + suite.addTest(SmallStoreStreamClobTest.suite()); + suite.addTest(BiggerStoreStreamClobTest.suite()); + + return suite; + } +} // End class _Suite Propchange: db/derby/code/trunk/java/testing/org/apache/derby/impl/jdbc/_Suite.java ------------------------------------------------------------------------------ svn:eol-style = native Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/ClobTest.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/ClobTest.java?view=auto&rev=544445 ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/ClobTest.java (added) +++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/ClobTest.java Tue Jun 5 04:08:37 2007 @@ -0,0 +1,461 @@ +/* + + Derby - Class org.apache.derbyTesting.functionTests.tests.jdbcapi.ClobTest + + 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.jdbcapi; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; + +import java.sql.Connection; +import java.sql.Clob; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import junit.framework.Test; + +import org.apache.derbyTesting.functionTests.util.streams.CharAlphabet; +import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetReader; +import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetStream; + +import org.apache.derbyTesting.junit.BaseJDBCTestCase; +import org.apache.derbyTesting.junit.BaseJDBCTestSetup; +import org.apache.derbyTesting.junit.TestConfiguration; + + + +/** + * Test the methods defined by the {@link java.sql.Clob} interface. + *

+ * Only methods defined by JDBC 3 or earlier are tested here, and the focus of + * the test is the interface methods. Less attention is given to inserting + * Clobs and fetching Clobs from the database. + */ +public class ClobTest + extends BaseJDBCTestCase { + + /** Buffer size to use when transferring data between streams. */ + private static final int TRANSFER_BUFFER_SIZE = 4*1024; // 4 KB + + /** Constant for Clob.setString method. */ + private static final int SET_STRING = 1; + /** Constant for Clob.setAsciiStream method. */ + private static final int SET_ASCII_STREAM = 2; + /** Constant for Clob.setCharacterStream method. */ + private static final int SET_CHARACTER_STREAM = 4; + + /** Test data, 18 characters long, containing only Norwegian letters. */ + private static final String NORWEGIAN_LETTERS = + "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5" + + "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5"; + + /** Result set used to obtain Clob. */ + private ResultSet rs = null; + /** + * The Clob used for testing. + * It is reinitialized to a Clob containing the empty string for each test. + */ + private Clob clob = null; + + public ClobTest(String testName) { + super(testName); + } + + public void testGetSubString_PosOneTooBig() + throws SQLException { + long length = this.clob.length(); + assertEquals("", this.clob.getSubString(length +1, 10)); + } + + public void testGetSubString_PosTooBig() { + try { + this.clob.getSubString(999, 10); + fail("getSubString with pos larger than clob length must fail"); + } catch (SQLException sqle) { + assertSQLState("XJ076", sqle); + } + } + + public void testGetSubString_PosNegative() + throws SQLException { + try { + this.clob.getSubString(-123, 10); + fail("getSubString with negative position should fail"); + } catch (SQLException sqle) { + assertSQLState("XJ070", sqle); + } + } + + public void testGetSubString_RequestZeroLength_PosValid() + throws SQLException { + // Tests if an exception is thrown or not. + // According to the JDBC spec, 0 is a valid length. + assertEquals("", this.clob.getSubString(1L, 0)); + } + + public void testGetSubString_RequestZeroLength_PosTooBig() + throws SQLException { + try { + this.clob.getSubString(999L, 0); + } catch (SQLException sqle) { + assertSQLState("XJ076", sqle); + } + } + + /** + * Tests if big strings can be handled. + *

+ * The motivation for the test is to make sure big buffers are filled with + * the call to read inside a loop. Big in this sense means bigger than some + * internal buffer. This is typically around 8 KB or so, but we try + * something considerably bigger. If a char/byte array is attempted filled + * with a single call to read, the resulting string wil typically contain + * \u0000 at positions after the size of the internal buffer. + */ + public void testGetSubString_BiggerThanInternalBuffer() + throws IOException, SQLException { + int stringLength = 1*1024*1024; // 1 M characters + transferData(new LoopingAlphabetReader(stringLength), + this.clob.setCharacterStream(1L), + TRANSFER_BUFFER_SIZE); + String obtained = this.clob.getSubString(1, stringLength); + assertEquals("Incorrect string length", + stringLength, obtained.length()); + // Obtain the string we inserted for comparison. + CharArrayWriter charWriter = new CharArrayWriter(); + transferData(new LoopingAlphabetReader(stringLength), charWriter, + TRANSFER_BUFFER_SIZE); + assertEquals("String do not match", + charWriter.toString(), obtained); + } + + public void testLengthOnEmptyClob() + throws SQLException { + assertEquals(0, this.clob.length()); + } + + public void testInsertStringOnEmptyClob_Singlebyte() + throws SQLException { + String content = "This is the new Clob content."; + this.clob.setString(1, content); + assertEquals("Incorrect length reported", + content.length(), this.clob.length()); + assertEquals("Clob content is incorrect", + content, this.clob.getSubString(1, content.length())); + } + + public void testInsertStringOnEmptyClob_Multibyte() + throws SQLException { + String content = "A few Norwegian letters: \u00e6, \u00e5, \u00f8."; + this.clob.setString(1, content); + assertEquals("Incorrect length reported", + content.length(), this.clob.length()); + assertEquals("Clob content is incorrect", + content, this.clob.getSubString(1, content.length())); + } + + public void testInsertStringInMiddle_Multibyte() + throws SQLException { + // Add some content to work on first. + this.clob.setString(1, NORWEGIAN_LETTERS); + assertEquals(NORWEGIAN_LETTERS, + this.clob.getSubString(1, NORWEGIAN_LETTERS.length())); + + // Replace a portion with single byte characters. + char[] modifiedContent = NORWEGIAN_LETTERS.toCharArray(); + // Replace chars at 0-based indexes 4,5 and 8 + modifiedContent[4] = 'a'; + modifiedContent[5] = 'b'; + modifiedContent[8] = 'c'; + String newContent = String.copyValueOf(modifiedContent); + // Do this in a "funny" order, or else it currently fails when running + // with the client driver. + assertEquals(1, this.clob.setString(9, "c")); + assertEquals(1, this.clob.setString(5, "a")); + assertEquals(1, this.clob.setString(6, "b")); + if (BaseJDBCTestCase.usingEmbedded()) { + assertEquals("Clob content is incorrect", + newContent, this.clob.getSubString(1, newContent.length())); + } else { + // Client currently truncates the Clob when inserting strings. + // See DERBY-1286 and DERBY-2652. + assertEquals("Clob content is incorrect", + newContent.substring(0, 6), + this.clob.getSubString(1, newContent.length())); + } + } + + public void testPositionWithString_ASCII_SimplePartialRecurringPattern() + throws IOException, SQLException { + String token = "xxSPOTxx"; + String inserted ="abcdexxSPxabcdexabxxSPxxxSPOTxabcxxSPOTxxabc"; + this.clob.setString(1L, inserted); + assertEquals("Invalid match position", + inserted.indexOf(token, 0) +1, this.clob.position(token, 1L)); + } + + public void testPositionWithString_USASCII() + throws IOException, SQLException { + String token = "xxSPOTxx"; + final long prefix = 91*1024 +7; + final long postfix = 12*1024; + insertDataWithToken(token, prefix, postfix, SET_ASCII_STREAM); + executeTestPositionWithStringToken(token, prefix); + } + + public void testPositionWithString_IOS88591() + throws IOException, SQLException { + String token = "xx\u00c6\u00c6\u00c6xx"; + final long prefix = 67*1024; + final long postfix = 1*1024-2; + insertDataWithToken(token, prefix, postfix, SET_ASCII_STREAM); + executeTestPositionWithStringToken(token, prefix); + } + + public void testPositionWithString_CJK() + throws IOException, SQLException { + final long prefix = 11L; + final long postfix = 90L; + char[] tmpChar = new char[1]; + LoopingAlphabetReader tokenSrc = + new LoopingAlphabetReader(1L, CharAlphabet.cjkSubset()); + tokenSrc.read(tmpChar); + String token = String.copyValueOf(tmpChar); + insertDataWithToken(token, prefix, postfix, SET_CHARACTER_STREAM); + //insertDataWithToken(token, prefix, 2*1024-7, SET_CHARACTER_STREAM); + executeTestPositionWithStringToken(token, prefix); + } + + /* Test ideas + * + * truncate: + * truncate both on in store and from createClob + * truncate multiple times, check length and compare content + * truncate with negative size + * truncate with too big size + * truncate to 0 + * truncate to current length + */ + + /** + * Insert text into a Clob using {@link java.sql.Clob#setAsciiStream} and + * then search for the specified token. + *

+ * Some data is inserted before and after the token, and the specified token + * is converted to bytes by using the ISO-8859-1 encoding. + * Note that ascii in JDBC is equivalent to ISO-8859-1, not US-ASCII. + */ + private void executeTestPositionWithStringToken(String token, long prefixLength) + throws IOException, SQLException { + + final long TOKEN_POS = prefixLength +1; + // Start searching behind the token. + assertEquals(-1, this.clob.position(token, TOKEN_POS+1)); + // Start searching exactly at the right position. + assertEquals(TOKEN_POS, this.clob.position(token, TOKEN_POS)); + // Start searching at the start of the Clob. + assertEquals(TOKEN_POS, this.clob.position(token, 1L)); + } + + /** + * Obtain a Clob containing the empty string. + */ + protected void setUp() + throws Exception { + // Obtain a Clob containing the empty string (""). + Statement stmt = createStatement(); + // Keep reference to the result set to be able to close it. + this.rs = stmt.executeQuery("select * from ClobTestData"); + assertTrue(this.rs.next()); + this.clob = this.rs.getClob(1); + } + + /** + * Nullify reference to Clob, close the parent result set. + */ + protected void tearDown() + throws Exception { + this.clob = null; + this.rs.close(); + super.tearDown(); + } + + public static Test suite() { + return new ClobTestSetup( + TestConfiguration.defaultSuite(ClobTest.class, false)); + } + + /** + * Transfer data from an input stream to an output stream. + * + * @param source source data + * @param dest destination to write to + * @param tz buffer size in number of bytes. Must be 1 or greater. + * @return Number of bytes read from the source data. This should equal the + * number of bytes written to the destination. + */ + private int transferData(InputStream source, OutputStream dest, int tz) + throws IOException { + if (tz < 1) { + throw new IllegalArgumentException( + "Buffer size must be 1 or greater: " + tz); + } + BufferedInputStream in = new BufferedInputStream(source); + BufferedOutputStream out = new BufferedOutputStream(dest, tz); + byte[] bridge = new byte[tz]; + int total = 0; + int read; + while ((read = in.read(bridge, 0, tz)) != -1) { + out.write(bridge, 0, read); + total += read; + } + in.close(); + // Don't close the stream, in case it will be written to again. + out.flush(); + return total; + } + + /** + * Transfer data from a source Reader to a destination Writer. + * + * @param source source data + * @param dest destination to write to + * @param tz buffer size in number of characters. Must be 1 or greater. + * @return Number of characters read from the source data. This should equal the + * number of characters written to the destination. + */ + private int transferData(Reader source, Writer dest, int tz) + throws IOException { + if (tz < 1) { + throw new IllegalArgumentException( + "Buffer size must be 1 or greater: " + tz); + } + BufferedReader in = new BufferedReader(source); + BufferedWriter out = new BufferedWriter(dest, tz); + char[] bridge = new char[tz]; + int total = 0; + int read; + while ((read = in.read(bridge, 0, tz)) != -1) { + out.write(bridge, 0, read); + total += read; + } + in.close(); + // Don't close the stream, in case it will be written to again. + out.flush(); + return total; + } + + private int transferData(Reader source, int tz) + throws IOException, SQLException { + if (tz < 1) { + throw new IllegalArgumentException( + "Buffer size must be 1 or greater: " + tz); + } + BufferedReader in = new BufferedReader(source); + char[] bridge = new char[tz]; + int total = 0; + int read; + while ((read = in.read(bridge, 0, tz)) != -1) { + this.clob.setString(total +1L, String.copyValueOf(bridge, 0, read)); + total += read; + } + in.close(); + return total; + } + + private void insertDataWithToken(String token, + long pre, long post, int mode) + throws IOException, SQLException { + int TRANSFER_BUFFER_SIZE = 4*1024; // Byte and char array size. + long total = 0; + switch (mode) { + case SET_STRING: { + Reader charIn = new LoopingAlphabetReader(pre); + total += transferData(charIn, TRANSFER_BUFFER_SIZE); + this.clob.setString(pre +1, token); + total += token.length(); + charIn = new LoopingAlphabetReader(post); + total += transferData(charIn, TRANSFER_BUFFER_SIZE); + break; + } case SET_ASCII_STREAM: { + OutputStream asciiOut = this.clob.setAsciiStream(1L); + InputStream asciiIn = new LoopingAlphabetStream(pre); + total += transferData(asciiIn, asciiOut, TRANSFER_BUFFER_SIZE); + byte[] tokenBytes = token.getBytes("ISO-8859-1"); + asciiOut.write(tokenBytes, 0, tokenBytes.length); + total += tokenBytes.length; + asciiIn = new LoopingAlphabetStream(post); + total += transferData(asciiIn, asciiOut, TRANSFER_BUFFER_SIZE); + break; + } case SET_CHARACTER_STREAM: { + Writer charOut = this.clob.setCharacterStream(1L); + Reader charIn = new LoopingAlphabetReader(pre); + total += transferData(charIn, charOut, TRANSFER_BUFFER_SIZE); + charOut.write(token); + total += token.length(); + charIn = new LoopingAlphabetReader(post); + total += transferData(charIn, charOut, TRANSFER_BUFFER_SIZE); + break; + } default: + throw new IllegalArgumentException( + "Unknown insertion mode: " + mode); + } + assertEquals("Invalid length after insertion", + pre + post + token.length(), this.clob.length()); + } + + /** + * Decorator creating the neccessary test data. + */ + private static class ClobTestSetup extends BaseJDBCTestSetup { + + ClobTestSetup(Test test) { + super(test); + } + + protected void setUp() throws SQLException { + Connection con = getConnection(); + Statement stmt = con.createStatement(); + stmt.execute("create table ClobTestData (" + + "dClob CLOB)"); + stmt.executeUpdate("insert into ClobTestData values ('')"); + stmt.close(); + } + + protected void tearDown() + throws Exception { + Connection con = getConnection(); + Statement stmt = con.createStatement(); + stmt.execute("drop table ClobTestData"); + stmt.close(); + super.tearDown(); + } + } // End inner class ClobTestSetup +} Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/ClobTest.java ------------------------------------------------------------------------------ svn:eol-style = native