[#1846] Add ResultQuery.keepResultSet() to indicate whether to keep

references to a potentially updatable JDBC ResultSet from Result
This commit is contained in:
Lukas Eder 2013-05-10 16:57:18 +02:00
parent a580bebbfe
commit cfce76a651
12 changed files with 398 additions and 26 deletions

View File

@ -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<A> & Record6<Integer, String, String, Date, Integer, ?>,
AP,
B extends UpdatableRecord<B>,
S extends UpdatableRecord<S> & Record1<String>,
B2S extends UpdatableRecord<B2S> & Record3<String, Integer, Integer>,
BS extends UpdatableRecord<BS>,
L extends TableRecord<L> & Record2<String, String>,
X extends TableRecord<X>,
DATE extends UpdatableRecord<DATE>,
BOOL extends UpdatableRecord<BOOL>,
D extends UpdatableRecord<D>,
T extends UpdatableRecord<T>,
U extends TableRecord<U>,
UU extends UpdatableRecord<UU>,
I extends TableRecord<I>,
IPK extends UpdatableRecord<IPK>,
T725 extends UpdatableRecord<T725>,
T639 extends UpdatableRecord<T639>,
T785 extends TableRecord<T785>>
extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T725, T639, T785> {
public KeepResultSetTests(jOOQAbstractTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T725, T639, T785> delegate) {
super(delegate);
}
private void testFailUpdateRow(ResultSet rs) {
try {
rs.updateRow();
fail();
}
catch (SQLException expected) {}
}
@Test
public void testKeepRSWithCloseAfterFetch() throws Exception {
Result<B> b1 = create().selectFrom(TBook()).fetch();
assertNull(b1.resultSet());
Result<B> b2 = create().selectFrom(TBook()).keepResultSet(CLOSE_AFTER_FETCH).fetch();
assertNull(b2.resultSet());
Cursor<B> c1 = create().selectFrom(TBook()).keepResultSet(CLOSE_AFTER_FETCH).fetchLazy();
while (c1.hasNext()) {
Result<B> result = c1.fetch(1);
assertNull(result.resultSet());
assertNotNull(c1.resultSet());
}
assertNull(c1.resultSet());
}
@Test
public void testKeepRSWithKeepAfterFetch() throws Exception {
Result<B> b2 = create().selectFrom(TBook()).keepResultSet(KEEP_AFTER_FETCH).fetch();
assertNotNull(b2.resultSet());
testFailUpdateRow(b2.resultSet());
b2.close();
assertNull(b2.resultSet());
Cursor<B> c1 = create().selectFrom(TBook()).keepResultSet(KEEP_AFTER_FETCH).fetchLazy();
while (c1.hasNext()) {
Result<B> 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<B> 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<B> 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()));
}
}

View File

@ -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();

View File

