This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit a405db945b3cb81d0ecd0f6a445b69d2041cc944 Author: Nikita Timofeev <stari...@gmail.com> AuthorDate: Mon Aug 5 17:34:02 2019 +0300 CAY-2527 API to map Object[] result to POJO --- .../cayenne/access/DataDomainQueryAction.java | 60 ++++++++++++++--- .../org/apache/cayenne/query/ColumnSelect.java | 14 ++++ .../apache/cayenne/query/ColumnSelectMetadata.java | 11 +++ .../org/apache/cayenne/query/QueryMetadata.java | 8 +++ .../java/org/apache/cayenne/query/SQLSelect.java | 9 +++ .../java/org/apache/cayenne/query/SQLTemplate.java | 7 ++ .../apache/cayenne/query/SQLTemplateMetadata.java | 11 +++ .../org/apache/cayenne/query/ColumnSelectIT.java | 36 ++++++++++ .../java/org/apache/cayenne/query/SQLSelectIT.java | 78 +++++++++++++++++++--- 9 files changed, 212 insertions(+), 22 deletions(-) diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java index 1128532..dba1041 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java @@ -59,6 +59,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; /** * Performs query routing and execution. During execution phase intercepts @@ -466,29 +467,38 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { @SuppressWarnings("unchecked") private void interceptObjectConversion() { - if (context != null && !metadata.isFetchingDataRows()) { - + if (context != null) { List mainRows = response.firstList(); // List<DataRow> or List<Object[]> if (mainRows != null && !mainRows.isEmpty()) { ObjectConversionStrategy<?> converter; - List<Object> rsMapping = metadata.getResultSetMapping(); - if (rsMapping == null) { - converter = new SingleObjectConversionStrategy(); + if(metadata.isFetchingDataRows()) { + converter = new IdentityConversionStrategy(); } else { - if (metadata.isSingleResultSetMapping()) { - if (rsMapping.get(0) instanceof EntityResultSegment) { - converter = new SingleObjectConversionStrategy(); + List<Object> rsMapping = metadata.getResultSetMapping(); + if (rsMapping == null) { + converter = new SingleObjectConversionStrategy(); + } else { + if (metadata.isSingleResultSetMapping()) { + if (rsMapping.get(0) instanceof EntityResultSegment) { + converter = new SingleObjectConversionStrategy(); + } else { + converter = new SingleScalarConversionStrategy(); + } } else { - converter = new SingleScalarConversionStrategy(); + converter = new MixedConversionStrategy(); } - } else { - converter = new MixedConversionStrategy(); } } + if(metadata.getResultMapper() != null) { + converter = new MapperConversionStrategy(converter); + } + converter.convert(mainRows); + // rewind response after firstList() call + response.reset(); } } } @@ -775,4 +785,32 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { } } } + + private class IdentityConversionStrategy extends ObjectConversionStrategy<Object> { + @Override + void convert(List<Object> mainRows) { + } + } + + /** + * Conversion strategy that uses mapper function to map raw result + */ + private class MapperConversionStrategy extends ObjectConversionStrategy<Object> { + + private final Function<Object, ?> mapper; + private final ObjectConversionStrategy<Object> parentStrategy; + + @SuppressWarnings("unchecked") + MapperConversionStrategy(ObjectConversionStrategy<?> parentStrategy) { + this.mapper = (Function)metadata.getResultMapper(); + this.parentStrategy = (ObjectConversionStrategy)parentStrategy; + } + + @Override + void convert(List<Object> mainRows) { + parentStrategy.convert(mainRows); + mainRows.replaceAll(mapper::apply); + } + } + } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java index b82c8c8..8e3bca4 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.function.Function; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.exp.property.BaseProperty; @@ -630,4 +631,17 @@ public class ColumnSelect<T> extends FluentSelect<T> { protected BaseQueryMetadata getBaseMetaData() { return metaData; } + + /** + * Wrap result to given class. Wrapper class should be public and have public constructor with no args. + * Columns order in the query should corespond to fields defined in that class. + * + * @param mapper function that maps result to required form. + * @since 4.2 + */ + @SuppressWarnings("unchecked") + public <E> ColumnSelect<E> map(Function<T, E> mapper) { + this.metaData.setResultMapper(mapper); + return (ColumnSelect<E>)this; + } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java index 5f5b153..2ab406e 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.function.Function; import org.apache.cayenne.exp.Expression; import org.apache.cayenne.exp.property.BaseProperty; @@ -44,6 +45,7 @@ class ColumnSelectMetadata extends ObjectSelectMetadata { private boolean isSingleResultSetMapping; private boolean suppressingDistinct; + private Function<?, ?> resultMapper; boolean resolve(Object root, EntityResolver resolver, ColumnSelect<?> query) { @@ -133,4 +135,13 @@ class ColumnSelectMetadata extends ObjectSelectMetadata { public void setSuppressingDistinct(boolean suppressingDistinct) { this.suppressingDistinct = suppressingDistinct; } + + void setResultMapper(Function<?, ?> resultMapper) { + this.resultMapper = resultMapper; + } + + @Override + public Function<?, ?> getResultMapper() { + return resultMapper; + } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java index 396cac1..2797f91 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java @@ -21,6 +21,7 @@ package org.apache.cayenne.query; import java.util.List; import java.util.Map; +import java.util.function.Function; import org.apache.cayenne.map.DataMap; import org.apache.cayenne.map.DbEntity; @@ -256,4 +257,11 @@ public interface QueryMetadata { * @since 4.0 */ boolean isSuppressingDistinct(); + + /** + * @since 4.2 + */ + default Function<?, ?> getResultMapper() { + return null; + } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java index b2f426f..e3f6fe8 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.DataRow; @@ -46,6 +47,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> { private List<Class<?>> resultColumnsTypes; private boolean useScalar; private boolean isFetchingDataRows; + private Function<T, ?> resultMapper; /** * Creates a query that selects DataRows and uses default routing. @@ -443,6 +445,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> { template.setStatementFetchSize(statementFetchSize); template.setQueryTimeout(queryTimeout); template.setUseScalar(useScalar); + template.setResultMapper(resultMapper); return template; } @@ -684,6 +687,12 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> { } @SuppressWarnings("unchecked") + public <E> SQLSelect<E> map(Function<T, E> mapper) { + this.resultMapper = mapper; + return (SQLSelect<E>)this; + } + + @SuppressWarnings("unchecked") private <E> SQLSelect<E> fetchingDataRows() { this.isFetchingDataRows = true; return (SQLSelect<E>) this; diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java index b2d5e9a..19b1968 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java @@ -700,4 +700,11 @@ public class SQLTemplate extends AbstractQuery implements ParameterizedQuery { public boolean isUseScalar() { return useScalar; } + + /** + * @since 4.2 + */ + public void setResultMapper(Function<?,?> resultMapper) { + this.metaData.setResultMapper(resultMapper); + } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplateMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplateMetadata.java index 4fe03c9..6bd92ed 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplateMetadata.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplateMetadata.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Function; /** * @since 3.0 @@ -34,6 +35,7 @@ import java.util.Map; public class SQLTemplateMetadata extends BaseQueryMetadata { private boolean isSingleResultSetMapping; + private Function<?, ?> resultMapper; @Override public boolean isSingleResultSetMapping() { @@ -125,4 +127,13 @@ public class SQLTemplateMetadata extends BaseQueryMetadata { } query.setResult(result); } + + void setResultMapper(Function<?,?> resultMapper) { + this.resultMapper = resultMapper; + } + + @Override + public Function<?, ?> getResultMapper() { + return resultMapper; + } } diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java index 3f31925..6ec41fb 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java @@ -41,6 +41,7 @@ import org.apache.cayenne.exp.property.EntityProperty; import org.apache.cayenne.exp.property.NumericProperty; import org.apache.cayenne.exp.property.PropertyFactory; import org.apache.cayenne.exp.property.StringProperty; +import org.apache.cayenne.reflect.PojoMapper; import org.apache.cayenne.test.jdbc.DBHelper; import org.apache.cayenne.test.jdbc.TableHelper; import org.apache.cayenne.testdo.testmap.Artist; @@ -1152,4 +1153,39 @@ public class ColumnSelectIT extends ServerCase { assertEquals("artist1", ((Artist)results.get(0)[1]).getArtistName()); assertEquals(1, results.get(0)[2]); } + + @Test + public void testMapToPojo() { + List<TestPojo> result = ObjectSelect.query(Artist.class) + .columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH, Artist.ARTIST_NAME.trim().length()) + .where(Artist.ARTIST_NAME.like("artist%")) + .orderBy(Artist.ARTIST_ID_PK_PROPERTY.asc()) + .map(TestPojo::new) + .select(context); + + assertEquals(20, result.size()); + + TestPojo testPojo0 = result.get(0); + assertNotNull(testPojo0); + assertEquals("artist1", testPojo0.name); + assertNotNull(testPojo0.date); + assertEquals(7, testPojo0.length); + + TestPojo testPojo19 = result.get(19); + assertNotNull(testPojo19); + assertEquals("artist20", testPojo19.name); + assertEquals(8, testPojo19.length); + assertNotNull(testPojo19.date); + } + + static class TestPojo { + String name; + Date date; + int length; + TestPojo(Object[] data) { + name = (String)data[0]; + date = (Date)data[1]; + length = (Integer)data[2]; + } + } } diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIT.java index 4185bb7..7d6f276 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIT.java @@ -128,6 +128,18 @@ public class SQLSelectIT extends ServerCase { } @Test + public void test_DataRowWithTypesMapped() throws Exception { + createArtistDataSet(); + + List<Object> result = SQLSelect.dataRowQuery("SELECT * FROM ARTIST_CT", Integer.class, String.class, LocalDateTime.class) + .columnNameCaps(CapsStrategy.UPPER) + .map(dataRow -> dataRow.get("ARTIST_ID")) + .select(context); + assertEquals(2, result.size()); + assertTrue(result.get(0) instanceof Integer); + } + + @Test public void test_DataRowWithDirectives() throws Exception { createArtistDataSet(); @@ -160,6 +172,20 @@ public class SQLSelectIT extends ServerCase { assertTrue(result.get(0)[1] instanceof String); } + @Test + public void testObjectArrayWithDefaultTypesReturnAndDirectivesMappedToPojo() throws Exception { + createArtistDataSet(); + + List<ArtistDataWrapper> result = SQLSelect + .arrayQuery("SELECT #result('ARTIST_ID' 'java.lang.Long'), #result('ARTIST_NAME' 'java.lang.String') FROM ARTIST_CT") + .map(ArtistDataWrapper::new) + .select(context); + + assertEquals(2, result.size()); + assertTrue(result.get(0).id > 0); + assertNotNull(result.get(0).name); + } + @Test(expected = CayenneRuntimeException.class) public void testObjectArrayReturnAndDirectives() throws Exception { createArtistDataSet(); @@ -235,6 +261,21 @@ public class SQLSelectIT extends ServerCase { } @Test + public void testObjectArrayWithCustomTypeMappedToPojo() throws SQLException { + createArtistDataSet(); + + List<ArtistDataWrapper> result = SQLSelect.scalarQuery("SELECT * FROM ARTIST_CT", + Integer.class, String.class, LocalDateTime.class) + .map(ArtistDataWrapper::new) + .select(context); + + assertEquals(2, result.size()); + assertTrue(result.get(0).id > 0); + assertNotNull(result.get(0).name); + assertNotNull(result.get(0).date); + } + + @Test public void test_DataRows_ClassRoot_Parameters() throws Exception { createPaintingsDataSet(); @@ -261,7 +302,7 @@ public class SQLSelectIT extends ServerCase { } @Test - public void test_DataRows_ColumnNameCaps() throws Exception { + public void test_DataRows_ColumnNameCaps() { SQLSelect<DataRow> q1 = SQLSelect.dataRowQuery("SELECT * FROM PAINTING WHERE PAINTING_TITLE = 'painting2'"); q1.upperColumnNames(); @@ -311,7 +352,9 @@ public class SQLSelectIT extends ServerCase { List<Painting> result = SQLSelect .query(Painting.class, "SELECT * FROM PAINTING WHERE PAINTING_TITLE = #bind($a)") - .param("a", "painting3").columnNameCaps(CapsStrategy.UPPER).select(context); + .param("a", "painting3") + .columnNameCaps(CapsStrategy.UPPER) + .select(context); assertEquals(1, result.size()); } @@ -367,7 +410,7 @@ public class SQLSelectIT extends ServerCase { createPaintingsDataSet(); try (ResultIterator<Painting> it = SQLSelect.query(Painting.class, "SELECT * FROM PAINTING") - .columnNameCaps(CapsStrategy.UPPER).iterator(context);) { + .columnNameCaps(CapsStrategy.UPPER).iterator(context)) { int count = 0; for (Painting p : it) { @@ -383,7 +426,7 @@ public class SQLSelectIT extends ServerCase { createPaintingsDataSet(); try (ResultBatchIterator<Painting> it = SQLSelect.query(Painting.class, "SELECT * FROM PAINTING") - .columnNameCaps(CapsStrategy.UPPER).batchIterator(context, 5);) { + .columnNameCaps(CapsStrategy.UPPER).batchIterator(context, 5)) { int count = 0; for (List<Painting> paintingList : it) { @@ -404,7 +447,7 @@ public class SQLSelectIT extends ServerCase { Integer.class) .param("a", "painting3").selectOne(context); - assertEquals(3l, id); + assertEquals(3L, id); } @Test @@ -415,7 +458,7 @@ public class SQLSelectIT extends ServerCase { Integer.class).select(context); assertEquals(20, ids.size()); - assertEquals(2l, ids.get(1).intValue()); + assertEquals(2L, ids.get(1).intValue()); } @Test @@ -436,7 +479,7 @@ public class SQLSelectIT extends ServerCase { Integer.class) .paramsArray("painting3").selectOne(context); - assertEquals(3l, id.intValue()); + assertEquals(3L, id.intValue()); } @Test @@ -449,8 +492,8 @@ public class SQLSelectIT extends ServerCase { Integer.class) .paramsArray("painting3", "painting2").select(context); - assertEquals(2l, ids.get(0).intValue()); - assertEquals(3l, ids.get(1).intValue()); + assertEquals(2L, ids.get(0).intValue()); + assertEquals(3L, ids.get(1).intValue()); } @Test @@ -469,7 +512,7 @@ public class SQLSelectIT extends ServerCase { .paramsArray(null, "painting1").select(context); assertEquals(1, ids.size()); - assertEquals(1l, ids.get(0).longValue()); + assertEquals(1L, ids.get(0).longValue()); } @Test @@ -492,6 +535,19 @@ public class SQLSelectIT extends ServerCase { .params(params).select(context); assertEquals(1, ids.size()); - assertEquals(1l, ids.get(0).longValue()); + assertEquals(1L, ids.get(0).longValue()); + } + + static class ArtistDataWrapper { + long id; + String name; + LocalDateTime date; + ArtistDataWrapper(Object[] data) { + id = ((Number)data[0]).longValue(); + name = (String)data[1]; + if(data.length > 2) { + date = (LocalDateTime)data[2]; + } + } } }