[#3212] Add support for value types in DefaultRecordMapper, when mapping Record1 types

This commit is contained in:
Lukas Eder 2014-04-28 10:46:39 +02:00
parent 4d27ced7b3
commit afbf75aee6
5 changed files with 97 additions and 1 deletions

View File

@ -1834,6 +1834,28 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
}
}
@Test
public void testFetchIntoValueType() throws Exception {
Result<Record1<Integer>> result =
create().select(TBook_ID())
.from(TBook())
.orderBy(TBook_ID())
.fetch();
assertEquals(BOOK_IDS, result.into(int.class));
assertEquals(BOOK_IDS, result.into(Integer.class));
assertEquals(BOOK_IDS_SHORT, result.into(short.class));
assertEquals(BOOK_IDS_SHORT, result.into(Short.class));
assertEquals(BOOK_IDS_STRING, result.into(String.class));
try {
create().selectFrom(TBook())
.fetchInto(int.class);
fail();
}
catch (MappingException expected) {}
}
@Test
public void testFetchIntoResultSet() throws Exception {
Result<B> result = create().selectFrom(TBook()).orderBy(TBook_ID()).fetch();

View File

@ -1592,6 +1592,11 @@ public abstract class jOOQAbstractTest<
new FetchTests(this).testFetchIntoGeneratedPojos();
}
@Test
public void testFetchIntoValueType() throws Exception {
new FetchTests(this).testFetchIntoValueType();
}
@Test
public void testFetchIntoRecordHandler() throws Exception {
new FetchTests(this).testFetchIntoRecordHandler();

View File

@ -40,6 +40,7 @@
*/
package org.jooq.impl;
import static java.util.Collections.unmodifiableCollection;
import static org.jooq.impl.SQLDataType.BLOB;
import static org.jooq.impl.SQLDataType.CLOB;
import static org.jooq.impl.SQLDataType.NCLOB;
@ -849,4 +850,12 @@ public class DefaultDataType<T> implements DataType<T> {
return BigDecimal.class;
}
}
static Collection<Class<?>> types() {
return unmodifiableCollection(SQL_DATATYPES_BY_TYPE.keySet());
}
static Collection<DataType<?>> dataTypes() {
return unmodifiableCollection(SQL_DATATYPES_BY_TYPE.values());
}
}

View File

@ -56,6 +56,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@ -67,6 +68,7 @@ import org.jooq.AttachableInternal;
import org.jooq.Configuration;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Record1;
import org.jooq.RecordMapper;
import org.jooq.RecordMapperProvider;
import org.jooq.RecordType;
@ -87,6 +89,24 @@ import org.jooq.tools.reflect.Reflect;
* specific arrays fails, a {@link MappingException} is thrown, wrapping
* conversion exceptions.
* <p>
* <h5>If <code>&lt;E></code> is a field "value type" and <code>&lt;R></code>
* has exactly one column:</h5>
* <p>
* Any Java type available from {@link SQLDataType} qualifies as a well-known
* "value type" that can be converted from a single-field {@link Record1}. The
* following rules apply:
* <p>
* <ul>
* <li>If <code>&lt;E></code> is a reference type like {@link String},
* {@link Integer}, {@link Long}, {@link Timestamp}, etc., then converting from
* <code>&lt;R></code> to <code>&lt;E></code> is mere convenience for calling
* {@link Record#getValue(int, Class)} with <code>fieldIndex = 0</code></li>
* <li>If <code>&lt;E></code> is a primitive type, the mapping result will be
* the corresponding wrapper type. <code>null</code> will map to the primitive
* type's initialisation value, e.g. <code>0</code> for <code>int</code>,
* <code>0.0</code> for <code>double</code>, <code>false</code> for
* <code>boolean</code>.</li>
* </ul>
* <h5>If a default constructor is available and any JPA {@link Column}
* annotations are found on the provided <code>&lt;E></code>, only those are
* used:</h5>
@ -245,6 +265,12 @@ public class DefaultRecordMapper<R extends Record, E> implements RecordMapper<R,
return;
}
// [#3212] "Value types" can be mapped from single-field Record1 types for convenience
if (type.isPrimitive() || DefaultDataType.types().contains(type)) {
delegate = new ValueTypeMapper();
return;
}
// [#1470] Return a proxy if the supplied type is an interface
if (Modifier.isAbstract(type.getModifiers())) {
delegate = new ProxyMapper();
@ -347,6 +373,18 @@ public class DefaultRecordMapper<R extends Record, E> implements RecordMapper<R,
}
}
private class ValueTypeMapper implements RecordMapper<R, E> {
@Override
public final E map(R record) {
int size = record.size();
if (size != 1)
throw new MappingException("Cannot map multi-column record of degree " + size + " to value type " + type);
return record.getValue(0, type);
}
}
/**
* Convert a record into an hash map proxy of a given type.
* <p>

View File

@ -41,13 +41,16 @@
package org.jooq.test;
import static org.junit.Assert.assertTrue;
import static org.jooq.impl.DSL.field;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import javax.persistence.Column;
import org.jooq.Field;
import org.jooq.Record1;
import org.jooq.Result;
import org.junit.Test;
@ -103,4 +106,23 @@ public class RecordMappingTest extends AbstractTest {
return "Boolean [oneZero=" + oneZero + ", oneZero1=" + oneZero1 + ", oneZero2=" + oneZero2 + "]";
}
}
@Test
public void testIntoValueTypes() throws Exception {
Field<Boolean> field = field("B", Boolean.class);
Result<Record1<Boolean>> result = create.newResult(field);
result.add(create.newRecord(field));
result.add(create.newRecord(field));
result.add(create.newRecord(field));
result.get(0).setValue(field, true);
result.get(1).setValue(field, false);
result.get(2).setValue(field, null);
assertEquals(Arrays.asList(true, false, false), result.into(boolean.class));
assertEquals(Arrays.asList(true, false, null), result.into(Boolean.class));
assertEquals(Arrays.asList(1, 0, 0), result.into(int.class));
assertEquals(Arrays.asList(1, 0, null), result.into(Integer.class));
assertEquals(Arrays.asList("true", "false", null), result.into(String.class));
}
}