db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kahat...@apache.org
Subject svn commit: r1164495 - in /db/derby/code/trunk/java: drda/org/apache/derby/impl/drda/ testing/org/apache/derbyTesting/functionTests/tests/derbynet/ testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/
Date Fri, 02 Sep 2011 12:00:36 GMT
Author: kahatlen
Date: Fri Sep  2 12:00:36 2011
New Revision: 1164495

URL: http://svn.apache.org/viewvc?rev=1164495&view=rev
Log:
DERBY-5236: Client driver silently truncates strings that exceed 32KB

Truncate the strings at 65535 bytes instead of 32700 bytes, and make sure
that the truncation happens on a character boundary.

Modified:
    db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DDMWriter.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/PrepareStatementTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/Derby5236Test.java

Modified: db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DDMWriter.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DDMWriter.java?rev=1164495&r1=1164494&r2=1164495&view=diff
==============================================================================
--- db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DDMWriter.java (original)
+++ db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DDMWriter.java Fri Sep  2 12:00:36
2011
@@ -58,6 +58,12 @@ class DDMWriter
 	// Default buffer size
 	private final static int DEFAULT_BUFFER_SIZE = 32767;
 
+    /**
+     * The maximum length in bytes for strings sent by {@code writeLDString()},
+     * which is the maximum unsigned integer value that fits in two bytes.
+     */
+    private final static int MAX_VARCHAR_BYTE_LENGTH = 0xFFFF;
+
 	/**
 	 * Output buffer.
 	 */
@@ -1193,41 +1199,51 @@ class DDMWriter
 		// actual writing of the length is delayed until we have encoded the
 		// string.
 		final int lengthPos = buffer.position();
-		// Position on which to start writing the string (right after length,
-		// which is 2 bytes long).
-		final int stringPos = lengthPos + 2;
-		// don't send more than LONGVARCHAR_MAX_LEN bytes
-		final int maxStrLen =
-			Math.min(maxEncodedLength(s), FdocaConstants.LONGVARCHAR_MAX_LEN);
-
-		ensureLength(2 + maxStrLen);
-
-		// limit the writable area of the output buffer
-		buffer.position(stringPos);
-		buffer.limit(stringPos + maxStrLen);
 
-		// encode the string
-		CharBuffer input = CharBuffer.wrap(s);
-		encoder.reset();
-		CoderResult res = encoder.encode(input, buffer, true);
-		if (res == CoderResult.UNDERFLOW) {
-			res = encoder.flush(buffer);
-		}
-		if (SanityManager.DEBUG) {
-			// UNDERFLOW is returned if the entire string was encoded, OVERFLOW
-			// is returned if the string was truncated at LONGVARCHAR_MAX_LEN
-			SanityManager.ASSERT(
-				res == CoderResult.UNDERFLOW || res == CoderResult.OVERFLOW,
-				"Unexpected coder result: " + res);
-		}
-
-		// write the length in bytes
-		buffer.putShort(lengthPos, (short) (maxStrLen - buffer.remaining()));
+        // Reserve two bytes for the length field and move the position to
+        // where the string should be inserted.
+        ensureLength(2);
+        final int stringPos = lengthPos + 2;
+        buffer.position(stringPos);
+
+        // Write the string.
+        writeString(s);
+
+        int byteLength = buffer.position() - stringPos;
+
+        // If the byte representation of the string is too long, it needs to
+        // be truncated.
+        if (byteLength > MAX_VARCHAR_BYTE_LENGTH) {
+            // Truncate the string down to the maximum byte length.
+            byteLength = MAX_VARCHAR_BYTE_LENGTH;
+            // Align with character boundaries so that we don't send over
+            // half a character.
+            while (isContinuationByte(buffer.get(stringPos + byteLength))) {
+                byteLength--;
+            }
+            // Set the buffer position right after the truncated string.
+            buffer.position(stringPos + byteLength);
+        }
 
-		// remove the limit on the output buffer
-		buffer.limit(buffer.capacity());
+        // Go back and write the length in bytes.
+        buffer.putShort(lengthPos, (short) byteLength);
 	}
 
