You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by rv...@apache.org on 2013/05/01 23:05:15 UTC

svn commit: r1478189 [1/2] - in /jena/Experimental/jena-jdbc: jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/ jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/ jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/ jena-j...

Author: rvesse
Date: Wed May  1 21:05:13 2013
New Revision: 1478189

URL: http://svn.apache.org/r1478189
Log:
Lots of improvements and bug fixes
Adds ability to have TYPE_SCROLL_INSENSITIVE result sets
Improves handling of transactions to share transactions on single threads where appropriate
Improves PreparedStatement support
Starts adding unit tests for prepared statements and executeBatch()

Added:
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedResults.java
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedSelectResults.java
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/TripleListResults.java
    jena/Experimental/jena-jdbc/jena-jdbc-driver-tdb/src/test/java/org/apache/jena/jdbc/tdb/connections/TestTdbDiskConnection.java
    jena/Experimental/jena-jdbc/jena-jdbc-driver-tdb/src/test/java/org/apache/jena/jdbc/tdb/connections/TestTdbMemConnection.java
Removed:
    jena/Experimental/jena-jdbc/jena-jdbc-driver-tdb/src/test/java/org/apache/jena/jdbc/tdb/connections/TestJenaJdbcTdbDiskConnection.java
    jena/Experimental/jena-jdbc/jena-jdbc-driver-tdb/src/test/java/org/apache/jena/jdbc/tdb/connections/TestJenaJdbcTdbMemConnection.java
Modified:
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/DatasetConnection.java
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/JenaConnection.java
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/StreamedResults.java
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetPreparedStatement.java
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetStatement.java
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaPreparedStatement.java
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaStatement.java
    jena/Experimental/jena-jdbc/jena-jdbc-core/src/test/java/org/apache/jena/jdbc/connections/AbstractJenaConnectionTests.java
    jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/connections/RemoteEndpointConnection.java
    jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/statements/RemoteEndpointPreparedStatement.java
    jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/statements/RemoteEndpointStatement.java
    jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/test/java/org/apache/jena/jdbc/remote/connections/TestRemoteEndpointConnectionWithGraphUris.java

Modified: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/DatasetConnection.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/DatasetConnection.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/DatasetConnection.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/DatasetConnection.java Wed May  1 21:05:13 2013
@@ -27,6 +27,7 @@ import org.apache.jena.jdbc.statements.J
 import org.apache.jena.jdbc.statements.JenaStatement;
 
 import com.hp.hpl.jena.query.Dataset;
+import com.hp.hpl.jena.query.ReadWrite;
 
 /**
  * Represents a connection to a {@link Dataset} instance
@@ -37,6 +38,9 @@ public abstract class DatasetConnection 
     protected Dataset ds;
     private boolean readonly = false;
 
+    private ThreadLocal<ReadWrite> transactionType = new ThreadLocal<ReadWrite>();
+    private ThreadLocal<Integer> transactionParticipants = new ThreadLocal<Integer>();
+
     /**
      * Creates a new dataset connection
      * 
@@ -84,32 +88,37 @@ public abstract class DatasetConnection 
     @Override
     protected JenaStatement createStatementInternal(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
             throws SQLException {
-        if (resultSetType != ResultSet.TYPE_FORWARD_ONLY)
-            throw new SQLException("Dataset backed connections currently only supports forward-only result sets");
+        if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE)
+            throw new SQLException("Dataset backed connections do not support scroll sensitive result sets");
         if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY)
             throw new SQLException("Dataset backed connections only supports read-only result sets");
-        return new DatasetStatement(this, ResultSet.FETCH_FORWARD, 0, resultSetHoldability, this.getAutoCommit(),
+        return new DatasetStatement(this, resultSetType, ResultSet.FETCH_FORWARD, 0, resultSetHoldability, this.getAutoCommit(),
                 this.getTransactionIsolation());
     }
-    
+
     @Override
-    protected JenaPreparedStatement createPreparedStatementInternal(String sparql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
-        if (resultSetType != ResultSet.TYPE_FORWARD_ONLY)
-            throw new SQLException("Dataset backed connections currently only supports forward-only result sets");
+    protected JenaPreparedStatement createPreparedStatementInternal(String sparql, int resultSetType, int resultSetConcurrency,
+            int resultSetHoldability) throws SQLException {
+        if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE)
+            throw new SQLException("Dataset backed connections do not support scroll sensitive result sets");
         if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY)
             throw new SQLException("Dataset backed connections only supports read-only result sets");
-        return new DatasetPreparedStatement(sparql, this, ResultSet.FETCH_FORWARD, 0, resultSetHoldability, this.getAutoCommit(), this.getTransactionIsolation());
+        return new DatasetPreparedStatement(sparql, this, resultSetType, ResultSet.FETCH_FORWARD, 0, resultSetHoldability,
+                this.getAutoCommit(), this.getTransactionIsolation());
     }
 
-    @Override public boolean isClosed() throws SQLException {
+    @Override
+    public boolean isClosed() throws SQLException {
         return this.ds == null;
     }
 
-    @Override public boolean isReadOnly() throws SQLException {
+    @Override
+    public boolean isReadOnly() throws SQLException {
         return this.readonly;
     }
 
-    @Override public boolean isValid(int timeout) throws SQLException {
+    @Override
+    public boolean isValid(int timeout) throws SQLException {
         return !this.isClosed();
     }
 
@@ -135,29 +144,88 @@ public abstract class DatasetConnection 
         }
     }
 
+    /**
+     * Begins a new transaction
+     * <p>
+     * Transactions are typically thread scoped and are shared by each thread so
+     * if there is an existing read transaction and another thread tries to
+     * start a read transaction it will join the existing read transaction.
+     * Trying to join a transaction not of the same type will produce an error.
+     * </p>
+     * 
+     * @param type
+     * @throws SQLException
+     */
+    public synchronized void begin(ReadWrite type) throws SQLException {
+        try {
+            if (ds.supportsTransactions()) {
+                if (ds.isInTransaction()) {
+                    // Additional participant in existing transaction
+                    ReadWrite currType = this.transactionType.get();
+                    if (currType.equals(type)) {
+                        this.transactionParticipants.set(this.transactionParticipants.get() + 1);
+                    } else {
+                        throw new SQLException(
+                                "Unable to start a transaction of a different type on the same thread as an existing transaction, please retry your operation on a different thread");
+                    }
+                } else {
+                    // Starting a new transaction
+                    this.transactionType.set(type);
+                    this.transactionParticipants.set(1);
+                    this.ds.begin(type);
+                }
+            }
+        } catch (SQLException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new SQLException("Unexpected error starting a transaction", e);
+        }
+    }
+
     @Override
