[#2343] Decouple lifecycle of Configuration and ExecuteContext

This commit is contained in:
Lukas Eder 2013-03-24 12:23:46 +01:00
parent a8c35286ac
commit dfb3dbf56c
8 changed files with 251 additions and 69 deletions

View File

@ -65,12 +65,12 @@ public class PrettyPrinter extends DefaultExecuteListener {
// Create a new factory for logging rendering purposes
// This factory doesn't need a connection, only the SQLDialect...
Executor pretty = new Executor(ctx.getDialect(),
Executor pretty = new Executor(ctx.configuration().getDialect(),
// ... and the flag for pretty-printing
SettingsTools.clone(ctx.getSettings()).withRenderFormatted(true));
SettingsTools.clone(ctx.configuration().getSettings()).withRenderFormatted(true));
Executor normal = new Executor(ctx.getDialect());
Executor normal = new Executor(ctx.configuration().getDialect());
String n = "" + count.incrementAndGet();

View File

@ -37,6 +37,7 @@ package org.jooq.test._.testcases;
import static java.util.Arrays.asList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@ -97,6 +98,116 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
super(delegate);
}
@Test
public void testExecuteListenerWithData() throws Exception {
Executor create = create();
create.getExecuteListeners().add(new DataListener());
create.selectOne().fetch();
}
public static class DataListener extends DefaultExecuteListener {
/**
* Generated UID
*/
private static final long serialVersionUID = 7399239846062763212L;
@Override
public void start(ExecuteContext ctx) {
assertTrue(ctx.data().isEmpty());
assertNull(ctx.data("X"));
assertNull(ctx.data("X", "start"));
}
private void assertThings(ExecuteContext ctx, String oldValue, String newValue) {
assertFalse(ctx.data().isEmpty());
assertEquals(1, ctx.data().size());
assertEquals(oldValue, ctx.data("X"));
assertEquals(oldValue, ctx.data("X", newValue));
}
@Override
public void renderStart(ExecuteContext ctx) {
assertThings(ctx, "start", "renderStart");
}
@Override
public void renderEnd(ExecuteContext ctx) {
assertThings(ctx, "renderStart", "renderEnd");
}
@Override
public void prepareStart(ExecuteContext ctx) {
assertThings(ctx, "renderEnd", "prepareStart");
}
@Override
public void prepareEnd(ExecuteContext ctx) {
assertThings(ctx, "prepareStart", "prepareEnd");
}
@Override
public void bindStart(ExecuteContext ctx) {
assertThings(ctx, "prepareEnd", "bindStart");
}
@Override
public void bindEnd(ExecuteContext ctx) {
assertThings(ctx, "bindStart", "bindEnd");
}
@Override
public void executeStart(ExecuteContext ctx) {
assertThings(ctx, "bindEnd", "executeStart");
}
@Override
public void executeEnd(ExecuteContext ctx) {
assertThings(ctx, "executeStart", "executeEnd");
}
@Override
public void fetchStart(ExecuteContext ctx) {
assertThings(ctx, "executeEnd", "fetchStart");
}
@Override
public void resultStart(ExecuteContext ctx) {
assertThings(ctx, "fetchStart", "resultStart");
}
@Override
public void recordStart(ExecuteContext ctx) {
assertThings(ctx, "resultStart", "recordStart");
}
@Override
public void recordEnd(ExecuteContext ctx) {
assertThings(ctx, "recordStart", "recordEnd");
}
@Override
public void resultEnd(ExecuteContext ctx) {
assertThings(ctx, "recordEnd", "resultEnd");
}
@Override
public void fetchEnd(ExecuteContext ctx) {
assertThings(ctx, "resultEnd", "fetchEnd");
}
@Override
public void end(ExecuteContext ctx) {
assertThings(ctx, "fetchEnd", "end");
}
@Override
public void exception(ExecuteContext ctx) {
fail();
}
}
@Test
public void testExecuteListenerCustomException() throws Exception {
Executor create = create();
@ -200,12 +311,12 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
assertEquals(ctx.query(), ctx.batchQueries()[0]);
assertEquals(1, ctx.batchSQL().length);
assertEquals("Bar", ctx.getData("Foo"));
assertEquals("Baz", ctx.getData("Bar"));
assertEquals("Bar", ctx.configuration().getData("Foo"));
assertEquals("Baz", ctx.configuration().getData("Bar"));
assertEquals(new HashMap<String, String>() {{
put("Foo", "Bar");
put("Bar", "Baz");
}}, ctx.getData());
}}, ctx.configuration().getData());
assertNull(ctx.routine());
assertEquals(ExecuteType.READ, ctx.type());
@ -503,23 +614,23 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
/**
* Generated UID
*/
private static final long serialVersionUID = 7399239846062763212L;
private static final long serialVersionUID = 7399239846062763212L;
// A counter that is incremented in callback methods
private static int callbackCount = 0;
private static int callbackCount = 0;
// Fields that are used to check whether callback methods were called
// in the expected order
public static int start;
public static int renderStart;
public static int renderEnd;
public static int prepareStart;
public static int prepareEnd;
public static List<Integer> bindStart = new ArrayList<Integer>();
public static List<Integer> bindEnd = new ArrayList<Integer>();
public static int executeStart;
public static int executeEnd;
public static int end;
public static int start;
public static int renderStart;
public static int renderEnd;
public static int prepareStart;
public static int prepareEnd;
public static List<Integer> bindStart = new ArrayList<Integer>();
public static List<Integer> bindEnd = new ArrayList<Integer>();
public static int executeStart;
public static int executeEnd;
public static int end;
@SuppressWarnings("serial")
private void checkBase(ExecuteContext ctx) {
@ -528,12 +639,12 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
assertTrue(ctx.batchQueries()[0].toString().toLowerCase().contains("insert"));
assertEquals(1, ctx.batchSQL().length);
assertEquals("Bar", ctx.getData("Foo"));
assertEquals("Baz", ctx.getData("Bar"));
assertEquals("Bar", ctx.configuration().getData("Foo"));
assertEquals("Baz", ctx.configuration().getData("Bar"));
assertEquals(new HashMap<String, String>() {{
put("Foo", "Bar");
put("Bar", "Baz");
}}, ctx.getData());
}}, ctx.configuration().getData());
assertNull(ctx.routine());
assertNull(ctx.resultSet());
@ -760,12 +871,12 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
assertTrue(ctx.batchQueries()[3].toString().toLowerCase().contains("insert"));
assertEquals(4, ctx.batchSQL().length);
assertEquals("Bar", ctx.getData("Foo"));
assertEquals("Baz", ctx.getData("Bar"));
assertEquals("Bar", ctx.configuration().getData("Foo"));
assertEquals("Baz", ctx.configuration().getData("Bar"));
assertEquals(new HashMap<String, String>() {{
put("Foo", "Bar");
put("Bar", "Baz");
}}, ctx.getData());
}}, ctx.configuration().getData());
assertNull(ctx.routine());
assertNull(ctx.resultSet());

