]> granicus.if.org Git - postgresql/commitdiff
Tue Jan 30 22:24:00 GMT 2001 peter@retep.org.uk
authorPeter Mount <peter@retep.org.uk>
Wed, 31 Jan 2001 08:26:02 +0000 (08:26 +0000)
committerPeter Mount <peter@retep.org.uk>
Wed, 31 Jan 2001 08:26:02 +0000 (08:26 +0000)
        - Fixed bug where Statement.setMaxRows() was a global setting. Now
          limited to just itself.
        - Changed LargeObject.read(byte[],int,int) to return the actual number
          of bytes read (used to be void).
        - LargeObject now supports InputStream's!
        - PreparedStatement.setBinaryStream() now works!
        - ResultSet.getBinaryStream() now returns an InputStream that doesn't
          copy the blob into memory first!
        - Connection.isClosed() now tests to see if the connection is still alive
          rather than if it thinks it's alive.

12 files changed:
src/interfaces/jdbc/CHANGELOG
src/interfaces/jdbc/example/basic.java
src/interfaces/jdbc/jdbc.jpx
src/interfaces/jdbc/org/postgresql/Connection.java
src/interfaces/jdbc/org/postgresql/jdbc1/Statement.java
src/interfaces/jdbc/org/postgresql/jdbc2/Connection.java
src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java
src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java
src/interfaces/jdbc/org/postgresql/jdbc2/Statement.java
src/interfaces/jdbc/org/postgresql/largeobject/BlobInputStream.java [new file with mode: 0644]
src/interfaces/jdbc/org/postgresql/largeobject/BlobOutputStream.java [new file with mode: 0644]
src/interfaces/jdbc/org/postgresql/largeobject/LargeObject.java

index 56d450d9ebbd5f55b99d350ca80411f4515dc836..ffc0ce9efb3501b850a6ea43a81b82a83347216c 100644 (file)
@@ -1,3 +1,14 @@
+Tue Jan 30 22:24:00 GMT 2001 peter@retep.org.uk
+        - Fixed bug where Statement.setMaxRows() was a global setting. Now
+          limited to just itself.
+        - Changed LargeObject.read(byte[],int,int) to return the actual number
+          of bytes read (used to be void).
+        - LargeObject now supports InputStream's!
+        - PreparedStatement.setBinaryStream() now works!
+        - ResultSet.getBinaryStream() now returns an InputStream that doesn't
+          copy the blob into memory first!
+        - Connection.isClosed() now tests to see if the connection is still alive
+          rather than if it thinks it's alive.
 Thu Jan 25 09:11:00 GMT 2001 peter@retep.org.uk
         - Added an alternative constructor to PGSQLException so that debugging
           some more osteric bugs is easier. If only 1 arg is supplied and it's
index 41302200ec16e43a736e34feb6bf2a152895cae5..5e382538747d32b322b85fc83a527d87d7bd48fa 100644 (file)
@@ -6,7 +6,7 @@ import java.text.*;
 
 /**
  *
- * $Id: basic.java,v 1.5 2000/06/06 11:05:57 peter Exp $
+ * $Id: basic.java,v 1.6 2001/01/31 08:26:01 peter Exp $
  *
  * This example tests the basic components of the JDBC driver, and shows
  * how even the simplest of queries can be implemented.
@@ -22,40 +22,40 @@ public class basic
 {
   Connection db;       // The connection to the database
   Statement  st;       // Our statement to run queries with
-  
+
   public basic(String args[]) throws ClassNotFoundException, FileNotFoundException, IOException, SQLException
   {
     String url = args[0];
     String usr = args[1];
     String pwd = args[2];
-    
+
     // Load the driver
     Class.forName("org.postgresql.Driver");
-    
+
     // Connect to database
     System.out.println("Connecting to Database URL = " + url);
     db = DriverManager.getConnection(url, usr, pwd);
-    
+
     System.out.println("Connected...Now creating a statement");
     st = db.createStatement();
-    
+
     // Clean up the database (in case we failed earlier) then initialise
     cleanup();
-    
+
     // Now run tests using JDBC methods
     doexample();
-    
+
     // Clean up the database
     cleanup();
-    
+
     // Finally close the database
     System.out.println("Now closing the connection");
     st.close();
     db.close();
-    
+
     //throw postgresql.Driver.notImplemented();
   }
-  
+
   /**
    * This drops the table (if it existed). No errors are reported.
    */