-    protected void commitInternal() throws SQLException {
+    protected synchronized void commitInternal() throws SQLException {
         try {
             if (ds.supportsTransactions()) {
                 if (ds.isInTransaction()) {
-                    ds.commit();
-                    ds.end();
+                    // How many participants are there in this transaction?
+                    int participants = this.transactionParticipants.get();
+                    if (participants > 1) {
+                        // Transaction should remain active
+                        this.transactionParticipants.set(participants - 1);
+                    } else {
+                        // Now safe to commit
+                        ds.commit();
+                        ds.end();
+                        this.transactionParticipants.remove();
+                        this.transactionType.remove();
+                    }
+                } else {
+                    throw new SQLException("Attempted to commit a transaction when there was no active transaction");
                 }
             }
+        } catch (SQLException e) {
+            throw e;
         } catch (Exception e) {
             throw new SQLException("Unexpected error committing the transaction", e);
         }
     }
 
     @Override
-    protected void rollbackInternal() throws SQLException {
+    protected synchronized void rollbackInternal() throws SQLException {
         try {
             if (ds.supportsTransactions()) {
                 if (ds.isInTransaction()) {
+                    // Regardless of participants a rollback is always immediate
                     ds.abort();
                     ds.end();
+                    this.transactionType.remove();
+                    this.transactionParticipants.remove();
+                } else {
+                    throw new SQLException("Attempted to rollback a transaction when there was no active transaction");
                 }
             }
+        } catch (SQLException e) {
+            throw e;
         } catch (Exception e) {
             throw new SQLException("Unexpected error rolling back the transaction", e);
         }

Modified: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/JenaConnection.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/JenaConnection.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/JenaConnection.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/connections/JenaConnection.java Wed May  1 21:05:13 2013
@@ -294,13 +294,15 @@ public abstract class JenaConnection imp
     }
 
     private void closeStatements() throws SQLException {
-        if (this.statements.size() > 0) {
-            LOGGER.info("Attempting to close " + this.statements.size() + " open statements");
-            for (Statement stmt : this.statements) {
-                stmt.close();
+        synchronized (this.statements) {
+            if (this.statements.size() > 0) {
+                LOGGER.info("Attempting to close " + this.statements.size() + " open statements");
+                for (Statement stmt : this.statements) {
+                    stmt.close();
+                }
+                LOGGER.info("All open statements were closed");
+                this.statements.clear();
             }
-            LOGGER.info("All open statements were closed");
-            this.statements.clear();
         }
     }
 
@@ -380,7 +382,9 @@ public abstract class JenaConnection imp
         if (this.isClosed())
             throw new SQLException("Cannot create a statement after the connection was closed");
         JenaStatement stmt = this.createStatementInternal(resultSetType, resultSetConcurrency, resultSetHoldability);
-        this.statements.add(stmt);
+        synchronized (this.statements) {
+            this.statements.add(stmt);
+        }
         return stmt;
     }
 
@@ -507,18 +511,24 @@ public abstract class JenaConnection imp
             throw new SQLException("Cannot create a statement after the connection was closed");
         return this.createPreparedStatementInternal(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
     }
-    
+
     /**
-     * Helper method which derived implementations must implement to provide an actual prepared statement implementation
-     * @param sql SPARQL command
-     * @param resultSetType Desired result set type
-     * @param resultSetConcurrency Result set concurrency
-     * @param resultSetHoldability Result set holdability
+     * Helper method which derived implementations must implement to provide an
+     * actual prepared statement implementation
+     * 
+     * @param sql
+     *            SPARQL command
+     * @param resultSetType
+     *            Desired result set type
+     * @param resultSetConcurrency
+     *            Result set concurrency
+     * @param resultSetHoldability
+     *            Result set holdability
      * @return Prepared statement
      * @throws SQLException
      */
-    protected abstract JenaPreparedStatement createPreparedStatementInternal(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
-            throws SQLException;
+    protected abstract JenaPreparedStatement createPreparedStatementInternal(String sql, int resultSetType,
+            int resultSetConcurrency, int resultSetHoldability) throws SQLException;
 
     @Override
     public void releaseSavepoint(Savepoint savepoint) throws SQLException {

Added: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedResults.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedResults.java?rev=1478189&view=auto
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedResults.java (added)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedResults.java Wed May  1 21:05:13 2013
@@ -0,0 +1,354 @@
+/**
+ * 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.jena.jdbc.results;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import org.apache.jena.jdbc.statements.JenaStatement;
+
+import com.hp.hpl.jena.query.QueryExecution;
+
+/**
+ * Represents a set of streamed results backed by some {@link QueryExecution},
+ * streamed results are considered to be forward only
+ * 
+ * @param <T>
+ *            Type of the underlying result rows
+ * 
+ */
+public abstract class MaterializedResults<T> extends QueryExecutionResults {
+
+    private T currItem;
+    private int currRow = 0;
+
+    /**
+     * Creates new materialized results
+     * 
+     * @param statement
+     *            Statement that created the result set
+     * @param qe
+     *            Query Execution
+     * @param commit
+     *            Whether a commit is necessary when the results are closed
+     * @throws SQLException
+     *             Thrown if the arguments are invalid
+     */
+    public MaterializedResults(JenaStatement statement, QueryExecution qe, boolean commit) throws SQLException {
+        super(statement, qe, commit);
+    }
+
+    /**
+     * Gets the current result row (if any)
+     * 
+     * @return Result row, null if not at a row
+     * @throws SQLException
+     *             Thrown if the result set is closed
+     */
+    protected T getCurrentRow() throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result set is closed");
+        return this.currItem;
+    }
+
+    /**
+     * Method which derived classes must implement to indicate whether they have
+     * further rows available to move forwards to
+     * 
+     * @return True if further rows are available, false otherwise
+     * @throws SQLException
+     *             Thrown if an error determining whether rows are available
+     */
+    protected abstract boolean hasNext() throws SQLException;
+
+    /**
+     * Method which derived classes must implement to indicate whether they have
+     * further rows available to move backwards to
+     * 
+     * @return True if further rows are available, false otherwise
+     * @throws SQLException
+     *             Thrown if an error determining whether rows are available
+     */
+    protected abstract boolean hasPrevious() throws SQLException;
+
+    /**
+     * Method which derived classes must implement to provide the next row
+     * available
+     * 
+     * @return Next row available
+     * @throws SQLException
+     *             Thrown if this method is invoked when no further rows are
+     *             available
+     */
+    protected abstract T moveNext() throws SQLException;
+
+    /**
+     * Method which derived classes must implement to provide the previous row
+     * available
+     * 
+     * @return Previous row available
+     * @throws SQLException
+     *             Thrown if this method is invoked when no previous rows are
+     *             available
+     */
+    protected abstract T movePrevious() throws SQLException;
+
+    protected abstract int getTotalRows() throws SQLException;
+
+    @Override
+    public final boolean absolute(int row) throws SQLException {
+        if (this.isClosed()) {
+            throw new SQLException("Cannot move to a row after the result set has been closed");
+        } else if (row == 1) {
+            // Try and move to the first row
+            return this.first();
+        } else if (row == -1) {
+            // Try and move to the last row
+            return this.last();
+        } else if (row == 0) {
+            // Try and move to before first row
+            this.beforeFirst();
+            return false;
+        } else if (row >= this.getTotalRows()) {
+            // Try and move to after last row
+            this.afterLast();
+            return false;
+        } else if (row <= 0) {
+            // Move to a row relative to the end of the result set
+            int destRow = this.getTotalRows() + row;
+            if (destRow < 1) {
+                // Move to before first
+                this.beforeFirst();
+                return false;
+            } else {
+                // Call ourselves to avoid repeating logic
+                return this.absolute(destRow);
+            }
+        } else if (row == this.currRow) {
+            // Already at the desired row
+            return true;
+        } else if (row < this.currRow) {
+            // After the desired row
+            while (this.hasPrevious() && row < this.currRow) {
+                this.currItem = this.movePrevious();
+                this.currRow--;
+            }
+            // Since we already checked that the desired row is a valid absolute
+            // row it is always possible to move backwards to the desired row
+            return true;
+        } else {
+            // Before the desired row
+            while (this.hasNext() && this.currRow < row) {
+                this.currItem = this.moveNext();
+                this.currRow++;
+            }
+            // Since we already checked that the desired row is a valid absolute
+            // row it is always possible to move forwards to the desired row
+            return true;
+        }
+    }
+
+    @Override
+    public final void afterLast() throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+
+        // Move to end of results if necessary
+        while (this.hasNext()) {
+            this.currItem = this.moveNext();
+            this.currRow++;
+        }
+        this.currItem = null;
+        this.currRow = this.getTotalRows() + 1;
+    }
+
+    @Override
+    public final void beforeFirst() throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+
+        // Move to start of results if necessary
+        while (this.hasPrevious()) {
+            this.currItem = this.movePrevious();
+            this.currRow--;
+        }
+        this.currItem = null;
+        this.currRow = 0;
+    }
+
+    @Override
+    protected final void closeInternal() throws SQLException {
+        this.currItem = null;
+        this.closeStreamInternal();
+    }
+
+    protected abstract void closeStreamInternal() throws SQLException;
+
+    @Override
+    public final boolean first() throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+
+        // If no rows this should always return false
+        if (this.getTotalRows() == 0)
+            return false;
+
+        // Are we already at the first row?
+        if (this.currRow == 1)
+            return true;
+
+        // Otherwise move backwards to it
+        while (this.hasPrevious()) {
+            this.currItem = this.movePrevious();
+            this.currRow--;
+        }
+        return true;
+    }
+
+    @Override
+    public final int getFetchDirection() throws SQLException {
+        return ResultSet.FETCH_FORWARD;
+    }
+
+    @Override
+    public final int getFetchSize() throws SQLException {
+        // TODO Need a buffering wrapper around ResultSet to make this
+        // configurable
+        return 0;
+    }
+
+    @Override
+    public final int getRow() throws SQLException {
+        return this.currRow;
+    }
+
+    @Override
+    public final int getType() throws SQLException {
+        return ResultSet.TYPE_SCROLL_INSENSITIVE;
+    }
+
+    @Override
+    public final boolean isAfterLast() throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+        return this.currRow > this.getTotalRows();
+    }
+
+    @Override
+    public final boolean isBeforeFirst() throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+        return this.currRow == 0;
+    }
+
+    @Override
+    public final boolean isFirst() throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+        return this.currRow == 1;
+    }
+
+    @Override
+    public final boolean isLast() throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+        return this.currRow == this.getTotalRows();
+    }
+
+    @Override
+    public final boolean last() throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Jena JDBC Result Sets are forward-only");
+
+        // If no rows this should always return false
+        if (this.getTotalRows() == 0)
+            return false;
+
+        // Are we already at the last row?
+        if (this.currRow == this.getTotalRows())
+            return true;
+
+        // Otherwise move forwards to the last row
+        while (this.hasNext()) {
+            this.currItem = this.moveNext();
+            this.currRow++;
+        }
+        return true;
+
+    }
+
+    @Override
+    public final boolean next() throws SQLException {
+        if (this.isClosed()) {
+            throw new SQLException("Cannot move to the next row in a closed result set");
+        } else {
+            if (this.hasNext()) {
+                this.currItem = this.moveNext();
+                this.currRow++;
+                return true;
+            } else {
+                this.currItem = null;
+                this.currRow = this.getTotalRows() + 1;
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public final boolean relative(int rows) throws SQLException {
+        if (this.isClosed()) {
+            throw new SQLException("Cannot move to a row after the result set has been closed");
+        } else if (rows == 0) {
+            // Already at the desired row
+            return true;
+        } else if (rows < 0) {
+            // Calculate destination row and use absolute() to move there
+            int destRow = this.currRow + rows;
+            return this.absolute(destRow);
+        } else if (this.currRow + rows > this.getTotalRows()) {
+            // Would result in moving beyond the end of the results
+            this.afterLast();
+            return false;
+        } else {
+            // Before the desired row
+            int moved = 0;
+            while (this.hasNext() && moved < rows) {
+                this.currItem = this.moveNext();
+                this.currRow++;
+                moved++;
+            }
+            // Since we already checked if the move would take us beyond the end
+            // of the results we will always have reached the desired row
+            return true;
+        }
+    }
+
+    @Override
+    public final void setFetchDirection(int direction) throws SQLException {
+        if (direction != ResultSet.FETCH_FORWARD)
+            throw new SQLFeatureNotSupportedException("Jena JDBC Result Sets only support forward fetch");
+    }
+
+    @Override
+    public final void setFetchSize(int rows) throws SQLException {
+        // TODO Need to provide some buffering wrapper over a ResultSet to make
+        // this possible
+        throw new SQLFeatureNotSupportedException();
+    }
+}

Added: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedSelectResults.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedSelectResults.java?rev=1478189&view=auto
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedSelectResults.java (added)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/MaterializedSelectResults.java Wed May  1 21:05:13 2013
@@ -0,0 +1,157 @@
+/**
+ * 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.jena.jdbc.results;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+import org.apache.jena.atlas.lib.Closeable;
+import org.apache.jena.jdbc.results.metadata.SelectResultsMetadata;
+import org.apache.jena.jdbc.statements.JenaStatement;
+
+import com.hp.hpl.jena.graph.Node;
+import com.hp.hpl.jena.query.QueryExecution;
+import com.hp.hpl.jena.query.ResultSetRewindable;
+import com.hp.hpl.jena.sparql.core.Var;
+import com.hp.hpl.jena.sparql.engine.binding.Binding;
+
+/**
+ * Represents SPARQL SELECT results
+ * 
+ */
+public class MaterializedSelectResults extends MaterializedResults<Binding> {
+
+    private ResultSetRewindable innerResults;
+    private Stack<Binding> previousResults = new Stack<Binding>();
+    private List<String> columns;
+    private SelectResultsMetadata metadata;
+
+    /**
+     * Creates new select results
+     * 
+     * @param statement
+     *            Statement that created the result set
+     * @param qe
+     *            Query Execution
+     * @param results
+     *            SPARQL Results
+     * @param commit
+     *            Whether a commit is necessary when the results are closed
+     * @throws SQLException
+     *             Thrown if the arguments are invalid
+     */
+    public MaterializedSelectResults(JenaStatement statement, QueryExecution qe, ResultSetRewindable results, boolean commit)
+            throws SQLException {
+        super(statement, qe, commit);
+        if (results == null)
+            throw new SQLException("SPARQL Results cannot be null");
+        this.innerResults = results;
+        this.columns = new ArrayList<String>(this.innerResults.getResultVars());
+        this.metadata = new SelectResultsMetadata(this, this.innerResults);
+    }
+
+    @Override
+    public void closeStreamInternal() throws SQLException {
+        if (this.innerResults != null) {
+            if (this.innerResults instanceof Closeable) {
+                ((Closeable) this.innerResults).close();
+            }
+            this.innerResults = null;
+        }
+    }
+
+    public int findColumn(String columnLabel) throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+        for (int i = 0; i < this.columns.size(); i++) {
+            if (this.columns.get(i).equals(columnLabel)) {
+                // Remember that JDBC uses a 1 based index
+                return i + 1;
+            }
+        }
+        throw new SQLException("The given column does not exist in this result set");
+    }
+
+    @Override
+    protected String findColumnLabel(int columnIndex) throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+        if (columnIndex >= 1 && columnIndex <= this.columns.size()) {
+            // Remember that JDBC uses a 1 based index
+            return this.columns.get(columnIndex - 1);
+        } else {
+            throw new SQLException("Column Index is out of bounds");
+        }
+    }
+
+    @Override
+    protected Node getNode(String columnLabel) throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+        if (this.getCurrentRow() == null)
+            throw new SQLException("Not currently at a row");
+        if (!this.columns.contains(columnLabel))
+            throw new SQLException("The given column does not exist in the result set");
+        return this.getCurrentRow().get(Var.alloc(columnLabel));
+    }
+
+    @Override
+    public ResultSetMetaData getMetaData() throws SQLException {
+        return this.metadata;
+    }
+
+    /**
+     * Gets whether there are further rows in the underlying SELECT results
+     */
+    @Override
+    protected boolean hasNext() throws SQLException {
+        // No null check here because superclass will not call us after we are
+        // closed and set to null
+        return this.innerResults.hasNext();
+    }
+
+    /**
+     * Gets the next row from the underlying SELECT results
+     */
+    @Override
+    protected Binding moveNext() throws SQLException {
+        // No null check here because superclass will not call us after we are
+        // closed and set to null
+        this.previousResults.push(this.innerResults.nextBinding());
+        return this.previousResults.peek();
+    }
+
+    @Override
+    protected boolean hasPrevious() throws SQLException {
+        return this.previousResults.size() > 0;
+    }
+
+    @Override
+    protected Binding movePrevious() throws SQLException {
+        return this.previousResults.pop();
+    }
+    
+    @Override
+    protected int getTotalRows() {
+        return this.innerResults.size();
+    }
+}