View File

@ -2041,6 +2041,11 @@ public abstract class jOOQAbstractTest<
new ExecuteListenerTests(this).testExecuteListenerOnResultQuery();
}
@Test
public void testExecuteListenerWithData() throws Exception {
new ExecuteListenerTests(this).testExecuteListenerWithData();
}
@Test
public void testExecuteListenerCustomException() throws Exception {
new ExecuteListenerTests(this).testExecuteListenerCustomException();

View File

@ -39,6 +39,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import org.jooq.conf.Settings;
import org.jooq.conf.StatementType;
@ -53,7 +54,58 @@ import org.jooq.exception.DataAccessException;
* @author Lukas Eder
* @see ExecuteListener
*/
public interface ExecuteContext extends Configuration {
public interface ExecuteContext {
/**
* Get all custom data from this <code>ExecuteContext</code>.
* <p>
* This is custom data that was previously set to the execute context using
* {@link #data(Object, Object)}. Use custom data if you want to pass data
* between events received by an {@link ExecuteListener}.
* <p>
* Unlike {@link Configuration#getData()}, these data's lifecycle only
* matches that of a single query execution.
*
* @return The custom data. This is never <code>null</code>
* @see ExecuteListener
*/
Map<Object, Object> data();
/**
* Get some custom data from this <code>ExecuteContext</code>.
* <p>
* This is custom data that was previously set to the execute context using
* {@link #data(Object, Object)}. Use custom data if you want to pass
* data between events received by an {@link ExecuteListener}.
* <p>
* Unlike {@link Configuration#getData()}, these data's lifecycle only
* matches that of a single query execution.
*
* @param key A key to identify the custom data
* @return The custom data or <code>null</code> if no such data is contained
* in this <code>ExecuteContext</code>
* @see ExecuteListener
*/
Object data(Object key);
/**
* Set some custom data to this <code>ExecuteContext</code>.
* <p>
* This is custom data that was previously set to the execute context using
* {@link #data(Object, Object)}. Use custom data if you want to pass
* data between events received by an {@link ExecuteListener}.
* <p>
* Unlike {@link Configuration#getData()}, these data's lifecycle only
* matches that of a single query execution.
*
* @param key A key to identify the custom data
* @param value The custom data or <code>null</code> to unset the custom
* data
* @return The previously set custom data or <code>null</code> if no data
* was previously set for the given key
* @see ExecuteListener
*/
Object data(Object key, Object value);
/**
* The configuration wrapped by this context
@ -63,8 +115,8 @@ public interface ExecuteContext extends Configuration {
/**
* The connection to be used in this execute context
* <p>
* This returns a proxy to the {@link #getConnectionProvider()}'s supplied
* connection. This proxy takes care of two things:
* This returns a proxy to the {@link Configuration#getConnectionProvider()}
* 's supplied connection. This proxy takes care of two things:
* <ul>
* <li>It takes care of properly implementing
* {@link Settings#getStatementType()}</li>

View File

@ -175,7 +175,7 @@ abstract class AbstractResultQuery<R extends Record> extends AbstractQuery imple
// [#1296] These dialects do not implement FOR UPDATE. But the same
// effect can be achieved using ResultSet.CONCUR_UPDATABLE
if (isForUpdate() && asList(CUBRID, SQLSERVER).contains(ctx.getDialect())) {
if (isForUpdate() && asList(CUBRID, SQLSERVER).contains(ctx.configuration().getDialect())) {
ctx.statement(ctx.connection().prepareStatement(ctx.sql(), TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE));
}
@ -206,7 +206,7 @@ abstract class AbstractResultQuery<R extends Record> extends AbstractQuery imple
// [#706] Postgres requires two separate queries running in the same
// transaction to be executed when fetching refcursor types
if (ctx.getDialect() == SQLDialect.POSTGRES && isSelectingRefCursor()) {
if (ctx.configuration().getDialect() == SQLDialect.POSTGRES && isSelectingRefCursor()) {
autoCommit = connection.getAutoCommit();
if (autoCommit) {
@ -222,7 +222,7 @@ abstract class AbstractResultQuery<R extends Record> extends AbstractQuery imple
// JTDS doesn't seem to implement PreparedStatement.execute()
// correctly, at least not for sp_help
if (ctx.getDialect() == ASE) {
if (ctx.configuration().getDialect() == ASE) {
ctx.resultSet(ctx.statement().executeQuery());
}
@ -246,7 +246,7 @@ abstract class AbstractResultQuery<R extends Record> extends AbstractQuery imple
}
}
else {
result = new ResultImpl<R>(ctx);
result = new ResultImpl<R>(ctx.configuration());
}
}
@ -258,7 +258,7 @@ abstract class AbstractResultQuery<R extends Record> extends AbstractQuery imple
while (ctx.resultSet() != null) {
anyResults = true;
Field<?>[] fields = new MetaDataFieldProvider(ctx, ctx.resultSet().getMetaData()).getFields();
Field<?>[] fields = new MetaDataFieldProvider(ctx.configuration(), ctx.resultSet().getMetaData()).getFields();
Cursor<Record> c = new CursorImpl<Record>(ctx, listener, fields, internIndexes(fields), true);
results.add(c.fetch());

View File

@ -183,7 +183,7 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractQuery implem
// Just in case, always set Sybase ASE statement mode to return
// Generated keys if client code wants to SELECT @@identity afterwards
if (ctx.getDialect() == SQLDialect.ASE) {
if (ctx.configuration().getDialect() == SQLDialect.ASE) {
ctx.statement(connection.prepareStatement(ctx.sql(), Statement.RETURN_GENERATED_KEYS));
return;
}
@ -196,7 +196,7 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractQuery implem
// Values should be returned from the INSERT
else {
switch (ctx.getDialect()) {
switch (ctx.configuration().getDialect()) {
// Postgres uses the RETURNING clause in SQL
case FIREBIRD:
@ -247,7 +247,7 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractQuery implem
else {
int result = 1;
ResultSet rs;
switch (ctx.getDialect()) {
switch (ctx.configuration().getDialect()) {
// SQLite can select _rowid_ after the insert
case SQLITE: {
@ -255,7 +255,7 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractQuery implem
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
Executor create = new Executor(ctx.connection(), SQLDialect.SQLITE, ctx.getSettings());
Executor create = new Executor(ctx.connection(), SQLDialect.SQLITE, ctx.configuration().getSettings());
returned =
create.select(returning)
.from(getInto())
@ -275,7 +275,7 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractQuery implem
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
selectReturning(ctx.configuration(), create(ctx).lastID());
selectReturning(ctx.configuration(), create(ctx.configuration()).lastID());
return result;
}
@ -305,7 +305,7 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractQuery implem
}
}
selectReturning(ctx, list.toArray());
selectReturning(ctx.configuration(), list.toArray());
return result;
}
finally {

View File

@ -43,7 +43,9 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jooq.Configuration;
import org.jooq.ConnectionProvider;
@ -66,14 +68,11 @@ import org.jooq.tools.jdbc.JDBCUtils;
*
* @author Lukas Eder
*/
class DefaultExecuteContext extends AbstractConfiguration implements ExecuteContext {
/**
* Generated UID
*/
private static final long serialVersionUID = -6653474082935089963L;
class DefaultExecuteContext implements ExecuteContext {
// Persistent attributes (repeatable)
private final Configuration configuration;
private final Map<Object, Object> data;
private final Query query;
private final Routine<?> routine;
private String sql;
@ -224,8 +223,8 @@ class DefaultExecuteContext extends AbstractConfiguration implements ExecuteCont
}
private DefaultExecuteContext(Configuration configuration, Query query, Query[] batchQueries, Routine<?> routine) {
super(configuration);
this.configuration = configuration;
this.data = new HashMap<Object, Object>();
this.query = query;
this.batchQueries = (batchQueries == null ? new Query[0] : batchQueries);
this.routine = routine;
@ -246,6 +245,21 @@ class DefaultExecuteContext extends AbstractConfiguration implements ExecuteCont
LOCAL_CONFIGURATION.set(configuration);
}
@Override
public final Map<Object, Object> data() {
return data;
}
@Override
public final Object data(Object key) {
return data.get(key);
}
@Override
public final Object data(Object key, Object value) {
return data.put(key, value);
}
@Override
public final ExecuteType type() {
@ -393,14 +407,14 @@ class DefaultExecuteContext extends AbstractConfiguration implements ExecuteCont
// wrapped by a ConnectionProxy, transparently, in order to implement
// Settings.getStatementType() correctly.
ConnectionProvider provider = connectionProvider != null ? connectionProvider : getConnectionProvider();
ConnectionProvider provider = connectionProvider != null ? connectionProvider : configuration.getConnectionProvider();
if (connection == null && provider != null) {
Connection c = provider.acquire();
if (c != null) {
LOCAL_CONNECTION.set(c);
connection = new SettingsEnabledConnection(new ProviderEnabledConnection(provider, c), getSettings());
connection = new SettingsEnabledConnection(new ProviderEnabledConnection(provider, c), configuration.getSettings());
}
}

View File

@ -1074,15 +1074,15 @@ final class Utils {
* Get a list of ExecuteListener instances (including defaults) from a
* configuration
*/
static final List<ExecuteListener> getListeners(Configuration configuration) {
static final List<ExecuteListener> getListeners(ExecuteContext ctx) {
List<ExecuteListener> result = new ArrayList<ExecuteListener>();
if (!FALSE.equals(configuration.getSettings().isExecuteLogging())) {
if (!FALSE.equals(ctx.configuration().getSettings().isExecuteLogging())) {
result.add(new StopWatchListener());
result.add(new LoggerListener());
}
result.addAll(configuration.getExecuteListeners());
result.addAll(ctx.configuration().getExecuteListeners());
return result;
}
@ -1745,7 +1745,7 @@ final class Utils {
}
else if (type == BigInteger.class) {
// The SQLite JDBC driver doesn't support BigDecimals
if (ctx.getDialect() == SQLDialect.SQLITE) {
if (ctx.configuration().getDialect() == SQLDialect.SQLITE) {
return Convert.convert(rs.getString(index), (Class<T>) BigInteger.class);
}
else {
@ -1755,7 +1755,7 @@ final class Utils {
}
else if (type == BigDecimal.class) {
// The SQLite JDBC driver doesn't support BigDecimals
if (ctx.getDialect() == SQLDialect.SQLITE) {
if (ctx.configuration().getDialect() == SQLDialect.SQLITE) {
return Convert.convert(rs.getString(index), (Class<T>) BigDecimal.class);
}
else {
@ -1772,7 +1772,7 @@ final class Utils {
return (T) rs.getClob(index);
}
else if (type == Date.class) {
return (T) getDate(ctx.getDialect(), rs, index);
return (T) getDate(ctx.configuration().getDialect(), rs, index);
}
else if (type == Double.class) {
return (T) wasNull(rs, Double.valueOf(rs.getDouble(index)));
@ -1793,13 +1793,13 @@ final class Utils {
return (T) rs.getString(index);
}
else if (type == Time.class) {
return (T) getTime(ctx.getDialect(), rs, index);
return (T) getTime(ctx.configuration().getDialect(), rs, index);
}
else if (type == Timestamp.class) {
return (T) getTimestamp(ctx.getDialect(), rs, index);
return (T) getTimestamp(ctx.configuration().getDialect(), rs, index);
}
else if (type == YearToMonth.class) {
if (ctx.getDialect() == POSTGRES) {
if (ctx.configuration().getDialect() == POSTGRES) {
Object object = rs.getObject(index);
return (T) (object == null ? null : PostgresUtils.toYearToMonth(object));
}
@ -1809,7 +1809,7 @@ final class Utils {
}
}
else if (type == DayToSecond.class) {
if (ctx.getDialect() == POSTGRES) {
if (ctx.configuration().getDialect() == POSTGRES) {
Object object = rs.getObject(index);
return (T) (object == null ? null : PostgresUtils.toDayToSecond(object));
}
@ -1835,7 +1835,7 @@ final class Utils {
return (T) (string == null ? null : ULong.valueOf(string));
}
else if (type == UUID.class) {
switch (ctx.getDialect()) {
switch (ctx.configuration().getDialect()) {
// [#1624] Some JDBC drivers natively support the
// java.util.UUID data type
@ -1859,7 +1859,7 @@ final class Utils {
// The type byte[] is handled earlier. byte[][] can be handled here
else if (type.isArray()) {
switch (ctx.getDialect()) {
switch (ctx.configuration().getDialect()) {
case POSTGRES: {
return pgGetArray(ctx, type, index);
}
@ -1871,13 +1871,13 @@ final class Utils {
}
}
else if (ArrayRecord.class.isAssignableFrom(type)) {
return (T) getArrayRecord(ctx, rs.getArray(index), (Class<? extends ArrayRecord<?>>) type);
return (T) getArrayRecord(ctx.configuration(), rs.getArray(index), (Class<? extends ArrayRecord<?>>) type);
}
else if (EnumType.class.isAssignableFrom(type)) {
return getEnumType(type, rs.getString(index));
}
else if (UDTRecord.class.isAssignableFrom(type)) {
switch (ctx.getDialect()) {
switch (ctx.configuration().getDialect()) {
case POSTGRES:
return (T) pgNewUDTRecord(type, rs.getObject(index));
}
@ -1886,7 +1886,7 @@ final class Utils {
}
else if (Result.class.isAssignableFrom(type)) {
ResultSet nested = (ResultSet) rs.getObject(index);
return (T) new Executor(ctx).fetch(nested);
return (T) new Executor(ctx.configuration()).fetch(nested);
}
else {
return (T) rs.getObject(index);
@ -2100,7 +2100,7 @@ final class Utils {
return (T) stmt.getTimestamp(index);
}
else if (type == YearToMonth.class) {
if (ctx.getDialect() == POSTGRES) {
if (ctx.configuration().getDialect() == POSTGRES) {
Object object = stmt.getObject(index);
return (T) (object == null ? null : PostgresUtils.toYearToMonth(object));
}
@ -2110,7 +2110,7 @@ final class Utils {
}
}
else if (type == DayToSecond.class) {
if (ctx.getDialect() == POSTGRES) {
if (ctx.configuration().getDialect() == POSTGRES) {
Object object = stmt.getObject(index);
return (T) (object == null ? null : PostgresUtils.toDayToSecond(object));
}
@ -2136,7 +2136,7 @@ final class Utils {
return (T) (string == null ? null : ULong.valueOf(string));
}
else if (type == UUID.class) {
switch (ctx.getDialect()) {
switch (ctx.configuration().getDialect()) {
// [#1624] Some JDBC drivers natively support the
// java.util.UUID data type
@ -2163,13 +2163,13 @@ final class Utils {
return (T) convertArray(stmt.getObject(index), (Class<? extends Object[]>)type);
}
else if (ArrayRecord.class.isAssignableFrom(type)) {
return (T) getArrayRecord(ctx, stmt.getArray(index), (Class<? extends ArrayRecord<?>>) type);
return (T) getArrayRecord(ctx.configuration(), stmt.getArray(index), (Class<? extends ArrayRecord<?>>) type);
}
else if (EnumType.class.isAssignableFrom(type)) {
return getEnumType(type, stmt.getString(index));
}
else if (UDTRecord.class.isAssignableFrom(type)) {
switch (ctx.getDialect()) {
switch (ctx.configuration().getDialect()) {
case POSTGRES:
return (T) pgNewUDTRecord(type, stmt.getObject(index));
}
@ -2178,7 +2178,7 @@ final class Utils {
}
else if (Result.class.isAssignableFrom(type)) {
ResultSet nested = (ResultSet) stmt.getObject(index);
return (T) new Executor(ctx).fetch(nested);
return (T) new Executor(ctx.configuration()).fetch(nested);
}
else {
return (T) stmt.getObject(index);