@@ -67,35 +67,36 @@ public class basic
       // We ignore any errors here
     }
   }
-  
+
   /**
    * This performs the example
    */
   public void doexample() throws SQLException
   {
     System.out.println("\nRunning tests:");
-    
+
     // First we need a table to store data in
     st.executeUpdate("create table basic (a int2, b int2)");
-    
+
     // Now insert some data, using the Statement
     st.executeUpdate("insert into basic values (1,1)");
     st.executeUpdate("insert into basic values (2,1)");
     st.executeUpdate("insert into basic values (3,1)");
-    
+
     // This shows how to get the oid of a just inserted row
+    // updated for 7.1
     st.executeUpdate("insert into basic values (4,1)");
-    int insertedOID = ((org.postgresql.ResultSet)st.getResultSet()).getInsertedOID();
+    int insertedOID = ((org.postgresql.jdbc2.Statement)st).getInsertedOID();
     System.out.println("Inserted row with oid "+insertedOID);
-    
+
     // Now change the value of b from 1 to 8
     st.executeUpdate("update basic set b=8");
     System.out.println("Updated "+st.getUpdateCount()+" rows");
-    
+
     // Now delete 2 rows
     st.executeUpdate("delete from basic where a<3");
     System.out.println("deleted "+st.getUpdateCount()+" rows");
-    
+
     // For large inserts, a PreparedStatement is more efficient, because it
     // supports the idea of precompiling the SQL statement, and to store
     // directly, a Java object into any column. PostgreSQL doesnt support
@@ -112,7 +113,7 @@ public class basic
       ps.executeUpdate();      // executeUpdate because insert returns no data
     }
     ps.close();                        // Always close when we are done with it
-    
+
     // Finally perform a query on the table
     System.out.println("performing a query");
     ResultSet rs = st.executeQuery("select a, b from basic");
@@ -126,7 +127,7 @@ public class basic
       }
       rs.close();      // again, you must close the result when done
     }
-    
+
     // Now run the query again, showing a more efficient way of getting the
     // result if you don't know what column number a value is in
     System.out.println("performing another query");
@@ -140,7 +141,7 @@ public class basic
       //
       int col_a = rs.findColumn("a");
       int col_b = rs.findColumn("b");
-      
+
       // Now we run through the result set, printing out the result.
       // Again, we must call .next() before attempting to read any results
       while(rs.next()) {
@@ -150,11 +151,22 @@ public class basic
       }
       rs.close();      // again, you must close the result when done
     }
-    
+
+    // Now test maxrows by setting it to 3 rows
+    st.setMaxRows(3);
+    System.out.println("performing a query limited to "+st.getMaxRows());
+    rs = st.executeQuery("select a, b from basic");
+    while(rs.next()) {
+      int a = rs.getInt("a");  // This shows how to get the value by name
+      int b = rs.getInt(2);    // This shows how to get the value by column
+      System.out.println("  a="+a+" b="+b);
+    }
+    rs.close();        // again, you must close the result when done
+
     // The last thing to do is to drop the table. This is done in the
     // cleanup() method.
   }
-  
+
   /**
    * Display some instructions on how to run the example
    */
@@ -164,22 +176,22 @@ public class basic
     System.out.println("Useage:\n java example.basic jdbc:postgresql:database user password [debug]\n\nThe debug field can be anything. It's presence will enable DriverManager's\ndebug trace. Unless you want to see screens of items, don't put anything in\nhere.");
     System.exit(1);
   }