Modified: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/StreamedResults.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/StreamedResults.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/StreamedResults.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/StreamedResults.java Wed May  1 21:05:13 2013
@@ -41,7 +41,7 @@ public abstract class StreamedResults<T>
     private int currRow = 0;
 
     /**
-     * Creates new select results
+     * Creates new streamed results
      * 
      * @param statement
      *            Statement that created the result set

Added: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/TripleListResults.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/TripleListResults.java?rev=1478189&view=auto
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/TripleListResults.java (added)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/results/TripleListResults.java Wed May  1 21:05:13 2013
@@ -0,0 +1,154 @@
+/**
+ * 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.jena.jdbc.results;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.apache.jena.atlas.iterator.PeekIterator;
+import org.apache.jena.jdbc.results.metadata.TripleResultsMetadata;
+import org.apache.jena.jdbc.statements.JenaStatement;
+
+import com.hp.hpl.jena.graph.Node;
+import com.hp.hpl.jena.graph.Triple;
+import com.hp.hpl.jena.query.QueryExecution;
+
+/**
+ * Represents results of a CONSTRUCT/DESCRIBE query where the results are
+ * materialized
+ * 
+ */
+public class TripleListResults extends MaterializedResults<Triple> {
+
+    private TripleResultsMetadata metadata;
+    private List<Triple> triples;
+
+    /**
+     * Creates a new result set which is backed by a triple iterator
+     * 
+     * @param statement
+     *            Statement
+     * @param qe
+     *            Query Execution
+     * @param ts
+     *            Triple Iterator
+     * @param commit
+     *            Whether a commit is necessary when the result set is closed
+     * @throws SQLException
+     *             Thrown if there is a problem creating the results
+     */
+    public TripleListResults(JenaStatement statement, QueryExecution qe, List<Triple> ts, boolean commit) throws SQLException {
+        super(statement, qe, commit);
+        if (ts == null)
+            throw new SQLException("Triple Iterator cannot be null");
+        this.triples = ts;
+        this.metadata = new TripleResultsMetadata(this, new PeekIterator<Triple>(ts.iterator()));
+    }
+
+    @Override
+    public int findColumn(String columnLabel) throws SQLException {
+        if (TripleResultsMetadata.COLUMN_LABEL_SUBJECT.equals(columnLabel)) {
+            return 1;
+        } else if (TripleResultsMetadata.COLUMN_LABEL_PREDICATE.equals(columnLabel)) {
+            return 2;
+        } else if (TripleResultsMetadata.COLUMN_LABEL_OBJECT.equals(columnLabel)) {
+            return 3;
+        } else {
+            throw new SQLException("Column " + columnLabel + " does not exist in these results");
+        }
+    }
+
+    @Override
+    protected boolean hasNext() throws SQLException {
+        // No null check here because superclass will not call us after we are
+        // closed and set to null
+        return this.getRow() < this.triples.size();
+    }
+
+    @Override
+    protected Triple moveNext() throws SQLException {
+        return this.triples.get(this.getRow());
+    }
+    
+    @Override
+    protected boolean hasPrevious() throws SQLException {
+        return this.getRow() > 1;
+    }
+    
+    @Override
+    protected Triple movePrevious() throws SQLException {
+        return this.triples.get(this.getRow() - 1);
+    }
+    
+    @Override
+    protected int getTotalRows() {
+        return this.triples.size();
+    }
+
+    @Override
+    protected void closeStreamInternal() throws SQLException {
+        if (this.triples != null) {
+            this.triples = null;
+        }
+    }
+
+    @Override
+    public ResultSetMetaData getMetaData() throws SQLException {
+        return metadata;
+    }
+
+    @Override
+    protected String findColumnLabel(int columnIndex) throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+        if (columnIndex >= 1 && columnIndex <= TripleResultsMetadata.NUM_COLUMNS) {
+            switch (columnIndex) {
+            case 1:
+                return TripleResultsMetadata.COLUMN_LABEL_SUBJECT;
+            case 2:
+                return TripleResultsMetadata.COLUMN_LABEL_PREDICATE;
+            case 3:
+                return TripleResultsMetadata.COLUMN_LABEL_OBJECT;
+            default:
+                throw new SQLException("Column Index is out of bounds");
+            }
+        } else {
+            throw new SQLException("Column Index is out of bounds");
+        }
+    }
+
+    @Override
+    protected Node getNode(String columnLabel) throws SQLException {
+        if (this.isClosed())
+            throw new SQLException("Result Set is closed");
+        if (this.getCurrentRow() == null)
+            throw new SQLException("Not currently at a row");
+        Triple t = this.getCurrentRow();
+        if (TripleResultsMetadata.COLUMN_LABEL_SUBJECT.equals(columnLabel)) {
+            return t.getSubject();
+        } else if (TripleResultsMetadata.COLUMN_LABEL_PREDICATE.equals(columnLabel)) {
+            return t.getPredicate();
+        } else if (TripleResultsMetadata.COLUMN_LABEL_OBJECT.equals(columnLabel)) {
+            return t.getObject();
+        } else {
+            throw new SQLException("Unknown column label");
+        }
+    }
+}