@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* Client code must assure that the {@link ResultSet} is closed explicitly
* to free database resources. Closing can be done through any of these
* methods:
* <ul>
* <li>{@link ResultSet#close()}</li>
* <li>{@link Result#close()}</li>
* <li>{@link Cursor#close()}</li>
* </ul>
*/
KEEP_AFTER_FETCH,
/**
* Keep the JDBC {@link ResultSet} after consuming it, updating the
* <code>ResultSet</code> at every change of a {@link Record}.
* <p>
* TODO: More details here
* <p>
* Client code must assure that the {@link ResultSet} is closed explicitly
* to free database resources. Closing can be done through any of these
* methods:
* <ul>
* <li>{@link ResultSet#close()}</li>
* <li>{@link Result#close()}</li>
* <li>{@link Cursor#close()}</li>
* </ul>
*/
UPDATE_ON_CHANGE,
/**
* Keep the JDBC {@link ResultSet} after consuming it, updating the
* <code>ResultSet</code> at every call to {@link Record#store()}, or
* {@link Result#store()} (<strong>This is not yet supported</strong>).
* <p>
* TODO: More details here
* <p>
* Client code must assure that the {@link ResultSet} is closed explicitly
* to free database resources. Closing can be done through any of these
* methods:
* <ul>
* <li>{@link ResultSet#close()}</li>
* <li>{@link Result#close()}</li>
* <li>{@link Cursor#close()}</li>
* </ul>
*/
UPDATE_ON_STORE
}

View File

@ -989,6 +989,18 @@ public interface ResultQuery<R extends Record> extends Query {
@Override
ResultQuery<R> keepStatement(boolean keepStatement);
/**
* Indicate how to deal with the JDBC {@link ResultSet} when fetching data
* into jOOQ {@link Result} or {@link Cursor} objects.
* <p>
* TODO: More info here.
* <p>
* <strong>Note:</strong> If JDBC <code>ResultSet</code> references are kept
* open after fetching data through jOOQ, you must explicitly close them
* using either {@link Result#close()}, or {@link Cursor#close()}
*/
ResultQuery<R> keepResultSet(KeepResultSetMode mode);
/**
* Specify the maximum number of rows returned by the underlying
* {@link Statement}.

View File

@ -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;
}

View File

@ -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<? extends Field<?>> 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

View File

@ -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<R extends Record> 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<R extends Record> extends AbstractQuery imple
return (ResultQuery<R>) super.keepStatement(k);
}
@Override
public final ResultQuery<R> 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<R> maxRows(int rows) {
this.maxRows = rows;
@ -210,6 +224,12 @@ abstract class AbstractResultQuery<R extends Record> 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<R extends Record> extends AbstractQuery imple
if (!many) {
if (ctx.resultSet() != null) {
Field<?>[] fields = getFields(ctx.resultSet().getMetaData());
cursor = new CursorImpl<R>(ctx, listener, fields, internIndexes(fields), lazy, keepStatement(), getRecordType());
cursor = new CursorImpl<R>(ctx, listener, fields, internIndexes(fields), keepStatement(), keepResultSet(), keepResultSetMode, getRecordType());
if (!lazy) {
result = cursor.fetch();
@ -297,7 +317,7 @@ abstract class AbstractResultQuery<R extends Record> extends AbstractQuery imple
anyResults = true;
Field<?>[] fields = new MetaDataFieldProvider(ctx.configuration(), ctx.resultSet().getMetaData()).getFields();
Cursor<Record> c = new CursorImpl<Record>(ctx, listener, fields, internIndexes(fields), false, true);
Cursor<Record> c = new CursorImpl<Record>(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<R extends Record> extends AbstractQuery imple
}
@Override
protected final boolean keepResult() {
return lazy;
protected final boolean keepResultSet() {
return lazy
|| keepResultSetMode == UPDATE_ON_CHANGE
|| keepResultSetMode == UPDATE_ON_STORE;
}
/**

View File

@ -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<R extends Record> extends AbstractQuery implem
ExecuteListener listener2 = new ExecuteListeners(ctx2);
ctx2.resultSet(rs);
returned = new CursorImpl<R>(ctx2, listener2, fieldArray(returning), null, true, false).fetch().into(getInto());
returned = new CursorImpl<R>(ctx2, listener2, fieldArray(returning), null, false, true, CLOSE_AFTER_FETCH).fetch().into(getInto());
return result;
}
}

View File

@ -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<R extends Record> implements Cursor<R> {
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<? extends R> type;
private boolean isClosed;
private transient CursorResultSet rs;
private transient int rsIndex;
private transient Iterator<R> iterator;
@SuppressWarnings("unchecked")
CursorImpl(ExecuteContext ctx, ExecuteListener listener, Field<?>[] fields, int[] internIndexes, boolean keepResult, boolean keepStatement) {
this(ctx, listener, fields, internIndexes, keepResult, keepStatement, (Class<? extends R>) 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<? extends R>) RecordImpl.class);
}
CursorImpl(ExecuteContext ctx, ExecuteListener listener, Field<?>[] fields, int[] internIndexes, boolean keepResult, boolean keepStatement, Class<? extends R> type) {
CursorImpl(ExecuteContext ctx, ExecuteListener listener, Field<?>[] fields, int[] internIndexes, boolean keepStatement, boolean keepResultSet, KeepResultSetMode keepResultSetMode, Class<? extends R> 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<R extends Record> implements Cursor<R> {
}
}
final boolean closeAfterFetch() {
return keepResultSetMode == null || keepResultSetMode == CLOSE_AFTER_FETCH;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public final Row fieldsRow() {
@ -283,7 +292,7 @@ class CursorImpl<R extends Record> implements Cursor<R> {
// [#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<R extends Record> implements Cursor<R> {
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<R extends Record> implements Cursor<R> {
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<R extends Record> implements Cursor<R> {
// [#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;
}
/**

View File

@ -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<Record>(ctx, listener, fields, null, true, false);
return new CursorImpl<Record>(ctx, listener, fields, null, false, true, CLOSE_AFTER_FETCH);
}
@Override

View File

@ -94,12 +94,12 @@ class ResultImpl<R extends Record> implements Result<R>, 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<R> records;
private Configuration configuration;
private transient ResultSet rs;
private final Fields fields;
private final List<R> records;
ResultImpl(Configuration configuration, ResultSet rs, Collection<? extends Field<?>> fields) {
this(configuration, rs, new Fields(fields));
@ -1198,7 +1198,7 @@ class ResultImpl<R extends Record> implements Result<R>, AttachableInternal {
if (rs != null) {
rs.close();
rs = null;
for (Record record : this) {
if (record instanceof AbstractRecord) {
((AbstractRecord) record).rs = null;

View File

@ -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<R extends Record> extends AbstractDelegatingQuery<Select<R>> im
return getDelegate().maxRows(rows);
}
@Override
public ResultQuery<R> keepResultSet(KeepResultSetMode mode) {
return getDelegate().keepResultSet(mode);
}
@Override
public final ResultQuery<R> resultSetConcurrency(int resultSetConcurrency) {
return getDelegate().resultSetConcurrency(resultSetConcurrency);