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];
+                       }
+               }
        }
 }

Reply via email to