diff --git a/src/interfaces/jdbc/CHANGELOG b/src/interfaces/jdbc/CHANGELOG index b02e4d3756a95a907ea771339d63bf51d9e225f4..53855073033f9973d63186687fe88f4be02dfc5f 100644 --- a/src/interfaces/jdbc/CHANGELOG +++ b/src/interfaces/jdbc/CHANGELOG @@ -1,3 +1,8 @@ +Web Feb 14 17:29:00 GMT 2001 peter@retep.org.uk + - Fixed bug in LargeObject & BlobOutputStream where the stream's output + was not flushed when either the stream or the blob were closed. + - Fixed PreparedStatement.setBinaryStream() where it ignored the length + Tue Feb 13 16:33:00 GMT 2001 peter@retep.org.uk - More TestCases implemented. Refined the test suite api's. - Removed need for SimpleDateFormat in ResultSet.getDate() improving diff --git a/src/interfaces/jdbc/build.xml b/src/interfaces/jdbc/build.xml index e95c646f5d27c701fe8a9c863fb1368bd3fe6a46..5f09ed887bce26563adaebdc8251f19470308861 100644 --- a/src/interfaces/jdbc/build.xml +++ b/src/interfaces/jdbc/build.xml @@ -3,7 +3,7 @@ build file to allow ant (http://jakarta.apache.org/ant/) to be used to build the PostgreSQL JDBC Driver. - $Id: build.xml,v 1.5 2001/02/07 09:13:20 peter Exp $ + $Id: build.xml,v 1.6 2001/02/14 17:45:13 peter Exp $ --> @@ -26,6 +26,7 @@ <property name="database" value="jdbc:postgresql:test" /> <property name="username" value="test" /> <property name="password" value="password" /> + <property name="tablename" value="jdbctest" /> <property name="junit.ui" value="textui" /> <!-- diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java b/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java index a0f1d0713c4e18cf718353aec49eec3190e22d52..93a175267dfcb136bd35cf04171677c26f209b10 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java @@ -431,9 +431,11 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta // is buffered internally anyhow, so there would be no performance // boost gained, if anything it would be worse! int c=x.read(); - while(c>-1) { + int p=0; + while(c>-1 && p<length) { los.write(c); c=x.read(); + p++; } los.close(); } catch(IOException se) { diff --git a/src/interfaces/jdbc/org/postgresql/largeobject/BlobOutputStream.java b/src/interfaces/jdbc/org/postgresql/largeobject/BlobOutputStream.java index 0ac435a78fd80b457f602941564d7c5e9cb17304..3cba1c3702a52a98ae075a9f70c4a1c7f7288c1c 100644 --- a/src/interfaces/jdbc/org/postgresql/largeobject/BlobOutputStream.java +++ b/src/interfaces/jdbc/org/postgresql/largeobject/BlobOutputStream.java @@ -92,6 +92,7 @@ public class BlobOutputStream extends OutputStream { */ public void close() throws IOException { try { + flush(); lo.close(); lo=null; } catch(SQLException se) { diff --git a/src/interfaces/jdbc/org/postgresql/largeobject/LargeObject.java b/src/interfaces/jdbc/org/postgresql/largeobject/LargeObject.java index c73301d131814e978f1ba8a48a5e6d44c38fe9fd..20afda578288d6c2a0f0c0624122871ee8aa43e5 100644 --- a/src/interfaces/jdbc/org/postgresql/largeobject/LargeObject.java +++ b/src/interfaces/jdbc/org/postgresql/largeobject/LargeObject.java @@ -62,6 +62,10 @@ public class LargeObject private int oid; // OID of this object private int fd; // the descriptor of the open large object + private BlobOutputStream os; // The current output stream + + private boolean closed=false; // true when we are closed + /** * This opens a large object. * @@ -100,9 +104,25 @@ public class LargeObject */ public void close() throws SQLException { - FastpathArg args[] = new FastpathArg[1]; - args[0] = new FastpathArg(fd); - fp.fastpath("lo_close",false,args); // true here as we dont care!! + if(!closed) { + // flush any open output streams + if(os!=null) { + try { + // we can't call os.close() otherwise we go into an infinite loop! + os.flush(); + } catch(IOException ioe) { + throw new SQLException(ioe.getMessage()); + } finally { + os=null; + } + } + + // finally close + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(fd); + fp.fastpath("lo_close",false,args); // true here as we dont care!! + closed=true; + } } /** @@ -279,7 +299,9 @@ public class LargeObject */ public OutputStream getOutputStream() throws SQLException { - return new BlobOutputStream(this); + if(os==null) + os = new BlobOutputStream(this); + return os; } } diff --git a/src/interfaces/jdbc/org/postgresql/test/JDBC2Tests.java b/src/interfaces/jdbc/org/postgresql/test/JDBC2Tests.java index 6aac14b4b1cd71cb02a65c63f6b47c37e6ef6c0c..37624f528da26c3d787ed6c4c171cbd43ce76cf6 100644 --- a/src/interfaces/jdbc/org/postgresql/test/JDBC2Tests.java +++ b/src/interfaces/jdbc/org/postgresql/test/JDBC2Tests.java @@ -207,13 +207,14 @@ public class JDBC2Tests extends TestSuite { // MetaData - // Fastpath/LargeObject - // Other misc tests, based on previous problems users have had or specific // features some applications require. suite.addTestSuite(JBuilderTest.class); suite.addTestSuite(MiscTest.class); + // Fastpath/LargeObject + suite.addTestSuite(BlobTest.class); + // That's all folks return suite; } diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/BlobTest.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/BlobTest.java new file mode 100644 index 0000000000000000000000000000000000000000..28a61044cf6a865e9ac451dc365f2062787b76d8 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/BlobTest.java @@ -0,0 +1,184 @@ +package org.postgresql.test.jdbc2; + +import org.postgresql.test.JDBC2Tests; +import junit.framework.TestCase; +import java.io.*; +import java.sql.*; + +import org.postgresql.largeobject.*; + +/** + * $Id: BlobTest.java,v 1.1 2001/02/14 17:45:17 peter Exp $ + * + * Some simple tests based on problems reported by users. Hopefully these will + * help prevent previous problems from re-occuring ;-) + * + */ +public class BlobTest extends TestCase { + + public BlobTest(String name) { + super(name); + } + + /** + * The table format used by this TestCase + */ + private static final String BLOB_TABLE_FMT = "id name,lo oid"; + + /** + * Tests one method of uploading a blob to the database + */ + public void testUploadBlob_LOOP() { + try { + Connection con = JDBC2Tests.openDB(); + + JDBC2Tests.createTable(con,BLOB_TABLE_FMT); + + con.setAutoCommit(false); + assert(!con.getAutoCommit()); + + assert(uploadFile(con,"build.xml",LOOP)>0); + + // Now compare the blob & the file. Note this actually tests the + // InputStream implementation! + assert(compareBlobs(con)); + + JDBC2Tests.closeDB(con); + } catch(Exception ex) { + assert(ex.getMessage(),false); + } + } + + /** + * Tests one method of uploading a blob to the database + */ + public void testUploadBlob_NATIVE() { + try { + Connection con = JDBC2Tests.openDB(); + + JDBC2Tests.createTable(con,BLOB_TABLE_FMT); + + con.setAutoCommit(false); + assert(!con.getAutoCommit()); + + assert(uploadFile(con,"build.xml",NATIVE_STREAM)>0); + + // Now compare the blob & the file. Note this actually tests the + // InputStream implementation! + assert(compareBlobs(con)); + + JDBC2Tests.closeDB(con); + } catch(Exception ex) { + assert(ex.getMessage(),false); + } + } + + private static final int LOOP = 0; // LargeObject API using loop + private static final int NATIVE_STREAM = 1; // LargeObject API using OutputStream + private static final int JDBC_STREAM = 2; // JDBC API using OutputStream + + /** + * Helper - uploads a file into a blob using old style methods. We use this + * because it always works, and we can use it as a base to test the new + * methods. + */ + private int uploadFile(Connection con,String file,int method) throws Exception { + LargeObjectManager lom = ((org.postgresql.Connection)con).getLargeObjectAPI(); + + FileInputStream fis = new FileInputStream(file); + + int oid = lom.create(LargeObjectManager.READWRITE); + LargeObject blob = lom.open(oid); + + int s,t; + byte buf[]; + OutputStream os; + + switch(method) + { + case LOOP: + buf = new byte[2048]; + t=0; + while((s=fis.read(buf,0,buf.length))>0) { + t+=s; + blob.write(buf,0,s); + } + break; + + case NATIVE_STREAM: + os = blob.getOutputStream(); + s= fis.read(); + while(s>-1) { + os.write(s); + s=fis.read(); + } + os.close(); + break; + + case JDBC_STREAM: + File f = new File(file); + PreparedStatement ps = con.prepareStatement(JDBC2Tests.insert("?")); + ps.setBinaryStream(1,fis,(int) f.length()); + ps.execute(); + break; + + default: + assert("Unknown method in uploadFile",false); + } + + blob.close(); + fis.close(); + + // Insert into the table + Statement st = con.createStatement(); + st.executeUpdate(JDBC2Tests.insert("id,lo","'"+file+"',"+oid)); + con.commit(); + st.close(); + + return oid; + } + + /** + * Helper - compares the blobs in a table with a local file. Note this alone + * tests the InputStream methods! + */ + private boolean compareBlobs(Connection con) throws Exception { + boolean result=true; + + LargeObjectManager lom = ((org.postgresql.Connection)con).getLargeObjectAPI(); + + Statement st = con.createStatement(); + ResultSet rs = st.executeQuery(JDBC2Tests.select("id,lo")); + assert(rs!=null); + + while(rs.next()) { + String file = rs.getString(1); + int oid = rs.getInt(2); + + FileInputStream fis = new FileInputStream(file); + LargeObject blob = lom.open(oid); + InputStream bis = blob.getInputStream(); + + int f=fis.read(); + int b=bis.read(); + int c=0; + while(f>=0 && b>=0 & result) { + result=(f==b); + f=fis.read(); + b=bis.read(); + c++; + } + result=result && f==-1 && b==-1; + + if(!result) + System.out.println("\nBlob compare failed at "+c+" of "+blob.size()); + + blob.close(); + fis.close(); + } + rs.close(); + st.close(); + + return result; + } +}