Modified: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetPreparedStatement.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetPreparedStatement.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetPreparedStatement.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetPreparedStatement.java Wed May  1 21:05:13 2013
@@ -51,8 +51,8 @@ public class DatasetPreparedStatement ex
      *             Thrown if there is an error with the statement parameters
      */
     public DatasetPreparedStatement(String sparql, DatasetConnection connection) throws SQLException {
-        this(sparql, connection, DEFAULT_FETCH_DIRECTION, DEFAULT_FETCH_SIZE, DEFAULT_HOLDABILITY, DEFAULT_AUTO_COMMIT,
-                DEFAULT_TRANSACTION_LEVEL);
+        this(sparql, connection, DEFAULT_TYPE, DEFAULT_FETCH_DIRECTION, DEFAULT_FETCH_SIZE, DEFAULT_HOLDABILITY,
+                DEFAULT_AUTO_COMMIT, DEFAULT_TRANSACTION_LEVEL);
     }
 
     /**
@@ -62,6 +62,8 @@ public class DatasetPreparedStatement ex
      *            SPARQL command
      * @param connection
      *            Connection
+     * @param type
+     *            Result Set type for result sets produced by this statement
      * @param fetchDir
      *            Fetch Direction
      * @param fetchSize
@@ -76,9 +78,9 @@ public class DatasetPreparedStatement ex
      *             Thrown if there is an error with the statement parameters
      * 
      */
