From 9287630fbc2e29cd858a5a05eba0a9dd8e756ef3 Mon Sep 17 00:00:00 2001 From: Kris Jurka Date: Tue, 3 Feb 2004 05:13:56 +0000 Subject: [PATCH] Fix the setXXXStream methods. If passed a null InputStream, convert this to a setNull call. The code originally would try to read the whole stream in one call to read(), but this doesn't work. The InputStream API makes it clear you must be prepared to loop and continue reading if you didn't get the whole request on the first try. Per report from Martin Holz. --- .../jdbc1/AbstractJdbc1Statement.java | 116 ++++++++++-------- .../postgresql/test/jdbc2/Jdbc2TestSuite.java | 1 + .../test/jdbc2/PreparedStatementTest.java | 91 ++++++++++++++ 3 files changed, 158 insertions(+), 50 deletions(-) create mode 100644 src/interfaces/jdbc/org/postgresql/test/jdbc2/PreparedStatementTest.java diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java b/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java index 9645893987d..50c214a30d9 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java @@ -26,7 +26,7 @@ import java.sql.Timestamp; import java.sql.Types; import java.util.Vector; -/* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Statement.java,v 1.41.2.2 2003/12/12 18:39:00 davec Exp $ +/* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Statement.java,v 1.41.2.3 2004/02/03 05:13:55 jurka Exp $ * This class defines methods of the jdbc1 specification. This class is * extended by org.postgresql.jdbc2.AbstractJdbc2Statement which adds the jdbc2 * methods. The real Statement class (for jdbc1) is org.postgresql.jdbc1.Jdbc1Statement @@ -1342,6 +1342,50 @@ public abstract class AbstractJdbc1Statement implements BaseStatement } } + private void setCharacterStreamPost71(int parameterIndex, InputStream x, int length, String encoding) throws SQLException + { + + if (x == null) + { + setNull(parameterIndex, Types.VARCHAR); + return; + } + + //Version 7.2 supports AsciiStream for all PG text types (char, varchar, text) + //As the spec/javadoc for this method indicate this is to be used for + //large String values (i.e. LONGVARCHAR) PG doesn't have a separate + //long varchar datatype, but with toast all text datatypes are capable of + //handling very large values. Thus the implementation ends up calling + //setString() since there is no current way to stream the value to the server + try + { + InputStreamReader l_inStream = new InputStreamReader(x, encoding); + char[] l_chars = new char[length]; + int l_charsRead = 0; + while (true) + { + int n = l_inStream.read(l_chars, l_charsRead, length - l_charsRead); + if (n == -1) + break; + + l_charsRead += n; + + if (l_charsRead == length) + break; + } + + setString(parameterIndex, new String(l_chars, 0, l_charsRead), PG_TEXT); + } + catch (UnsupportedEncodingException l_uee) + { + throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_uee); + } + catch (IOException l_ioe) + { + throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_ioe); + } + } + /* * When a very large ASCII value is input to a LONGVARCHAR parameter, * it may be more practical to send it via a java.io.InputStream. @@ -1362,27 +1406,7 @@ public abstract class AbstractJdbc1Statement implements BaseStatement { if (connection.haveMinimumCompatibleVersion("7.2")) { - //Version 7.2 supports AsciiStream for all PG text types (char, varchar, text) - //As the spec/javadoc for this method indicate this is to be used for - //large String values (i.e. LONGVARCHAR) PG doesn't have a separate - //long varchar datatype, but with toast all text datatypes are capable of - //handling very large values. Thus the implementation ends up calling - //setString() since there is no current way to stream the value to the server - try - { - InputStreamReader l_inStream = new InputStreamReader(x, "ASCII"); - char[] l_chars = new char[length]; - int l_charsRead = l_inStream.read(l_chars, 0, length); - setString(parameterIndex, new String(l_chars, 0, l_charsRead), PG_TEXT); - } - catch (UnsupportedEncodingException l_uee) - { - throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_uee); - } - catch (IOException l_ioe) - { - throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_ioe); - } + setCharacterStreamPost71(parameterIndex, x, length, "ASCII"); } else { @@ -1411,27 +1435,7 @@ public abstract class AbstractJdbc1Statement implements BaseStatement { if (connection.haveMinimumCompatibleVersion("7.2")) { - //Version 7.2 supports AsciiStream for all PG text types (char, varchar, text) - //As the spec/javadoc for this method indicate this is to be used for - //large String values (i.e. LONGVARCHAR) PG doesn't have a separate - //long varchar datatype, but with toast all text datatypes are capable of - //handling very large values. Thus the implementation ends up calling - //setString() since there is no current way to stream the value to the server - try - { - InputStreamReader l_inStream = new InputStreamReader(x, "UTF-8"); - char[] l_chars = new char[length]; - int l_charsRead = l_inStream.read(l_chars, 0, length); - setString(parameterIndex, new String(l_chars, 0, l_charsRead), PG_TEXT); - } - catch (UnsupportedEncodingException l_uee) - { - throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_uee); - } - catch (IOException l_ioe) - { - throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_ioe); - } + setCharacterStreamPost71(parameterIndex, x, length, "UTF-8"); } else { @@ -1459,6 +1463,12 @@ public abstract class AbstractJdbc1Statement implements BaseStatement { if (connection.haveMinimumCompatibleVersion("7.2")) { + if (x == null) + { + setNull(parameterIndex, Types.VARBINARY); + return; + } + //Version 7.2 supports BinaryStream for for the PG bytea type //As the spec/javadoc for this method indicate this is to be used for //large binary values (i.e. LONGVARBINARY) PG doesn't have a separate @@ -1466,10 +1476,21 @@ public abstract class AbstractJdbc1Statement implements BaseStatement //handling very large values. Thus the implementation ends up calling //setBytes() since there is no current way to stream the value to the server byte[] l_bytes = new byte[length]; - int l_bytesRead; + int l_bytesRead = 0; try { - l_bytesRead = x.read(l_bytes, 0, length); + while (true) + { + int n = x.read(l_bytes, l_bytesRead, length - l_bytesRead); + if (n == -1) + break; + + l_bytesRead += n; + + if (l_bytesRead == length) + break; + + } } catch (IOException l_ioe) { @@ -1479,11 +1500,6 @@ public abstract class AbstractJdbc1Statement implements BaseStatement { setBytes(parameterIndex, l_bytes); } - // x.read will return -1 not 0 on an empty InputStream - else if (l_bytesRead == -1) - { - setBytes(parameterIndex, new byte[0]); - } else { //the stream contained less data than they said diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/Jdbc2TestSuite.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/Jdbc2TestSuite.java index 7a5acab8e26..e1b480a1f1c 100644 --- a/src/interfaces/jdbc/org/postgresql/test/jdbc2/Jdbc2TestSuite.java +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/Jdbc2TestSuite.java @@ -42,6 +42,7 @@ public class Jdbc2TestSuite extends TestSuite suite.addTestSuite(TimestampTest.class); // PreparedStatement + suite.addTestSuite(PreparedStatementTest.class); // ServerSide Prepared Statements suite.addTestSuite(ServerPreparedStmtTest.class); diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/PreparedStatementTest.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/PreparedStatementTest.java new file mode 100644 index 00000000000..e6ddf59475d --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/PreparedStatementTest.java @@ -0,0 +1,91 @@ +package org.postgresql.test.jdbc2; + +import org.postgresql.test.TestUtil; +import junit.framework.TestCase; +import java.io.*; +import java.sql.*; + + +public class PreparedStatementTest extends TestCase +{ + + private Connection conn; + + public PreparedStatementTest(String name) + { + super(name); + } + + protected void setUp() throws SQLException + { + conn = TestUtil.openDB(); + TestUtil.createTable(conn, "streamtable", "bin bytea, str text"); + } + + protected void tearDown() throws SQLException + { + TestUtil.dropTable(conn, "streamtable"); + TestUtil.closeDB(conn); + } + + public void testSetBinaryStream() throws SQLException + { + ByteArrayInputStream bais; + byte buf[] = new byte[10]; + for (int i=0; i