diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/KeepResultSetTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/KeepResultSetTests.java new file mode 100644 index 0000000000..91afafa311 --- /dev/null +++ b/jOOQ-test/src/org/jooq/test/_/testcases/KeepResultSetTests.java @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2009-2013, Lukas Eder, lukas.eder@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.test._.testcases; + +import static java.util.Arrays.asList; +import static org.jooq.KeepResultSetMode.CLOSE_AFTER_FETCH; +import static org.jooq.KeepResultSetMode.KEEP_AFTER_FETCH; +import static org.jooq.KeepResultSetMode.UPDATE_ON_CHANGE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.jooq.Cursor; +import org.jooq.Record1; +import org.jooq.Record2; +import org.jooq.Record3; +import org.jooq.Record6; +import org.jooq.Result; +import org.jooq.TableRecord; +import org.jooq.UpdatableRecord; +import org.jooq.test.BaseTest; +import org.jooq.test.jOOQAbstractTest; + +import org.junit.Test; + +public class KeepResultSetTests< + A extends UpdatableRecord & Record6, + AP, + B extends UpdatableRecord, + S extends UpdatableRecord & Record1, + B2S extends UpdatableRecord & Record3, + BS extends UpdatableRecord, + L extends TableRecord & Record2, + X extends TableRecord, + DATE extends UpdatableRecord, + BOOL extends UpdatableRecord, + D extends UpdatableRecord, + T extends UpdatableRecord, + U extends TableRecord, + UU extends UpdatableRecord, + I extends TableRecord, + IPK extends UpdatableRecord, + T725 extends UpdatableRecord, + T639 extends UpdatableRecord, + T785 extends TableRecord> +extends BaseTest { + + public KeepResultSetTests(jOOQAbstractTest delegate) { + super(delegate); + } + + private void testFailUpdateRow(ResultSet rs) { + try { + rs.updateRow(); + fail(); + } + catch (SQLException expected) {} + + } + + @Test + public void testKeepRSWithCloseAfterFetch() throws Exception { + Result b1 = create().selectFrom(TBook()).fetch(); + assertNull(b1.resultSet()); + + Result b2 = create().selectFrom(TBook()).keepResultSet(CLOSE_AFTER_FETCH).fetch(); + assertNull(b2.resultSet()); + + Cursor c1 = create().selectFrom(TBook()).keepResultSet(CLOSE_AFTER_FETCH).fetchLazy(); + while (c1.hasNext()) { + Result result = c1.fetch(1); + assertNull(result.resultSet()); + assertNotNull(c1.resultSet()); + } + + assertNull(c1.resultSet()); + } + + @Test + public void testKeepRSWithKeepAfterFetch() throws Exception { + Result b2 = create().selectFrom(TBook()).keepResultSet(KEEP_AFTER_FETCH).fetch(); + assertNotNull(b2.resultSet()); + testFailUpdateRow(b2.resultSet()); + b2.close(); + assertNull(b2.resultSet()); + + Cursor c1 = create().selectFrom(TBook()).keepResultSet(KEEP_AFTER_FETCH).fetchLazy(); + while (c1.hasNext()) { + Result result = c1.fetch(1); + assertNotNull(result.resultSet()); + assertNotNull(c1.resultSet()); + } + + assertNotNull(c1.resultSet()); + c1.close(); + assertNull(c1.resultSet()); + } + + @Test + public void testKeepRSWithUpdateOnChange() throws Exception { + jOOQAbstractTest.reset = false; + + Result books = + create().selectFrom(TBook()) + .orderBy(TBook_ID()) + .keepResultSet(UPDATE_ON_CHANGE) + .fetch(); + + assertNotNull(books.resultSet()); + for (int i = 0; i < books.size(); i++) { + books.get(i).setValue(TBook_TITLE(), "Title " + i); + } + + Result booksTest = getBooks(); + assertEquals( + asList("Title 0", "Title 1", "Title 2", "Title 3"), + booksTest.getValues(TBook_TITLE())); + + // After closing, setting values to records should no longer have any + // effect + books.close(); + assertNull(books.resultSet()); + books.get(0).setValue(TBook_TITLE(), "XX"); + assertEquals("Title 0", getBook(1).getValue(TBook_TITLE())); + } +} diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 4061126b1a..5256ed3bb4 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -121,6 +121,7 @@ import org.jooq.test._.testcases.GroupByTests; import org.jooq.test._.testcases.InsertUpdateTests; import org.jooq.test._.testcases.JDBCTests; import org.jooq.test._.testcases.JoinTests; +import org.jooq.test._.testcases.KeepResultSetTests; import org.jooq.test._.testcases.LoaderTests; import org.jooq.test._.testcases.MetaDataTests; import org.jooq.test._.testcases.OrderByTests; @@ -2214,6 +2215,21 @@ public abstract class jOOQAbstractTest< new BenchmarkTests(this).testBenchmarkSelect(); } + @Test + public void testKeepRSWithCloseAfterFetch() throws Exception { + new KeepResultSetTests(this).testKeepRSWithCloseAfterFetch(); + } + + @Test + public void testKeepRSWithKeepAfterFetch() throws Exception { + new KeepResultSetTests(this).testKeepRSWithKeepAfterFetch(); + } + + @Test + public void testKeepRSWithUpdateOnChange() throws Exception { + new KeepResultSetTests(this).testKeepRSWithUpdateOnChange(); + } + @Test public void testKeepStatement() throws Exception { new StatementTests(this).testKeepStatement(); diff --git a/jOOQ/src/main/java/org/jooq/KeepResultSetMode.java b/jOOQ/src/main/java/org/jooq/KeepResultSetMode.java new file mode 100644 index 0000000000..3230e116de --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/KeepResultSetMode.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2009-2013, Lukas Eder, lukas.eder@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq; + +import java.sql.ResultSet; + +/** + * A {@link ResultQuery}'s execution mode with respect to keeping open JDBC + * {@link ResultSet} references after fetching. + *

+ * This mode type is used with + * {@link ResultQuery#keepResultSet(KeepResultSetMode)} to indicate how to deal + * with JDBC {@link ResultSet} references after fetching them into jOOQ + * {@link Result} objects. + *

+ * See the various modes for details. + * + * @author Lukas Eder + */ +public enum KeepResultSetMode { + + /** + * Close the JDBC {@link ResultSet} after consuming it (this is the + * default). + */ + CLOSE_AFTER_FETCH, + + /** + * Keep the JDBC {@link ResultSet} after consuming it. + *

+ * Client code must assure that the {@link ResultSet} is closed explicitly + * to free database resources. Closing can be done through any of these + * methods: + *

    + *
  • {@link ResultSet#close()}
  • + *
  • {@link Result#close()}
  • + *
  • {@link Cursor#close()}
  • + *
+ */ + KEEP_AFTER_FETCH, + + /** + * Keep the JDBC {@link ResultSet} after consuming it, updating the + * ResultSet at every change of a {@link Record}. + *

+ * TODO: More details here + *

+ * Client code must assure that the {@link ResultSet} is closed explicitly + * to free database resources. Closing can be done through any of these + * methods: + *

    + *
  • {@link ResultSet#close()}
  • + *
  • {@link Result#close()}
  • + *
  • {@link Cursor#close()}
  • + *
+ */ + UPDATE_ON_CHANGE, + + /** + * Keep the JDBC {@link ResultSet} after consuming it, updating the + * ResultSet at every call to {@link Record#store()}, or + * {@link Result#store()} (This is not yet supported). + *

+ * TODO: More details here + *

+ * Client code must assure that the {@link ResultSet} is closed explicitly + * to free database resources. Closing can be done through any of these + * methods: + *

    + *
  • {@link ResultSet#close()}
  • + *
  • {@link Result#close()}
  • + *
  • {@link Cursor#close()}
  • + *
+ */ + UPDATE_ON_STORE +} diff --git a/jOOQ/src/main/java/org/jooq/ResultQuery.java b/jOOQ/src/main/java/org/jooq/ResultQuery.java index 64a3e134fb..5164db2b9f 100644 --- a/jOOQ/src/main/java/org/jooq/ResultQuery.java +++ b/jOOQ/src/main/java/org/jooq/ResultQuery.java @@ -989,6 +989,18 @@ public interface ResultQuery extends Query { @Override ResultQuery keepStatement(boolean keepStatement); + /** + * Indicate how to deal with the JDBC {@link ResultSet} when fetching data + * into jOOQ {@link Result} or {@link Cursor} objects. + *

+ * TODO: More info here. + *

+ * Note: If JDBC ResultSet references are kept + * open after fetching data through jOOQ, you must explicitly close them + * using either {@link Result#close()}, or {@link Cursor#close()} + */ + ResultQuery keepResultSet(KeepResultSetMode mode); + /** * Specify the maximum number of rows returned by the underlying * {@link Statement}. diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java index 419ceb1579..d2c2133167 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractQuery.java @@ -306,7 +306,7 @@ abstract class AbstractQuery extends AbstractQueryPart implements Query, Attacha finally { // [#2385] Successful fetchLazy() needs to keep open resources - if (!keepResult() || ctx.exception() != null) { + if (!keepResultSet() || ctx.exception() != null) { Utils.safeClose(listener, ctx, keepStatement()); } @@ -329,7 +329,7 @@ abstract class AbstractQuery extends AbstractQueryPart implements Query, Attacha * Default implementation to indicate whether this query should close the * {@link ResultSet} after execution. Subclasses may override this method. */ - protected boolean keepResult() { + protected boolean keepResultSet() { return false; } diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java index 44d6d30d12..e40e08e22b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java @@ -42,9 +42,11 @@ import static org.jooq.impl.Utils.getAnnotatedMembers; import static org.jooq.impl.Utils.getMatchingGetter; import static org.jooq.impl.Utils.getMatchingMembers; import static org.jooq.impl.Utils.hasColumnAnnotations; +import static org.jooq.impl.Utils.translate; import java.lang.reflect.Method; import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; @@ -78,6 +80,8 @@ abstract class AbstractRecord extends AbstractStore implements Record { final RowImpl fields; final Value[] values; + transient ResultSet rs; + transient int rsIndex; AbstractRecord(Collection> fields) { this(new RowImpl(fields)); @@ -289,6 +293,23 @@ abstract class AbstractRecord extends AbstractStore implements Record { changed(true); } } + + if (rs != null) { + try { + if (rs.getRow() != rsIndex) { + rs.absolute(rsIndex); + } + + // [#1846] TODO: Add more typesafety here + rs.updateObject(fieldsRow().indexOf(field) + 1, value); + + // [#1846] TODO: Update only in case of KeepResultSetMode.UPDATE_ON_CHANGE + rs.updateRow(); + } + catch (SQLException e) { + throw translate("Error when updating ResultSet", e); + } + } } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java index 994e11f421..fab8b3addc 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java @@ -39,6 +39,9 @@ import static java.sql.ResultSet.CONCUR_UPDATABLE; import static java.sql.ResultSet.TYPE_SCROLL_SENSITIVE; import static java.util.Arrays.asList; import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.jooq.KeepResultSetMode.CLOSE_AFTER_FETCH; +import static org.jooq.KeepResultSetMode.UPDATE_ON_CHANGE; +import static org.jooq.KeepResultSetMode.UPDATE_ON_STORE; import static org.jooq.SQLDialect.ASE; import static org.jooq.SQLDialect.CUBRID; import static org.jooq.SQLDialect.SQLSERVER; @@ -63,6 +66,7 @@ import org.jooq.ExecuteContext; import org.jooq.ExecuteListener; import org.jooq.Field; import org.jooq.FutureResult; +import org.jooq.KeepResultSetMode; import org.jooq.Record; import org.jooq.RecordHandler; import org.jooq.RecordMapper; @@ -88,6 +92,7 @@ abstract class AbstractResultQuery extends AbstractQuery imple private static final JooqLogger log = JooqLogger.getLogger(AbstractResultQuery.class); private int maxRows; + private KeepResultSetMode keepResultSetMode; private int resultSetConcurrency; private int resultSetType; private int resultSetHoldability; @@ -136,6 +141,15 @@ abstract class AbstractResultQuery extends AbstractQuery imple return (ResultQuery) super.keepStatement(k); } + @Override + public final ResultQuery keepResultSet(KeepResultSetMode mode) { + if (mode == UPDATE_ON_STORE) + throw new UnsupportedOperationException("UPDATE_ON_STORE is not yet supported"); + + this.keepResultSetMode = mode; + return this; + } + @Override public final ResultQuery maxRows(int rows) { this.maxRows = rows; @@ -210,6 +224,12 @@ abstract class AbstractResultQuery extends AbstractQuery imple } } + // [#1846] When updatable Results are fetched + else if (keepResultSetMode == UPDATE_ON_CHANGE || + keepResultSetMode == UPDATE_ON_STORE) { + ctx.statement(ctx.connection().prepareStatement(ctx.sql(), TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE)); + } + // [#1296] These dialects do not implement FOR UPDATE. But the same // effect can be achieved using ResultSet.CONCUR_UPDATABLE else if (isForUpdate() && asList(CUBRID, SQLSERVER).contains(ctx.configuration().dialect())) { @@ -276,7 +296,7 @@ abstract class AbstractResultQuery extends AbstractQuery imple if (!many) { if (ctx.resultSet() != null) { Field[] fields = getFields(ctx.resultSet().getMetaData()); - cursor = new CursorImpl(ctx, listener, fields, internIndexes(fields), lazy, keepStatement(), getRecordType()); + cursor = new CursorImpl(ctx, listener, fields, internIndexes(fields), keepStatement(), keepResultSet(), keepResultSetMode, getRecordType()); if (!lazy) { result = cursor.fetch(); @@ -297,7 +317,7 @@ abstract class AbstractResultQuery extends AbstractQuery imple anyResults = true; Field[] fields = new MetaDataFieldProvider(ctx.configuration(), ctx.resultSet().getMetaData()).getFields(); - Cursor c = new CursorImpl(ctx, listener, fields, internIndexes(fields), false, true); + Cursor c = new CursorImpl(ctx, listener, fields, internIndexes(fields), true, false, CLOSE_AFTER_FETCH); results.add(c.fetch()); if (ctx.statement().getMoreResults()) { @@ -328,8 +348,10 @@ abstract class AbstractResultQuery extends AbstractQuery imple } @Override - protected final boolean keepResult() { - return lazy; + protected final boolean keepResultSet() { + return lazy + || keepResultSetMode == UPDATE_ON_CHANGE + || keepResultSetMode == UPDATE_ON_STORE; } /** diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractStoreQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractStoreQuery.java index ea93b08aee..ec95ba206e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractStoreQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractStoreQuery.java @@ -35,6 +35,7 @@ */ package org.jooq.impl; +import static org.jooq.KeepResultSetMode.CLOSE_AFTER_FETCH; import static org.jooq.impl.Utils.fieldArray; import static org.jooq.util.sqlite.SQLiteDSL.rowid; @@ -344,7 +345,7 @@ abstract class AbstractStoreQuery extends AbstractQuery implem ExecuteListener listener2 = new ExecuteListeners(ctx2); ctx2.resultSet(rs); - returned = new CursorImpl(ctx2, listener2, fieldArray(returning), null, true, false).fetch().into(getInto()); + returned = new CursorImpl(ctx2, listener2, fieldArray(returning), null, false, true, CLOSE_AFTER_FETCH).fetch().into(getInto()); return result; } } diff --git a/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java b/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java index 8674a1702b..07d2ceec1f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/CursorImpl.java @@ -36,6 +36,7 @@ package org.jooq.impl; import static java.lang.Boolean.TRUE; +import static org.jooq.KeepResultSetMode.CLOSE_AFTER_FETCH; import static org.jooq.impl.Utils.DATA_LOCK_ROWS_FOR_UPDATE; import java.io.InputStream; @@ -66,6 +67,7 @@ import org.jooq.Cursor; import org.jooq.ExecuteContext; import org.jooq.ExecuteListener; import org.jooq.Field; +import org.jooq.KeepResultSetMode; import org.jooq.Record; import org.jooq.RecordHandler; import org.jooq.RecordMapper; @@ -84,26 +86,29 @@ class CursorImpl implements Cursor { private final ExecuteListener listener; private final Field[] fields; private final boolean[] intern; - private final boolean keepResult; + private final boolean keepResultSet; + private final KeepResultSetMode keepResultSetMode; private final boolean keepStatement; private final Class type; private boolean isClosed; private transient CursorResultSet rs; + private transient int rsIndex; private transient Iterator iterator; @SuppressWarnings("unchecked") - CursorImpl(ExecuteContext ctx, ExecuteListener listener, Field[] fields, int[] internIndexes, boolean keepResult, boolean keepStatement) { - this(ctx, listener, fields, internIndexes, keepResult, keepStatement, (Class) RecordImpl.class); + CursorImpl(ExecuteContext ctx, ExecuteListener listener, Field[] fields, int[] internIndexes, boolean keepStatement, boolean keepResultSet, KeepResultSetMode keepResultSetMode) { + this(ctx, listener, fields, internIndexes, keepStatement, keepResultSet, keepResultSetMode, (Class) RecordImpl.class); } - CursorImpl(ExecuteContext ctx, ExecuteListener listener, Field[] fields, int[] internIndexes, boolean keepResult, boolean keepStatement, Class type) { + CursorImpl(ExecuteContext ctx, ExecuteListener listener, Field[] fields, int[] internIndexes, boolean keepStatement, boolean keepResultSet, KeepResultSetMode keepResultSetMode, Class type) { this.ctx = ctx; this.listener = (listener != null ? listener : new ExecuteListeners(ctx)); this.fields = fields; this.type = type; - this.keepResult = keepResult; this.keepStatement = keepStatement; + this.keepResultSet = keepResultSet; + this.keepResultSetMode = keepResultSetMode; this.rs = new CursorResultSet(); this.intern = new boolean[fields.length]; @@ -114,6 +119,10 @@ class CursorImpl implements Cursor { } } + final boolean closeAfterFetch() { + return keepResultSetMode == null || keepResultSetMode == CLOSE_AFTER_FETCH; + } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public final Row fieldsRow() { @@ -283,7 +292,7 @@ class CursorImpl implements Cursor { // [#1868] If this Result / Cursor was "kept" through a lazy // execution, we must assure that the ExecuteListener lifecycle is // correctly terminated. - Utils.safeClose(listener, ctx, keepStatement, keepResult); + Utils.safeClose(listener, ctx, keepStatement, keepResultSet); } @Override @@ -1258,11 +1267,13 @@ class CursorImpl implements Cursor { return result; } + @SuppressWarnings("unchecked") private final R fetchOne() { - R record = null; + AbstractRecord record = null; try { if (!isClosed && rs.next()) { + ++rsIndex; // [#1296] Force a row-lock by updating the row if the // FOR UPDATE clause is simulated @@ -1271,16 +1282,23 @@ class CursorImpl implements Cursor { rs.updateRow(); } - record = Utils.newRecord(type, fields, ctx.configuration()); + record = (AbstractRecord) Utils.newRecord(type, fields, ctx.configuration()); + + // [#1846] Add a reference to the Cursor's ResultSet if + // Updatable ResultSets are requested + if (!closeAfterFetch()) { + record.rs = rs; + record.rsIndex = rsIndex; + } ctx.record(record); listener.recordStart(ctx); for (int i = 0; i < fields.length; i++) { - setValue((AbstractRecord) record, fields[i], i); + setValue(record, fields[i], i); if (intern[i]) { - ((AbstractRecord) record).getValue0(i).intern(); + record.getValue0(i).intern(); } } @@ -1297,11 +1315,13 @@ class CursorImpl implements Cursor { // [#1868] [#2373] [#2385] This calls through to Utils.safeClose() // if necessary, lazy-terminating the ExecuteListener lifecycle if // the result is not eager-fetched. - if (record == null) { + // [#1846] When fetching updatable Results, do not close the + // Cursor's ResultSet! + if (record == null && closeAfterFetch()) { CursorImpl.this.close(); } - return record; + return (R) record; } /** diff --git a/jOOQ/src/main/java/org/jooq/impl/DSLContextImpl.java b/jOOQ/src/main/java/org/jooq/impl/DSLContextImpl.java index 533ae88a3a..927490802b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSLContextImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSLContextImpl.java @@ -36,6 +36,7 @@ package org.jooq.impl; +import static org.jooq.KeepResultSetMode.CLOSE_AFTER_FETCH; import static org.jooq.SQLDialect.ASE; import static org.jooq.SQLDialect.CUBRID; import static org.jooq.SQLDialect.DB2; @@ -561,7 +562,7 @@ class DSLContextImpl implements DSLContext, Serializable { ExecuteListener listener = new ExecuteListeners(ctx); ctx.resultSet(rs); - return new CursorImpl(ctx, listener, fields, null, true, false); + return new CursorImpl(ctx, listener, fields, null, false, true, CLOSE_AFTER_FETCH); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java index 60bfc50415..701b1f0330 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java @@ -94,12 +94,12 @@ class ResultImpl implements Result, AttachableInternal { /** * Generated UID */ - private static final long serialVersionUID = 6416154375799578362L; + private static final long serialVersionUID = 6416154375799578362L; - private Configuration configuration; - private ResultSet rs; - private final Fields fields; - private final List records; + private Configuration configuration; + private transient ResultSet rs; + private final Fields fields; + private final List records; ResultImpl(Configuration configuration, ResultSet rs, Collection> fields) { this(configuration, rs, new Fields(fields)); @@ -1198,7 +1198,7 @@ class ResultImpl implements Result, AttachableInternal { if (rs != null) { rs.close(); rs = null; - + for (Record record : this) { if (record instanceof AbstractRecord) { ((AbstractRecord) record).rs = null; diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java index b2aef9c5d7..6810537137 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java @@ -56,6 +56,7 @@ import org.jooq.ForeignKey; import org.jooq.FutureResult; import org.jooq.GroupField; import org.jooq.JoinType; +import org.jooq.KeepResultSetMode; import org.jooq.Operator; import org.jooq.Param; import org.jooq.QueryPart; @@ -918,6 +919,11 @@ class SelectImpl extends AbstractDelegatingQuery> im return getDelegate().maxRows(rows); } + @Override + public ResultQuery keepResultSet(KeepResultSetMode mode) { + return getDelegate().keepResultSet(mode); + } + @Override public final ResultQuery resultSetConcurrency(int resultSetConcurrency) { return getDelegate().resultSetConcurrency(resultSetConcurrency);