-    public DatasetPreparedStatement(String sparql, DatasetConnection connection, int fetchDir, int fetchSize, int holdability,
-            boolean autoCommit, int transactionLevel) throws SQLException {
-        super(sparql, connection, fetchDir, fetchSize, holdability, autoCommit, transactionLevel);
+    public DatasetPreparedStatement(String sparql, DatasetConnection connection, int type, int fetchDir, int fetchSize,
+            int holdability, boolean autoCommit, int transactionLevel) throws SQLException {
+        super(sparql, connection, type, fetchDir, fetchSize, holdability, autoCommit, transactionLevel);
         this.dsConn = connection;
     }
 
@@ -101,7 +103,7 @@ public class DatasetPreparedStatement ex
     @Override
     protected void beginTransaction(ReadWrite type) throws SQLException {
         try {
-            this.dsConn.getJenaDataset().begin(type);
+            this.dsConn.begin(type);
         } catch (Exception e) {
             throw new SQLException("Unexpected error starting a transaction", e);
         }

Modified: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetStatement.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetStatement.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetStatement.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/DatasetStatement.java Wed May  1 21:05:13 2013
@@ -49,7 +49,7 @@ public class DatasetStatement extends Je
      *             Thrown if there is an error with the statement parameters
      */
     public DatasetStatement(DatasetConnection connection) throws SQLException {
-        this(connection, DEFAULT_FETCH_DIRECTION, DEFAULT_FETCH_SIZE, DEFAULT_HOLDABILITY, DEFAULT_AUTO_COMMIT,
+        this(connection, DEFAULT_TYPE, DEFAULT_FETCH_DIRECTION, DEFAULT_FETCH_SIZE, DEFAULT_HOLDABILITY, DEFAULT_AUTO_COMMIT,
                 DEFAULT_TRANSACTION_LEVEL);
     }
 
@@ -58,6 +58,8 @@ public class DatasetStatement extends Je
      * 
      * @param connection
      *            Connection
+     * @param type
+     *            Result Set type for result sets produced by this statement
      * @param fetchDir
      *            Fetch Direction
      * @param fetchSize
@@ -72,9 +74,9 @@ public class DatasetStatement extends Je
      *             Thrown if there is an error with the statement parameters
      * 
      */
-    public DatasetStatement(DatasetConnection connection, int fetchDir, int fetchSize, int holdability, boolean autoCommit,
+    public DatasetStatement(DatasetConnection connection, int type, int fetchDir, int fetchSize, int holdability, boolean autoCommit,
             int transactionLevel) throws SQLException {
-        super(connection, fetchDir, fetchSize, holdability, autoCommit, transactionLevel);
+        super(connection, type, fetchDir, fetchSize, holdability, autoCommit, transactionLevel);
         this.dsConn = connection;
     }
 
@@ -97,7 +99,7 @@ public class DatasetStatement extends Je
     @Override
     protected void beginTransaction(ReadWrite type) throws SQLException {
         try {
-            this.dsConn.getJenaDataset().begin(type);
+            this.dsConn.begin(type);
         } catch (Exception e) {
             throw new SQLException("Unexpected error starting a transaction", e);
         }

Modified: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaPreparedStatement.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaPreparedStatement.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaPreparedStatement.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaPreparedStatement.java Wed May  1 21:05:13 2013
@@ -65,6 +65,8 @@ public abstract class JenaPreparedStatem
      * @param sparql
      * @param connection
      *            Connection
+     * @param type
+     *            Result Set type for result sets produced by this statement
      * @param fetchDir
      *            Fetch Direction
      * @param fetchSize
@@ -78,9 +80,9 @@ public abstract class JenaPreparedStatem
      * @throws SQLException
      *             Thrown if there is a problem preparing the statement
      */
-    public JenaPreparedStatement(String sparql, JenaConnection connection, int fetchDir, int fetchSize, int holdability,
-            boolean autoCommit, int transactionLevel) throws SQLException {
-        super(connection, fetchDir, fetchSize, holdability, autoCommit, transactionLevel);
+    public JenaPreparedStatement(String sparql, JenaConnection connection, int type, int fetchDir, int fetchSize,
+            int holdability, boolean autoCommit, int transactionLevel) throws SQLException {
+        super(connection, type, fetchDir, fetchSize, holdability, autoCommit, transactionLevel);
 
         this.sparqlStr.setCommandText(sparql);
         this.paramMetadata = new JenaParameterMetadata(this.sparqlStr);
@@ -110,7 +112,7 @@ public abstract class JenaPreparedStatem
     public int executeUpdate() throws SQLException {
         return this.executeUpdate(this.sparqlStr.toString());
     }
-    
+
     @Override
     public ResultSetMetaData getMetaData() throws SQLException {
         // Return null because we don't know in advance the column types
@@ -305,7 +307,7 @@ public abstract class JenaPreparedStatem
     @Override
     public void setObject(int parameterIndex, Object value) throws SQLException {
         if (value instanceof Node) {
-            this.setParameter(parameterIndex, (Node)value);
+            this.setParameter(parameterIndex, (Node) value);
         } else {
             throw new SQLException("Calls to setObject() must pass an object of type Node");
         }
@@ -338,8 +340,7 @@ public abstract class JenaPreparedStatem
 
     @Override
     public void setShort(int parameterIndex, short value) throws SQLException {
-        this.setParameter(parameterIndex,
-                NodeFactory.createLiteral(Short.toString(value), XSDDatatype.XSDshort));
+        this.setParameter(parameterIndex, NodeFactory.createLiteral(Short.toString(value), XSDDatatype.XSDshort));
     }
 
     @Override
@@ -356,20 +357,17 @@ public abstract class JenaPreparedStatem
 
     @Override
     public void setTime(int parameterIndex, Time value, Calendar arg2) throws SQLException {
-        // TODO Auto-generated method stub
-
+        throw new SQLFeatureNotSupportedException();
     }
 
     @Override
     public void setTimestamp(int parameterIndex, Timestamp value) throws SQLException {
-        // TODO Auto-generated method stub
-
+        throw new SQLFeatureNotSupportedException();
     }
 
     @Override
     public void setTimestamp(int parameterIndex, Timestamp value, Calendar arg2) throws SQLException {
-        // TODO Auto-generated method stub
-
+        throw new SQLFeatureNotSupportedException();
     }
 
     @Override

Modified: jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaStatement.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaStatement.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaStatement.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/main/java/org/apache/jena/jdbc/statements/JenaStatement.java Wed May  1 21:05:13 2013
@@ -30,10 +30,13 @@ import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.jena.atlas.iterator.Iter;
 import org.apache.jena.jdbc.connections.JenaConnection;
 import org.apache.jena.jdbc.results.AskResults;
+import org.apache.jena.jdbc.results.MaterializedSelectResults;
 import org.apache.jena.jdbc.results.SelectResults;
 import org.apache.jena.jdbc.results.TripleIteratorResults;
+import org.apache.jena.jdbc.results.TripleListResults;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -41,6 +44,7 @@ import com.hp.hpl.jena.query.Query;
 import com.hp.hpl.jena.query.QueryExecution;
 import com.hp.hpl.jena.query.QueryFactory;
 import com.hp.hpl.jena.query.ReadWrite;
+import com.hp.hpl.jena.query.ResultSetFactory;
 import com.hp.hpl.jena.update.UpdateFactory;
 import com.hp.hpl.jena.update.UpdateProcessor;
 import com.hp.hpl.jena.update.UpdateRequest;
@@ -51,7 +55,7 @@ import com.hp.hpl.jena.update.UpdateRequ
  * 
  */
 public abstract class JenaStatement implements Statement {
-    
+
     private static final Logger LOGGER = LoggerFactory.getLogger(JenaStatement.class);
 
     protected static final int DEFAULT_HOLDABILITY = ResultSet.CLOSE_CURSORS_AT_COMMIT;
@@ -60,6 +64,7 @@ public abstract class JenaStatement impl
     protected static final boolean DEFAULT_AUTO_COMMIT = JenaConnection.DEFAULT_AUTO_COMMIT;
     protected static final int DEFAULT_TRANSACTION_LEVEL = JenaConnection.DEFAULT_ISOLATION_LEVEL;
     protected static final int NO_LIMIT = 0;
+    protected static final int DEFAULT_TYPE = ResultSet.TYPE_FORWARD_ONLY;
 
     private List<String> commands = new ArrayList<String>();
     private SQLWarning warnings = null;
@@ -68,6 +73,7 @@ public abstract class JenaStatement impl
     private Queue<ResultSet> results = new LinkedList<ResultSet>();
     private List<ResultSet> openResults = new ArrayList<ResultSet>();
     private boolean closed = false;
+    private int type = DEFAULT_TYPE;
     private int fetchDirection = DEFAULT_FETCH_DIRECTION;
     private int fetchSize = DEFAULT_FETCH_SIZE;
     private int holdability = DEFAULT_HOLDABILITY;
@@ -88,7 +94,7 @@ public abstract class JenaStatement impl
      *             Thrown if the arguments are invalid
      */
     public JenaStatement(JenaConnection connection) throws SQLException {
-        this(connection, DEFAULT_FETCH_DIRECTION, DEFAULT_FETCH_SIZE, DEFAULT_HOLDABILITY, DEFAULT_AUTO_COMMIT,
+        this(connection, DEFAULT_TYPE, DEFAULT_FETCH_DIRECTION, DEFAULT_FETCH_SIZE, DEFAULT_HOLDABILITY, DEFAULT_AUTO_COMMIT,
                 DEFAULT_TRANSACTION_LEVEL);
     }
 
@@ -97,6 +103,8 @@ public abstract class JenaStatement impl
      * 
      * @param connection
      *            Connection
+     * @param type
+     *            Result Set type for result sets produced by this statement
      * @param fetchDir
      *            Fetch Direction
      * @param fetchSize
@@ -111,12 +119,13 @@ public abstract class JenaStatement impl
      *             Thrown if there is an error with the statement parameters
      * 
      */
-    public JenaStatement(JenaConnection connection, int fetchDir, int fetchSize, int holdability, boolean autoCommit,
+    public JenaStatement(JenaConnection connection, int type, int fetchDir, int fetchSize, int holdability, boolean autoCommit,
             int transactionLevel) throws SQLException {
         if (connection == null)
             throw new SQLException("Cannot create a Statement with a null connection");
         this.connection = connection;
         this.checkFetchDirection(fetchDir);
+        this.type = type;
         this.fetchDirection = fetchDir;
         this.fetchSize = fetchSize;
         this.checkHoldability(holdability);
@@ -192,7 +201,7 @@ public abstract class JenaStatement impl
     public final boolean execute(String sql) throws SQLException {
         if (this.isClosed())
             throw new SQLException("The Statement is closed");
-                
+
         // Pre-process the command text
         LOGGER.info("Received input command text:\n {}", sql);
         sql = this.connection.applyPreProcessors(sql);
@@ -230,12 +239,15 @@ public abstract class JenaStatement impl
     private boolean executeQuery(Query q) throws SQLException {
         if (this.isClosed())
             throw new SQLException("The Statement is closed");
-        
+
         // Do we need transactions?
         boolean needsBegin = (!this.autoCommit && this.transactionLevel != Connection.TRANSACTION_NONE && !this
                 .hasActiveTransaction());
         boolean needsCommit = (this.autoCommit && this.transactionLevel != Connection.TRANSACTION_NONE);
 
+        // Do this first in a separate try catch so if we fail to start a
+        // transaction we don't then try to roll it back which can mask the
+        // actual cause of the error
         try {
             // Start a transaction if necessary
             if (needsCommit) {
@@ -245,7 +257,12 @@ public abstract class JenaStatement impl
                 LOGGER.info("Starting a new transaction to run query, transaction will not be auto-committed");
                 this.beginTransaction(ReadWrite.WRITE);
             }
-            
+        } catch (Exception e) {
+            LOGGER.error("Starting the new transaction failed", e);
+            throw new SQLException("Failed to start a new query transaction", e);
+        }
+
+        try {
             // Pre-process the query
             q = this.connection.applyPreProcessors(q);
 
@@ -270,26 +287,68 @@ public abstract class JenaStatement impl
 
             // Return the appropriate result set type
             if (q.isSelectType()) {
-                this.currResults = new SelectResults(this, qe, qe.execSelect(), needsCommit);
-                return true;
+                switch (this.type) {
+                case ResultSet.TYPE_SCROLL_INSENSITIVE:
+                    this.currResults = new MaterializedSelectResults(this, qe, ResultSetFactory.makeRewindable(qe.execSelect()),
+                            false);
+                    break;
+                case ResultSet.TYPE_FORWARD_ONLY:
+                default:
+                    this.currResults = new SelectResults(this, qe, qe.execSelect(), needsCommit);
+                    break;
+                }
             } else if (q.isAskType()) {
                 boolean askRes = qe.execAsk();
                 qe.close();
                 this.currResults = new AskResults(this, askRes, needsCommit);
-                return true;
             } else if (q.isDescribeType()) {
-                this.currResults = new TripleIteratorResults(this, qe, qe.execDescribeTriples(), needsCommit);
-                return true;
+                switch (this.type) {
+                case ResultSet.TYPE_SCROLL_INSENSITIVE:
+                    this.currResults = new TripleListResults(this, qe, Iter.toList(qe.execDescribeTriples()), false);
+                    break;
+                case ResultSet.TYPE_FORWARD_ONLY:
+                default:
+                    this.currResults = new TripleIteratorResults(this, qe, qe.execDescribeTriples(), needsCommit);
+                    break;
+                }
             } else if (q.isConstructType()) {
-                this.currResults = new TripleIteratorResults(this, qe, qe.execConstructTriples(), needsCommit);
-                return true;
+                switch (this.type) {
+                case ResultSet.TYPE_SCROLL_INSENSITIVE:
+                    this.currResults = new TripleListResults(this, qe, Iter.toList(qe.execConstructTriples()), false);
+                    break;
+                case ResultSet.TYPE_FORWARD_ONLY:
+                default:
+                    this.currResults = new TripleIteratorResults(this, qe, qe.execConstructTriples(), needsCommit);
+                    break;
+                }
             } else {
                 throw new SQLException("Unknown SPARQL Query type");
             }
+
+            // Can immediately commit when type is
+            // TYPE_SCROLL_INSENSITIVE and auto-committing since we have
+            // already materialized results so don't need the read
+            // transaction
+            if (this.type == ResultSet.TYPE_SCROLL_INSENSITIVE && needsCommit) {
+                LOGGER.info("Auto-committing query transaction since results have been materialized");
+                this.commitTransaction();
+            }
+
+            return true;
         } catch (SQLException e) {
+            if (needsCommit) {
+                // When auto-committing and query fails roll back immediately
+                LOGGER.warn("Rolling back failed query transaction");
+                this.rollbackTransaction();
+            }
             throw e;
-        } catch (Exception e) {
+        } catch (Throwable e) {
             LOGGER.error("SPARQL Query evaluation failed", e);
+            if (needsCommit) {
+                // When auto-committing and query fails roll back immediately
+                LOGGER.warn("Rolling back failed query transaction");
+                this.rollbackTransaction();
+            }
             throw new SQLException("Error occurred during SPARQL query evaluation", e);
         }
     }
@@ -317,7 +376,6 @@ public abstract class JenaStatement impl
                 .hasActiveTransaction());
         boolean needsCommit = (this.autoCommit && this.transactionLevel != Connection.TRANSACTION_NONE);
 
-        boolean commit = false;
         try {
             // Start a Transaction if necessary
             if (needsCommit || needsBegin) {
@@ -328,30 +386,39 @@ public abstract class JenaStatement impl
                 }
                 this.beginTransaction(ReadWrite.WRITE);
             }
-            
+        } catch (Exception e) {
+            LOGGER.error("Starting the new transaction failed", e);
+            throw new SQLException("Failed to start a new query transaction", e);
+        }
+
+        try {
             // Pre-process the update
             u = this.connection.applyPreProcessors(u);
 
             // Execute updates
             UpdateProcessor processor = this.createUpdateProcessor(u);
             processor.execute();
-            commit = true;
+
+            // If auto-committing can commit immediately
+            if (needsCommit) {
+                LOGGER.info("Auto-committing update transaction");
+                this.commitTransaction();
+            }
+
             return 0;
         } catch (SQLException e) {
+            if (needsCommit) {
+                LOGGER.warn("Rolling back failed update transaction");
+                this.rollbackTransaction();
+            }
             throw e;
         } catch (Exception e) {
             LOGGER.error("SPARQL Update evaluation failed", e);
-            throw new SQLException("Error occurred during SPARQL update evaluation", e);
-        } finally {
             if (needsCommit) {
-                if (commit) {
-                    LOGGER.info("Committing update transaction");
-                    this.commitTransaction();
-                } else {
-                    LOGGER.warn("Rolling back failed update transaction");
-                    this.rollbackTransaction();
-                }
+                LOGGER.warn("Rolling back failed update transaction");
+                this.rollbackTransaction();
             }
+            throw new SQLException("Error occurred during SPARQL update evaluation", e);
         }
     }
 
@@ -375,19 +442,17 @@ public abstract class JenaStatement impl
 
     @Override
     public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
-        throw new SQLFeatureNotSupportedException("Use the single argument form of execute()");
+        return this.execute(sql);
     }
 
     @Override
     public boolean execute(String sql, int[] columnIndexes) throws SQLException {
-        throw new SQLFeatureNotSupportedException("Use the single argument form of execute()");
+        return this.execute(sql);
     }
 
     @Override
     public boolean execute(String sql, String[] columnNames) throws SQLException {
-        // TODO: Assuming a SELECT * or DESCRIBE * could replace the * with the
-        // specific column names
-        throw new SQLFeatureNotSupportedException("Use the single argument form of execute()");
+        return this.execute(sql);
     }
 
     @Override
@@ -395,6 +460,12 @@ public abstract class JenaStatement impl
         if (this.isClosed())
             throw new SQLException("The Statement is closed");
 
+        // Issue warning where appropriate
+        if (this.commands.size() > 1 && this.autoCommit && this.holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT) {
+            this.setWarning("Executing this batch of commands may lead to unexpectedly closed result sets because auto-commit is enabled and commit behaviour is set to close cursors at commit");
+        }
+
+        // Go ahead and process the batch
         int[] rets = new int[this.commands.size()];
         ResultSet curr = this.currResults;
         for (int i = 0; i < this.commands.size(); i++) {
@@ -402,8 +473,11 @@ public abstract class JenaStatement impl
                 // True means it returned a ResultSet
                 this.results.add(this.getResultSet());
                 this.currResults = null;
-                rets[i] = 0;
+                rets[i] = SUCCESS_NO_INFO;
             } else {
+                // Need to add a null to getMoreResults() to produce correct
+                // behavior across subsequent calls to getMoreResults()
+                this.results.add(null);
                 rets[i] = this.getUpdateCount();
             }
         }
@@ -420,7 +494,7 @@ public abstract class JenaStatement impl
     public final ResultSet executeQuery(String sql) throws SQLException {
         if (this.isClosed())
             throw new SQLException("The Statement is closed");
-        
+
         // Pre-process the command text
         LOGGER.info("Received input command text:\n {}", sql);
         sql = this.connection.applyPreProcessors(sql);
@@ -449,12 +523,12 @@ public abstract class JenaStatement impl
             throw new SQLException("The Statement is closed");
         if (this.connection.isReadOnly())
             throw new SQLException("The JDBC connection is currently in read-only mode, updates are not permitted");
-        
+
         // Pre-process the command text
         LOGGER.info("Received input command text:\n {}", sql);
         sql = this.connection.applyPreProcessors(sql);
         LOGGER.info("Command text after pre-processing:\n {}", sql);
-        
+
         UpdateRequest u = null;
         try {
             u = UpdateFactory.create(sql);

Modified: jena/Experimental/jena-jdbc/jena-jdbc-core/src/test/java/org/apache/jena/jdbc/connections/AbstractJenaConnectionTests.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-core/src/test/java/org/apache/jena/jdbc/connections/AbstractJenaConnectionTests.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-core/src/test/java/org/apache/jena/jdbc/connections/AbstractJenaConnectionTests.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-core/src/test/java/org/apache/jena/jdbc/connections/AbstractJenaConnectionTests.java Wed May  1 21:05:13 2013
@@ -17,7 +17,11 @@
  */
 package org.apache.jena.jdbc.connections;
 
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.sql.Connection;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
@@ -26,6 +30,7 @@ import java.sql.Types;
 
 import org.apache.jena.jdbc.JdbcCompatibility;
 import org.apache.jena.jdbc.connections.JenaConnection;
+import org.apache.jena.jdbc.results.metadata.AskResultsMetadata;
 import org.apache.jena.jdbc.results.metadata.TripleResultsMetadata;
 import org.apache.jena.jdbc.utils.TestUtils;
 import org.apache.log4j.BasicConfigurator;
@@ -77,6 +82,33 @@ public abstract class AbstractJenaConnec
     protected abstract JenaConnection getConnection(Dataset ds) throws SQLException;
 
     /**
+     * Method which indicates whether a named graph is being used as the default
+     * graph since some tests need to know this in order to adjust SPARQL
+     * Updates issued appropriately
+     * <p>
+     * By default assumed to be false, override if you need to make it true for
+     * your connection
+     * </p>
+     * 
+     * @return
+     */
+    protected boolean usesNamedGraphAsDefault() {
+        return false;
+    }
+
+    /**
+     * Method which returns the name of the default graph when a named graph is
+     * being used as the default graph
+     * 
+     * @return Named Graph being used as the default graph
+     * @throws SQLException Thrown if this feature is not being used
+     */
+    protected String getDefaultGraphName() throws SQLException {
+        throw new SQLException(
+                "Named Default Graph not used by these tests, please override getDefaultGraphName() if your connection uses this feature");
+    }
+
+    /**
      * Create and close a connection to an empty database
      * 
      * @throws SQLException
@@ -320,6 +352,153 @@ public abstract class AbstractJenaConnec
     }
 
     /**
+     * Tests use of prepared statements
+     * 
+     * @throws SQLException
+     * @throws MalformedURLException
+     */
+    @Test
+    public void connection_prepared_statement_select_01() throws SQLException, MalformedURLException {
+        // Prepare a dataset
+        Dataset ds = DatasetFactory.createMem();
+        ds.asDatasetGraph().add(
+                new Quad(NodeFactory.createURI("http://example/graph"), NodeFactory.createURI("http://example/subject"),
+                        NodeFactory.createURI("http://example/predicate"), NodeFactory.createURI("http://example/object")));
+
+        // Work with the connection
+        JenaConnection conn = this.getConnection(ds);
+        conn.setJdbcCompatibilityLevel(JdbcCompatibility.HIGH);
+        PreparedStatement stmt = conn.prepareStatement("SELECT * WHERE { GRAPH ? { ?s ?p ?o } }");
+        ParameterMetaData metadata = stmt.getParameterMetaData();
+        Assert.assertEquals(1, metadata.getParameterCount());
+        stmt.setURL(1, new URL("http://example/graph"));
+
+        ResultSet rset = stmt.executeQuery();
+        Assert.assertNotNull(rset);
+        Assert.assertFalse(rset.isClosed());
+        Assert.assertTrue(rset.isBeforeFirst());
+
+        // Check result set metadata
+        checkSelectMetadata(rset, 3);
+
+        // Should have a row
+        Assert.assertTrue(rset.next());
+        Assert.assertTrue(rset.isFirst());
+        Assert.assertEquals(1, rset.getRow());
+
+        // Should be no further rows
+        Assert.assertFalse(rset.next());
+        Assert.assertTrue(rset.isAfterLast());
+        Assert.assertFalse(rset.isClosed());
+
+        // Close things
+        rset.close();
+        Assert.assertTrue(rset.isClosed());
+        stmt.close();
+        Assert.assertTrue(stmt.isClosed());
+        conn.close();
+        Assert.assertTrue(conn.isClosed());
+    }
+
+    /**
+     * Tests use of prepared statements
+     * 
+     * @throws SQLException
+     * @throws MalformedURLException
+     */
+    @Test
+    public void connection_prepared_statement_select_02() throws SQLException, MalformedURLException {
+        // Prepare a dataset
+        Dataset ds = DatasetFactory.createMem();
+        ds.asDatasetGraph().add(
+                new Quad(NodeFactory.createURI("http://example/graph"), NodeFactory.createURI("http://example/subject"),
+                        NodeFactory.createURI("http://example/predicate"), NodeFactory.createLiteral("value")));
+
+        // Work with the connection
+        JenaConnection conn = this.getConnection(ds);
+        conn.setJdbcCompatibilityLevel(JdbcCompatibility.HIGH);
+        PreparedStatement stmt = conn.prepareStatement("SELECT * WHERE { GRAPH ?g { ?s ?p ? } }");
+        ParameterMetaData metadata = stmt.getParameterMetaData();
+        Assert.assertEquals(1, metadata.getParameterCount());
+        stmt.setString(1, "value");
+
+        ResultSet rset = stmt.executeQuery();
+        Assert.assertNotNull(rset);
+        Assert.assertFalse(rset.isClosed());
+        Assert.assertTrue(rset.isBeforeFirst());
+
+        // Check result set metadata
+        checkSelectMetadata(rset, 3);
+
+        // Should have a row
+        Assert.assertTrue(rset.next());
+        Assert.assertTrue(rset.isFirst());
+        Assert.assertEquals(1, rset.getRow());
+
+        // Should be no further rows
+        Assert.assertFalse(rset.next());
+        Assert.assertTrue(rset.isAfterLast());
+        Assert.assertFalse(rset.isClosed());
+
+        // Close things
+        rset.close();
+        Assert.assertTrue(rset.isClosed());
+        stmt.close();
+        Assert.assertTrue(stmt.isClosed());
+        conn.close();
+        Assert.assertTrue(conn.isClosed());
+    }
+
+    /**
+     * Tests use of prepared statements
+     * 
+     * @throws SQLException
+     * @throws MalformedURLException
+     */
+    @Test
+    public void connection_prepared_statement_select_03() throws SQLException, MalformedURLException {
+        // Prepare a dataset
+        Dataset ds = DatasetFactory.createMem();
+        ds.asDatasetGraph().add(
+                new Quad(NodeFactory.createURI("http://example/graph"), NodeFactory.createURI("http://example/subject"),
+                        NodeFactory.createURI("http://example/predicate"), NodeFactory.createLiteral("value")));
+
+        // Work with the connection
+        JenaConnection conn = this.getConnection(ds);
+        conn.setJdbcCompatibilityLevel(JdbcCompatibility.HIGH);
+        PreparedStatement stmt = conn.prepareStatement("SELECT * WHERE { GRAPH ?g { ?s ?p ? } }");
+        ParameterMetaData metadata = stmt.getParameterMetaData();
+        Assert.assertEquals(1, metadata.getParameterCount());
+        stmt.setNString(1, "value");
+
+        ResultSet rset = stmt.executeQuery();
+        Assert.assertNotNull(rset);
+        Assert.assertFalse(rset.isClosed());
+        Assert.assertTrue(rset.isBeforeFirst());
+
+        // Check result set metadata
+        checkSelectMetadata(rset, 3);
+
+        // Should have a row
+        Assert.assertTrue(rset.next());
+        Assert.assertTrue(rset.isFirst());
+        Assert.assertEquals(1, rset.getRow());
+
+        // Should be no further rows
+        Assert.assertFalse(rset.next());
+        Assert.assertTrue(rset.isAfterLast());
+        Assert.assertFalse(rset.isClosed());
+
+        // Close things
+        rset.close();
+        Assert.assertTrue(rset.isClosed());
+        stmt.close();
+        Assert.assertTrue(stmt.isClosed());
+        conn.close();
+        Assert.assertTrue(conn.isClosed());
+    }
+
+    /**
      * Runs a SELECT query on a non-empty database with max rows set and checks
      * that the appropriate number of rows are returned
      * 
@@ -794,4 +973,183 @@ public abstract class AbstractJenaConnec
         conn.close();
         Assert.assertTrue(conn.isClosed());
     }
+
+    /**
+     * Runs a batch of operations and checks the results results
+     * 
+     * @throws SQLException
+     */
+    @Test
+    public void connection_statement_batch_01() throws SQLException {
+        JenaConnection conn = this.getConnection();
+        conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
+        Statement stmt = conn.createStatement();
+
+        // Create and execute a batch
+        stmt.addBatch("SELECT * WHERE { ?s ?p ?o }");
+        int[] batchResults = stmt.executeBatch();
+        Assert.assertEquals(1, batchResults.length);
+
+        // Expect first result set returned to be SELECT results
+        Assert.assertEquals(batchResults[0], Statement.SUCCESS_NO_INFO);
+        ResultSet rset = stmt.getResultSet();
+        Assert.assertNotNull(rset);
+
+        // Check result set metadata
+        checkSelectMetadata(rset, 3);
+
+        // Check result set
+        Assert.assertFalse(rset.isClosed());
+        Assert.assertTrue(rset.isBeforeFirst());
+        Assert.assertFalse(rset.next());
+        Assert.assertTrue(rset.isAfterLast());
+        Assert.assertFalse(stmt.isClosed());
+
+        // Expect there to be no further results
+        Assert.assertFalse(stmt.getMoreResults());
+        Assert.assertTrue(rset.isClosed()); // Should cause previous result set
+                                            // to be closed
+
+        // Close things
+        rset.close();
+        Assert.assertTrue(rset.isClosed());
+        stmt.close();
+        Assert.assertTrue(stmt.isClosed());
+        conn.close();
+        Assert.assertTrue(conn.isClosed());
+    }
+
+    /**
+     * Runs a batch of operations and checks the results results
+     * 
+     * @throws SQLException
+     */
+    @Test
+    public void connection_statement_batch_02() throws SQLException {
+        JenaConnection conn = this.getConnection();
+        conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
+        Statement stmt = conn.createStatement();
+
+        // Create and execute a batch
+        stmt.addBatch("SELECT * WHERE { ?s ?p ?o }");
+        stmt.addBatch("ASK WHERE { }");
+        int[] batchResults = stmt.executeBatch();
+        Assert.assertEquals(2, batchResults.length);
+
+        // Expect first result set returned to be SELECT results
+        Assert.assertEquals(batchResults[0], Statement.SUCCESS_NO_INFO);
+        ResultSet rset = stmt.getResultSet();
+        Assert.assertNotNull(rset);
+
+        // Check result set metadata
+        checkSelectMetadata(rset, 3);
+
+        // Check result set
+        Assert.assertFalse(rset.isClosed());
+        Assert.assertTrue(rset.isBeforeFirst());
+        Assert.assertFalse(rset.next());
+        Assert.assertTrue(rset.isAfterLast());
+        Assert.assertFalse(stmt.isClosed());
+
+        // Expect there to be a further ASK result set
+        Assert.assertEquals(batchResults[1], Statement.SUCCESS_NO_INFO);
+        Assert.assertTrue(stmt.getMoreResults());
+        Assert.assertTrue(rset.isClosed()); // Should close the previous result
+                                            // set
+        rset = stmt.getResultSet();
+        Assert.assertNotNull(rset);
+
+        // Check result set metadata
+        checkAskMetadata(rset);
+
+        // Check result set
+        Assert.assertFalse(rset.isClosed());
+        Assert.assertTrue(rset.isBeforeFirst());
+        Assert.assertTrue(rset.next());
+        Assert.assertTrue(rset.getBoolean(AskResultsMetadata.COLUMN_LABEL_ASK));
+        Assert.assertFalse(rset.next());
+        Assert.assertTrue(rset.isAfterLast());
+
+        // Close things
+        rset.close();
+        Assert.assertTrue(rset.isClosed());
+        stmt.close();
+        Assert.assertTrue(stmt.isClosed());
+        conn.close();
+        Assert.assertTrue(conn.isClosed());
+    }
+
+    /**
+     * Runs a batch of operations and checks the results results
+     * 
+     * @throws SQLException
+     */
+    @Test
+    public void connection_statement_batch_03() throws SQLException {
+        JenaConnection conn = this.getConnection();
+        conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
+
+        // This test needs scroll insensitive result sets as otherwise the first
+        // SELECT may see data from the update if the underlying connection does
+        // not have suitable transaction isolation
+        Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
+
+        // Create and execute a batch
+        stmt.addBatch("SELECT * WHERE { ?s ?p ?o }");
+        if (this.usesNamedGraphAsDefault()) {
+            stmt.addBatch("INSERT DATA { GRAPH <" + this.getDefaultGraphName() + "> { <http://x> <http://y> <http://z> } }");
+        } else {
+            stmt.addBatch("INSERT DATA { <http://x> <http://y> <http://z> }");
+        }
+        stmt.addBatch("SELECT * WHERE { ?s ?p ?o }");
+        int[] batchResults = stmt.executeBatch();
+        Assert.assertEquals(3, batchResults.length);
+
+        // Expect first result set returned to be SELECT results
+        Assert.assertEquals(batchResults[0], Statement.SUCCESS_NO_INFO);
+        ResultSet rset = stmt.getResultSet();
+        Assert.assertNotNull(rset);
+
+        // Check result set metadata
+        checkSelectMetadata(rset, 3);
+
+        // Check result set
+        Assert.assertFalse(rset.isClosed());
+        Assert.assertTrue(rset.isBeforeFirst());
+        Assert.assertFalse(rset.next());
+        Assert.assertTrue(rset.isAfterLast());
+        Assert.assertFalse(stmt.isClosed());
+
+        // Next results should be for the update so should return null for the
+        // result set
+        Assert.assertTrue(batchResults[1] >= 0);
+        Assert.assertTrue(stmt.getMoreResults());
+        rset = stmt.getResultSet();
+        Assert.assertNull(rset);
+
+        // Expect there to be a further SELECT result set
+        Assert.assertEquals(batchResults[2], Statement.SUCCESS_NO_INFO);
+        Assert.assertTrue(stmt.getMoreResults());
+        rset = stmt.getResultSet();
+        Assert.assertNotNull(rset);
+
+        // Check result set metadata
+        checkSelectMetadata(rset, 3);
+
+        // Check result set
+        Assert.assertFalse(rset.isClosed());
+        Assert.assertTrue(rset.isBeforeFirst());
+        Assert.assertTrue(rset.next()); // Should now be a row because previous
+                                        // command in batch inserted data
+        Assert.assertFalse(rset.next());
+        Assert.assertTrue(rset.isAfterLast());
+
+        // Close things
+        rset.close();
+        Assert.assertTrue(rset.isClosed());
+        stmt.close();
+        Assert.assertTrue(stmt.isClosed());
+        conn.close();
+        Assert.assertTrue(conn.isClosed());
+    }
 }

Modified: jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/connections/RemoteEndpointConnection.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/connections/RemoteEndpointConnection.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/connections/RemoteEndpointConnection.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/connections/RemoteEndpointConnection.java Wed May  1 21:05:13 2013
@@ -20,7 +20,6 @@ package org.apache.jena.jdbc.remote.conn
 
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
-import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
@@ -178,22 +177,22 @@ public class RemoteEndpointConnection ex
             throws SQLException {
         if (this.isClosed())
             throw new SQLException("Cannot create a statement after the connection was closed");
-        if (resultSetType != ResultSet.TYPE_FORWARD_ONLY)
-            throw new SQLException("Remote endpoint backed connection currently only support forward-only result sets");
+        if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE)
+            throw new SQLException("Remote endpoint backed connection do not support scroll sensitive result sets");
         if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY)
             throw new SQLException("Remote endpoint backed connections only support read-only result sets");
-        return new RemoteEndpointStatement(this, this.username, this.password, ResultSet.FETCH_FORWARD, 0, resultSetHoldability);
+        return new RemoteEndpointStatement(this, this.username, this.password, resultSetType, ResultSet.FETCH_FORWARD, 0, resultSetHoldability);
     }
     
     @Override
     protected JenaPreparedStatement createPreparedStatementInternal(String sparql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
         if (this.isClosed())
             throw new SQLException("Cannot create a statement after the connection was closed");
-        if (resultSetType != ResultSet.TYPE_FORWARD_ONLY)
-            throw new SQLException("Remote endpoint backed connection currently only support forward-only result sets");
+        if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE)
+            throw new SQLException("Remote endpoint backed connection do not support scroll sensitive result sets");
         if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY)
             throw new SQLException("Remote endpoint backed connections only support read-only result sets");
-        return new RemoteEndpointPreparedStatement(sparql, this, this.username, this.password, ResultSet.FETCH_FORWARD, 0, resultSetHoldability);
+        return new RemoteEndpointPreparedStatement(sparql, this, this.username, this.password, resultSetType, ResultSet.FETCH_FORWARD, 0, resultSetHoldability);
     }
 
     @Override
@@ -212,37 +211,6 @@ public class RemoteEndpointConnection ex
     }
 
     @Override
-    public PreparedStatement prepareStatement(String sql) throws SQLException {
-        throw new SQLFeatureNotSupportedException("Prepared statements are not yet supported for Jena JDBC");
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
-        throw new SQLFeatureNotSupportedException("Prepared statements are not yet supported for Jena JDBC");
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
-        throw new SQLFeatureNotSupportedException("Prepared statements are not yet supported for Jena JDBC");
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
-        throw new SQLFeatureNotSupportedException("Prepared statements are not yet supported for Jena JDBC");
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
-        throw new SQLFeatureNotSupportedException("Prepared statements are not yet supported for Jena JDBC");
-    }
-
-    @Override
-    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
-            throws SQLException {
-        throw new SQLFeatureNotSupportedException("Prepared statements are not yet supported for Jena JDBC");
-    }
-
-    @Override
     public void setReadOnly(boolean readOnly) throws SQLException {
         if (this.isClosed())
             throw new SQLException("Cannot set read-only mode on a closed connection");

Modified: jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/statements/RemoteEndpointPreparedStatement.java
URL: http://svn.apache.org/viewvc/jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/statements/RemoteEndpointPreparedStatement.java?rev=1478189&r1=1478188&r2=1478189&view=diff
==============================================================================
--- jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/statements/RemoteEndpointPreparedStatement.java (original)
+++ jena/Experimental/jena-jdbc/jena-jdbc-driver-remote/src/main/java/org/apache/jena/jdbc/remote/statements/RemoteEndpointPreparedStatement.java Wed May  1 21:05:13 2013
@@ -55,20 +55,22 @@ public class RemoteEndpointPreparedState
      *             Thrown if there is an error with the statement parameters
      */
     public RemoteEndpointPreparedStatement(String sparql, RemoteEndpointConnection connection) throws SQLException {
-        this(sparql, connection, null, null, DEFAULT_FETCH_DIRECTION, DEFAULT_FETCH_SIZE, DEFAULT_HOLDABILITY);
+        this(sparql, connection, null, null, DEFAULT_TYPE, DEFAULT_FETCH_DIRECTION, DEFAULT_FETCH_SIZE, DEFAULT_HOLDABILITY);
     }
 
     /**
      * Creates a new statement
      * 
      * @param sparql
-     *  SPARQL command
+     *            SPARQL command
      * @param connection
      *            Connection
      * @param username
      *            Username for HTTP Basic Authentication
      * @param password
      *            Username for HTTP Basic Authentication
+     * @param type
+     *            Result Set type for result sets produced by this statement
      * @param fetchDir
      *            Fetch Direction
      * @param fetchSize
@@ -79,9 +81,9 @@ public class RemoteEndpointPreparedState
      *             Thrown if there is an error with the statement parameters
      * 
      */
-    public RemoteEndpointPreparedStatement(String sparql, RemoteEndpointConnection connection, String username, char[] password, int fetchDir,
-            int fetchSize, int holdability) throws SQLException {
-        super(sparql, connection, fetchDir, fetchSize, holdability, false, Connection.TRANSACTION_NONE);
+    public RemoteEndpointPreparedStatement(String sparql, RemoteEndpointConnection connection, String username, char[] password,
+            int type, int fetchDir, int fetchSize, int holdability) throws SQLException {
+        super(sparql, connection, type, fetchDir, fetchSize, holdability, false, Connection.TRANSACTION_NONE);
         this.remoteConn = connection;
         this.username = username;
         this.password = password;