-  
+
   /**
    * This little lot starts the test
    */
   public static void main(String args[])
   {
     System.out.println("PostgreSQL basic test v6.3 rev 1\n");
-    
+
     if(args.length<3)
       instructions();
-    
+
     // This line outputs debug information to stderr. To enable this, simply
     // add an extra parameter to the command line
     if(args.length>3)
       DriverManager.setLogStream(System.err);
-    
+
     // Now run the tests
     try {
       basic test = new basic(args);
index abe4c8f86a42580881c0212c92295a1109c2ca93..2f8a779a6e688cdf21fcc8c7de4f73009ea9799d 100644 (file)
@@ -9,7 +9,7 @@
   <property category="sys" name="CheckStable" value="1" />\r
   <property category="sys" name="Company" value="" />\r
   <property category="sys" name="Copyright" value="Copyright (c) 2001" />\r
-  <property category="sys" name="DefaultPackage" value="org.postgresql.core" />\r
+  <property category="sys" name="DefaultPackage" value="org.postgresql.largeobject" />\r
   <property category="sys" name="Description" value="" />\r
   <property category="sys" name="DocPath" value="doc" />\r
   <property category="sys" name="ExcludeClassEnabled" value="0" />\r
index d852538846b07cefa5d9b1a7b47603b8a334ad64..87fbc337a73a9e316f3e41a2319d7011a0606250 100644 (file)
@@ -10,7 +10,7 @@ import org.postgresql.largeobject.*;
 import org.postgresql.util.*;
 
 /**
- * $Id: Connection.java,v 1.13 2001/01/18 17:37:12 peter Exp $
+ * $Id: Connection.java,v 1.14 2001/01/31 08:26:01 peter Exp $
  *
  * This abstract class is used by org.postgresql.Driver to open either the JDBC1 or
  * JDBC2 versions of the Connection class.
@@ -22,7 +22,7 @@ public abstract class Connection
   public PG_Stream pg_stream;
 
   // This is set by org.postgresql.Statement.setMaxRows()
-  public int maxrows = 0;              // maximum no. of rows; 0 = unlimited
+  //public int maxrows = 0;            // maximum no. of rows; 0 = unlimited
 
   private String PG_HOST;
   private int PG_PORT;
@@ -414,6 +414,11 @@ public abstract class Connection
      */
     public java.sql.ResultSet ExecSQL(String sql,java.sql.Statement stat) throws SQLException
     {
+      // added Jan 30 2001 to correct maxrows per statement
+      int maxrows=0;
+      if(stat!=null)
+        maxrows=stat.getMaxRows();
+
        // added Oct 7 1998 to give us thread safety.
        synchronized(pg_stream) {
            // Deallocate all resources in the stream associated
index f20bc997fd5784974e9a6c1799e77f06a354942d..a4835a72ac57aa75759ddaa3c266faa7d1aab877 100644 (file)
@@ -29,6 +29,7 @@ public class Statement implements java.sql.Statement
     SQLWarning warnings = null;        // The warnings chain.
     int timeout = 0;           // The timeout for a query (not used)
     boolean escapeProcessing = true;// escape processing flag
+    int maxrows=0;
 
        /**
         * Constructor for a Statement.  It simply sets the connection
@@ -129,7 +130,7 @@ public class Statement implements java.sql.Statement
         */
        public int getMaxRows() throws SQLException
        {
-               return connection.maxrows;
+               return maxrows;
        }
 
        /**
@@ -141,7 +142,7 @@ public class Statement implements java.sql.Statement
         */
        public void setMaxRows(int max) throws SQLException
        {
-         connection.maxrows = max;
+         maxrows = max;
        }
 
        /**
index ab1be094ee01b2a518e0ca30278293d4e81e5bb5..8b505d1ab944e7b661bf25e6684f80b5813b757a 100644 (file)
@@ -17,7 +17,7 @@ import org.postgresql.largeobject.*;
 import org.postgresql.util.*;
 
 /**
- * $Id: Connection.java,v 1.5 2001/01/18 17:37:14 peter Exp $
+ * $Id: Connection.java,v 1.6 2001/01/31 08:26:02 peter Exp $
  *
  * A Connection represents a session with a specific database.  Within the
  * context of a Connection, SQL statements are executed and results are
@@ -265,7 +265,27 @@ public class Connection extends org.postgresql.Connection implements java.sql.Co
    */
   public boolean isClosed() throws SQLException
   {
-    return (pg_stream == null);
+    // If the stream is gone, then close() was called
+    if(pg_stream == null)
+      return true;
+
+    // ok, test the connection
+    try {
+      // by sending an empty query. If we are dead, then an SQLException should
+      // be thrown
+      java.sql.ResultSet rs = ExecSQL(" ");
+      if(rs!=null)
+        rs.close();
+
+      // By now, we must be alive
+      return false;
+    } catch(SQLException se) {
+      // Why throw an SQLException as this may fail without throwing one,
+      // ie isClosed() is called incase the connection has died, and we don't
+      // want to find out by an Exception, so instead we return true, as its
+      // most likely why it was thrown in the first place.
+      return true;
+    }
   }
 
   /**
index e2b7b4f8a1c81a247c0f1a51bcbbdd2e93374e78..54c7a9b589f78a083661ea16b2d56a69d15e72f3 100644 (file)
@@ -23,13 +23,13 @@ import org.postgresql.util.*;
  * parameter.  For instance, if the IN parameter has SQL type Integer, then
  * setInt should be used.
  *
- * <p>If arbitrary parameter type conversions are required, then the setObject 
+ * <p>If arbitrary parameter type conversions are required, then the setObject
  * method should be used with a target SQL type.
  *
  * @see ResultSet
  * @see java.sql.PreparedStatement
  */
-public class PreparedStatement extends Statement implements java.sql.PreparedStatement 
+public class PreparedStatement extends Statement implements java.sql.PreparedStatement
 {
        String sql;
        String[] templateStrings;
@@ -124,7 +124,7 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
                }
                s.append(templateStrings[inStrings.length]);
                return super.executeUpdate(s.toString());       // in Statement class
-       }       
+       }
 
        /**
         * Set a parameter to SQL NULL
@@ -264,7 +264,7 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
          else {
            StringBuffer b = new StringBuffer();
            int i;
-           
+
            b.append('\'');
            for (i = 0 ; i < x.length() ; ++i)
              {
@@ -327,7 +327,7 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
          //
          //set(parameterIndex, df.format(new java.util.Date(x.getTime()+86400000)));
        }
-  
+
        /**
         * Set a parameter to a java.sql.Time value.  The driver converts
         * this to a SQL TIME value when it sends it to the database.
@@ -406,7 +406,7 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
         * When a very large binary value is input to a LONGVARBINARY parameter,
         * it may be more practical to send it via a java.io.InputStream.
         * JDBC will read the data from the stream as needed, until it reaches
-        * end-of-file.  
+        * end-of-file.
         *
         * <P><B>Note:</B> This stream object can either be a standard Java
         * stream object or your own subclass that implements the standard
@@ -418,7 +418,25 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
         */
        public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException
        {
-               throw new PSQLException("postgresql.prep.is");
+          LargeObjectManager lom = connection.getLargeObjectAPI();
+          int oid = lom.create();
+          LargeObject lob = lom.open(oid);
+          OutputStream los = lob.getOutputStream();
+          try {
+            // could be buffered, but then the OutputStream returned by LargeObject
+            // 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) {
+              los.write(c);
+              c=x.read();
+            }
+            los.close();
+          } catch(IOException se) {
+            throw new PSQLException("postgresql.prep.is",se);
+          }
+          // lob is closed by the stream so don't call lob.close()
+          setInt(parameterIndex,oid);
        }
 
        /**
@@ -453,7 +471,7 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
         * @param x the object containing the input parameter value
         * @param targetSqlType The SQL type to be send to the database
         * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC
-        *      types this is the number of digits after the decimal.  For 
+        *      types this is the number of digits after the decimal.  For
         *      all other types this value will be ignored.
         * @exception SQLException if a database access error occurs
         */
@@ -501,7 +519,7 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
        {
                setObject(parameterIndex, x, targetSqlType, 0);
        }
-       
+
   /**
    * This stores an Object into a parameter.
    * <p>New for 6.4, if the object is not recognised, but it is
@@ -542,7 +560,7 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
 
        /**
         * Some prepared statements return multiple results; the execute method
-        * handles these complex statements as well as the simpler form of 
+        * handles these complex statements as well as the simpler form of
         * statements handled by executeQuery and executeUpdate
         *
         * @return true if the next result is a ResultSet; false if it is an
@@ -584,11 +602,11 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
                s.append(templateStrings[inStrings.length]);
                return s.toString();
        }
-       
+
        // **************************************************************
-       //      END OF PUBLIC INTERFACE 
+       //      END OF PUBLIC INTERFACE
        // **************************************************************
-       
+
        /**
         * There are a lot of setXXX classes which all basically do
         * the same thing.  We need a method which actually does the
@@ -604,62 +622,62 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
                        throw new PSQLException("postgresql.prep.range");
                inStrings[paramIndex - 1] = s;
        }
-    
+
     // ** JDBC 2 Extensions **
-    
+
     public void addBatch() throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public java.sql.ResultSetMetaData getMetaData() throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public void setArray(int i,Array x) throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public void setBlob(int i,Blob x) throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public void setCharacterStream(int i,java.io.Reader x,int length) throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public void setClob(int i,Clob x) throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public void setNull(int i,int t,String s) throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public void setRef(int i,Ref x) throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public void setDate(int i,java.sql.Date d,java.util.Calendar cal) throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public void setTime(int i,Time t,java.util.Calendar cal) throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
     public void setTimestamp(int i,Timestamp t,java.util.Calendar cal) throws SQLException
     {
        throw org.postgresql.Driver.notImplemented();
     }
-    
+
 }
index 59aef99ad5ee8242eb64571c2c14a3a17661ec4e..14fe603b69bfc6cddb96543f702883b6886ca0ba 100644 (file)
@@ -581,6 +581,15 @@ public class ResultSet extends org.postgresql.ResultSet implements java.sql.Resu
    */
   public InputStream getBinaryStream(int columnIndex) throws SQLException
   {
+    // New in 7.1 Handle OID's as BLOBS so return the input stream
+    if(!wasNullFlag)
+      if( fields[columnIndex - 1].getOID() == 26) {
+       LargeObjectManager lom = connection.getLargeObjectAPI();
+       LargeObject lob = lom.open(getInt(columnIndex));
+        return lob.getInputStream();
+      }
+
+    // Not an OID so fake the stream
     byte b[] = getBytes(columnIndex);
 
     if (b != null)
index 04315c096a16206ca027f0f71b34f0e1cf8879d0..a0a40c146955cbcdd066e2195b9da7fa5acb98c3 100644 (file)
@@ -32,6 +32,7 @@ public class Statement implements java.sql.Statement
     private Vector batch=null;
     int resultsettype;                // the resultset type to return
     int concurrency;         // is it updateable or not?
+    int maxrows=0;            // the maximum number of rows to return 0=unlimited
 
        /**
         * Constructor for a Statement.  It simply sets the connection
@@ -134,7 +135,7 @@ public class Statement implements java.sql.Statement
         */
        public int getMaxRows() throws SQLException
        {
-               return connection.maxrows;
+               return maxrows;
        }
 
        /**
@@ -146,7 +147,7 @@ public class Statement implements java.sql.Statement
         */
        public void setMaxRows(int max) throws SQLException
        {
-         connection.maxrows = max;
+         maxrows = max;
        }
 
        /**
@@ -274,7 +275,8 @@ public class Statement implements java.sql.Statement
        if(escapeProcessing)
            sql=connection.EscapeSQL(sql);
 
-       result = connection.ExecSQL(sql);
+        // New in 7.1, pass Statement so that ExecSQL can customise to it
+       result = connection.ExecSQL(sql,this);
 
         // New in 7.1, required for ResultSet.getStatement() to work
         ((org.postgresql.jdbc2.ResultSet)result).setStatement(this);
@@ -388,11 +390,6 @@ public class Statement implements java.sql.Statement
        throw org.postgresql.Driver.notImplemented();
     }
 
-    //public int getKeysetSize() throws SQLException
-    //{
-//     throw org.postgresql.Driver.notImplemented();
-    //}
-
     public int getResultSetConcurrency() throws SQLException
     {
       // new in 7.1
@@ -415,11 +412,6 @@ public class Statement implements java.sql.Statement
        throw org.postgresql.Driver.notImplemented();
     }
 
-    //public void setKeysetSize(int keys) throws SQLException
-    //{
-//     throw org.postgresql.Driver.notImplemented();
-    //}
-
     public void setResultSetConcurrency(int value) throws SQLException
     {
       concurrency=value;
@@ -430,4 +422,17 @@ public class Statement implements java.sql.Statement
       resultsettype=value;
     }
 
+    /**
+     * New in 7.1: Returns the Last inserted oid. This should be used, rather
+     * than the old method using getResultSet, which for executeUpdate returns
+     * null.
+     * @return OID of last insert
+     */
+    public int getInsertedOID() throws SQLException
+    {
+      if(result!=null)
+        return ((org.postgresql.ResultSet)result).getInsertedOID();
+      return 0;
+    }
+
 }
diff --git a/src/interfaces/jdbc/org/postgresql/largeobject/BlobInputStream.java b/src/interfaces/jdbc/org/postgresql/largeobject/BlobInputStream.java
new file mode 100644 (file)
index 0000000..646ea97
--- /dev/null
@@ -0,0 +1,156 @@
+package org.postgresql.largeobject;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.sql.SQLException;
+
+/**
+ * This is an initial implementation of an InputStream from a large object.
+ * For now, the bare minimum is implemented. Later (after 7.1) we will overide
+ * the other read methods to optimise them.
+ */
+public class BlobInputStream extends InputStream {
+  /**
+   * The parent LargeObject
+   */
+  private LargeObject lo;
+
+  /**
+   * Buffer used to improve performance
+   */
+  private byte[] buffer;
+
+  /**
+   * Position within buffer
+   */
+  private int bpos;
+
+  /**
+   * The buffer size
+   */
+  private int bsize;
+
+  /**
+   * The mark position
+   */
+  private int mpos=0;
+
+  /**
+   * @param lo LargeObject to read from
+   */
+  public BlobInputStream(LargeObject lo) {
+    this(lo,1024);
+  }
+
+  /**
+   * @param lo LargeObject to read from
+   * @param bsize buffer size
+   */
+  public BlobInputStream(LargeObject lo,int bsize) {
+    this.lo=lo;
+    buffer=null;
+    bpos=0;
+    this.bsize=bsize;
+  }
+
+  /**
+   * The minimum required to implement input stream
+   */
+  public int read() throws java.io.IOException {
+    try {
+      if(buffer==null || bpos>=buffer.length) {
+        buffer=lo.read(bsize);
+        bpos=0;
+      }
+
+      // Handle EOF
+      if(bpos>=buffer.length)
+        return -1;
+
+      return (int) buffer[bpos++];
+    } catch(SQLException se) {
+      throw new IOException(se.toString());
+    }
+  }
+
+
+    /**
+     * Closes this input stream and releases any system resources associated
+     * with the stream.
+     *
+     * <p> The <code>close</code> method of <code>InputStream</code> does
+     * nothing.
+     *
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public void close() throws IOException {
+      try {
+        lo.close();
+        lo=null;
+      } catch(SQLException se) {
+        throw new IOException(se.toString());
+      }
+    }
+
+    /**
+     * Marks the current position in this input stream. A subsequent call to
+     * the <code>reset</code> method repositions this stream at the last marked
+     * position so that subsequent reads re-read the same bytes.
+     *
+     * <p> The <code>readlimit</code> arguments tells this input stream to
+     * allow that many bytes to be read before the mark position gets
+     * invalidated.
+     *
+     * <p> The general contract of <code>mark</code> is that, if the method
+     * <code>markSupported</code> returns <code>true</code>, the stream somehow
+     * remembers all the bytes read after the call to <code>mark</code> and
+     * stands ready to supply those same bytes again if and whenever the method
+     * <code>reset</code> is called.  However, the stream is not required to
+     * remember any data at all if more than <code>readlimit</code> bytes are
+     * read from the stream before <code>reset</code> is called.
+     *
+     * <p> The <code>mark</code> method of <code>InputStream</code> does
+     * nothing.
+     *
+     * @param   readlimit   the maximum limit of bytes that can be read before
+     *                      the mark position becomes invalid.
+     * @see     java.io.InputStream#reset()
+     */
+    public synchronized void mark(int readlimit) {
+      try {
+        mpos=lo.tell();
+      } catch(SQLException se) {
+        //throw new IOException(se.toString());
+      }
+    }
+
+    /**
+     * Repositions this stream to the position at the time the
+     * <code>mark</code> method was last called on this input stream.
+     * NB: If mark is not called we move to the begining.
+     * @see     java.io.InputStream#mark(int)
+     * @see     java.io.IOException
+     */
+    public synchronized void reset() throws IOException {
+      try {
+        lo.seek(mpos);
+      } catch(SQLException se) {
+        throw new IOException(se.toString());
+      }
+    }
+
+    /**
+     * Tests if this input stream supports the <code>mark</code> and
+     * <code>reset</code> methods. The <code>markSupported</code> method of
+     * <code>InputStream</code> returns <code>false</code>.
+     *
+     * @return  <code>true</code> if this true type supports the mark and reset
+     *          method; <code>false</code> otherwise.
+     * @see     java.io.InputStream#mark(int)
+     * @see     java.io.InputStream#reset()
+     */
+    public boolean markSupported() {
+       return true;
+    }
+
+}
\ No newline at end of file
diff --git a/src/interfaces/jdbc/org/postgresql/largeobject/BlobOutputStream.java b/src/interfaces/jdbc/org/postgresql/largeobject/BlobOutputStream.java
new file mode 100644 (file)
index 0000000..0ac435a
--- /dev/null
@@ -0,0 +1,102 @@
+package org.postgresql.largeobject;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.sql.SQLException;
+
+/**
+ * This implements a basic output stream that writes to a LargeObject
+ */
+public class BlobOutputStream extends OutputStream {
+  /**
+   * The parent LargeObject
+   */
+  private LargeObject lo;
+
+  /**
+   * Buffer
+   */
+  private byte buf[];
+
+  /**
+   * Size of the buffer (default 1K)
+   */
+  private int bsize;
+
+  /**
+   * Position within the buffer
+   */
+  private int bpos;
+
+  /**
+   * Create an OutputStream to a large object
+   * @param lo LargeObject
+   */
+  public BlobOutputStream(LargeObject lo) {
+    this(lo,1024);
+  }
+
+  /**
+   * Create an OutputStream to a large object
+   * @param lo LargeObject
+   * @param bsize The size of the buffer used to improve performance
+   */
+  public BlobOutputStream(LargeObject lo,int bsize) {
+    this.lo=lo;
+    this.bsize=bsize;
+    buf=new byte[bsize];
+    bpos=0;
+  }
+
+  public void write(int b) throws java.io.IOException {
+      try {
+        if(bpos>=bsize) {
+          lo.write(buf);
+          bpos=0;
+        }
+        buf[bpos++]=(byte)b;
+      } catch(SQLException se) {
+        throw new IOException(se.toString());
+      }
+  }
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes
+     * to be written out. The general contract of <code>flush</code> is
+     * that calling it is an indication that, if any bytes previously
+     * written have been buffered by the implementation of the output
+     * stream, such bytes should immediately be written to their
+     * intended destination.
+     *
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public void flush() throws IOException {
+      try {
+        if(bpos>0)
+          lo.write(buf,0,bpos);
+        bpos=0;
+      } catch(SQLException se) {
+        throw new IOException(se.toString());
+      }
+    }
+
+    /**
+     * Closes this output stream and releases any system resources
+     * associated with this stream. The general contract of <code>close</code>
+     * is that it closes the output stream. A closed stream cannot perform
+     * output operations and cannot be reopened.
+     * <p>
+     * The <code>close</code> method of <code>OutputStream</code> does nothing.
+     *
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public void close() throws IOException {
+      try {
+        lo.close();
+        lo=null;
+      } catch(SQLException se) {
+        throw new IOException(se.toString());
+      }
+    }
+
+}
\ No newline at end of file
index 5cfd1383ffb1c19e8f262d0ea1f32c62f7907493..c73301d131814e978f1ba8a48a5e6d44c38fe9fd 100644 (file)
@@ -16,7 +16,7 @@ import org.postgresql.fastpath.*;
  * for this object.
  *
  * <p>Normally, client code would use the getAsciiStream, getBinaryStream,
- * or getUnicodeStream methods in ResultSet, or setAsciiStream, 
+ * or getUnicodeStream methods in ResultSet, or setAsciiStream,
  * setBinaryStream, or setUnicodeStream methods in PreparedStatement to
  * access Large Objects.
  *
@@ -47,21 +47,21 @@ public class LargeObject
    * Indicates a seek from the begining of a file
    */
   public static final int SEEK_SET = 0;
-  
+
   /**
    * Indicates a seek from the current position
    */
   public static final int SEEK_CUR = 1;
-  
+
   /**
    * Indicates a seek from the end of a file
    */
   public static final int SEEK_END = 2;
-  
+
   private Fastpath     fp;     // Fastpath API to use
   private int          oid;    // OID of this object
   private int          fd;     // the descriptor of the open large object
-  
+
   /**
    * This opens a large object.
    *
@@ -78,13 +78,13 @@ public class LargeObject
   {
     this.fp = fp;
     this.oid = oid;
-    
+
     FastpathArg args[] = new FastpathArg[2];
     args[0] = new FastpathArg(oid);
     args[1] = new FastpathArg(mode);
     this.fd = fp.getInteger("lo_open",args);
   }
-  
+
   /**
    * @return the OID of this LargeObject
    */
@@ -92,7 +92,7 @@ public class LargeObject
   {
     return oid;
   }
-  
+
   /**
    * This method closes the object. You must not call methods in this
    * object after this is called.
@@ -104,7 +104,7 @@ public class LargeObject
     args[0] = new FastpathArg(fd);
     fp.fastpath("lo_close",false,args); // true here as we dont care!!
   }
-  
+
   /**
    * Reads some data from the object, and return as a byte[] array
    *
@@ -120,7 +120,7 @@ public class LargeObject
        args[0] = new FastpathArg(fd);
        args[1] = new FastpathArg(len);
        return fp.getData("loread",args);
-       
+
        // This version allows us to break this down into 4k blocks
        //if(len<=4048) {
        //// handle as before, return the whole block in one go
@@ -145,20 +145,25 @@ public class LargeObject
        //return buf;
        //}
     }
-    
+
   /**
    * Reads some data from the object into an existing array
    *
    * @param buf destination array
    * @param off offset within array
    * @param len number of bytes to read
+   * @return the number of bytes actually read
    * @exception SQLException if a database-access error occurs.
    */
-  public void read(byte buf[],int off,int len) throws SQLException
+  public int read(byte buf[],int off,int len) throws SQLException
   {
-    System.arraycopy(read(len),0,buf,off,len);
+    byte b[] = read(len);
+    if(b.length<len)
+      len=b.length;
+    System.arraycopy(b,0,buf,off,len);
+    return len;
   }
-  
+
   /**
    * Writes an array to the object
    *
@@ -172,7 +177,7 @@ public class LargeObject
     args[1] = new FastpathArg(buf);
     fp.fastpath("lowrite",false,args);
   }
-  
+
   /**
    * Writes some data from an array to the object
    *
@@ -187,7 +192,7 @@ public class LargeObject
     System.arraycopy(buf,off,data,0,len);
     write(data);
   }
-  
+
   /**
    * Sets the current position within the object.
    *
@@ -206,7 +211,7 @@ public class LargeObject
     args[2] = new FastpathArg(ref);
     fp.fastpath("lo_lseek",false,args);
   }
-  
+
   /**
    * Sets the current position within the object.
    *
@@ -220,7 +225,7 @@ public class LargeObject
   {
     seek(pos,SEEK_SET);
   }
-  
+
   /**
    * @return the current position within the object
    * @exception SQLException if a database-access error occurs.
@@ -231,7 +236,7 @@ public class LargeObject
     args[0] = new FastpathArg(fd);
     return fp.getInteger("lo_tell",args);
   }
-  
+
   /**
    * This method is inefficient, as the only way to find out the size of
    * the object is to seek to the end, record the current position, then
@@ -250,7 +255,7 @@ public class LargeObject
     seek(cp,SEEK_SET);
     return sz;
   }
-  
+
   /**
    * Returns an InputStream from this object.
    *
@@ -261,9 +266,9 @@ public class LargeObject
    */
   public InputStream getInputStream() throws SQLException
   {
-    throw org.postgresql.Driver.notImplemented();
+    return new BlobInputStream(this);
   }
-  
+
   /**
    * Returns an OutputStream to this object
    *
@@ -274,6 +279,7 @@ public class LargeObject
    */
   public OutputStream getOutputStream() throws SQLException
   {
-    throw org.postgresql.Driver.notImplemented();
+    return new BlobOutputStream(this);
   }
+
 }