+    /**
+     * Check if a byte value represents a continuation byte in a UTF-8 byte
+     * sequence. Continuation bytes in UTF-8 always match the bit pattern
+     * {@code 10xxxxxx}.
+     *
+     * @param b the byte to check
+     * @return {@code true} if {@code b} is a continuation byte, or
+     * {@code false} if it is the first byte in a UTF-8 sequence
+     */
+    private static boolean isContinuationByte(byte b) {
+        // Check the values of the two most significant bits. If they are
+        // 10xxxxxx, it's a continuation byte.
+        return (b & 0xC0) == 0x80;
+    }
+
 	/**
 	 * Write string with default encoding
 	 *

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/PrepareStatementTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/PrepareStatementTest.java?rev=1164495&r1=1164494&r2=1164495&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/PrepareStatementTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/PrepareStatementTest.java
Fri Sep  2 12:00:36 2011
@@ -1288,9 +1288,16 @@ public class PrepareStatementTest extend
      */
     private static String makeString(int length)
     {
-        StringBuffer buf = new StringBuffer();
-        for (int i = 0; i < length; ++i) buf.append("X");
-        return buf.toString();
+        return makeString(length, 'X');
+    }
+
+    /**
+     * Return a string of the given length filled with the specified character.
+     */
+    private static String makeString(int length, char ch) {
+        char[] buf = new char[length];
+        Arrays.fill(buf, ch);
+        return new String(buf);
     }
 
     /**
@@ -1319,4 +1326,62 @@ public class PrepareStatementTest extend
         rs.close();
     }
 
+    /**
+     * Verify that string values aren't truncated when their UTF-8 encoded
+     * representation exceeds 32KB. DERBY-5236.
+     */
+    public void testLongColumn() throws Exception {
+        PreparedStatement ps = prepareStatement(
+                "values cast(? as varchar(32672))");
+
+        String s1 = makeString(20000, '\u4e10');
+        ps.setString(1, s1);
+        JDBC.assertSingleValueResultSet(ps.executeQuery(), s1);
+
+        // 64K-1 bytes, should be OK.
+        String s2 =
+                s1 + makeString(64 * 1024 - s1.getBytes("UTF-8").length - 1);
+        ps.setString(1, s2);
+        JDBC.assertSingleValueResultSet(ps.executeQuery(), s2);
+
+        // 64K bytes, will be truncated to 64K-1 by the client driver because
+        // of limitation in the protocol.
+        String s3 = s2 + 'X';
+        ps.setString(1, s3);
+        if (usingDerbyNetClient()) {
+            String expected = s3.substring(0, s3.length() - 1);
+            JDBC.assertSingleValueResultSet(ps.executeQuery(), expected);
+        } else {
+            // Embedded is OK. No truncation.
+            JDBC.assertSingleValueResultSet(ps.executeQuery(), s3);
+        }
+
+        // 64K+1 bytes, will be truncated by the client driver because of
+        // limitation in the protocol. Should be truncated to to 64K-2 to
+        // match the character boundary.
+        String s4 = s3.substring(0, s3.length() - 2) + '\u4e10';
+        ps.setString(1, s4);
+        if (usingDerbyNetClient()) {
+            String expected = s4.substring(0, s4.length() - 1);
+            JDBC.assertSingleValueResultSet(ps.executeQuery(), expected);
+        } else {
+            // Embedded is OK. No truncation.
+            JDBC.assertSingleValueResultSet(ps.executeQuery(), s4);
+        }
+
+        // Try two columns at 64K+1 bytes. Expect same result as above.
+        PreparedStatement ps2 = prepareStatement(
+                "values (cast(? as varchar(32672)), " +
+                "cast(? as varchar(32672)))");
+        ps2.setString(1, s4);
+        ps2.setString(2, s4);
+        if (usingDerbyNetClient()) {
+            String expected = s4.substring(0, s4.length() - 1);
+            String[][] expectedRow = {{expected, expected}};
+            JDBC.assertFullResultSet(ps2.executeQuery(), expectedRow);
+        } else {
+            String[][] expectedRow = {{s4, s4}};
+            JDBC.assertFullResultSet(ps2.executeQuery(), expectedRow);
+        }
+    }
 }



Mime
View raw message