Repository: cayenne Updated Branches: refs/heads/master cd83358e7 -> 7b90a4879
CAY-2244 Using iterate() with ColumnSelect causes a ClassCastException Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/7b90a487 Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/7b90a487 Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/7b90a487 Branch: refs/heads/master Commit: 7b90a487997c05ade6db6ce199d1aafe66212122 Parents: cd83358 Author: Nikita Timofeev <stari...@gmail.com> Authored: Wed Feb 22 14:05:11 2017 +0300 Committer: Nikita Timofeev <stari...@gmail.com> Committed: Wed Feb 22 14:05:11 2017 +0300 ---------------------------------------------------------------------- .../org/apache/cayenne/access/DataContext.java | 121 ++++++++++++------- .../cayenne/query/ObjectSelect_RunIT.java | 57 +++++++++ 2 files changed, 131 insertions(+), 47 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cayenne/blob/7b90a487/cayenne-server/src/main/java/org/apache/cayenne/access/DataContext.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContext.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContext.java index cc0a65e..264a7e8 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContext.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContext.java @@ -800,59 +800,35 @@ public class DataContext extends BaseContext { @SuppressWarnings("unchecked") @Override - public <T> ResultIterator<T> iterator(Select<T> query) { - final ResultIterator<DataRow> rows = performIteratedQuery(query); + public <T> ResultIterator<T> iterator(final Select<T> query) { + final ResultIterator<?> rows = performIteratedQuery(query); + final QueryMetadata md = query.getMetaData(getEntityResolver()); - QueryMetadata md = query.getMetaData(getEntityResolver()); - if (md.isFetchingDataRows()) { + if (md.isFetchingDataRows() || isObjectArrayResult(md)) { + // no need to convert result return (ResultIterator<T>) rows; } else { - // this is a bit optimized version of 'objectFromDataRow' with // resolver cached for reuse... still the rest is pretty suboptimal - ClassDescriptor descriptor = md.getClassDescriptor(); - final ObjectResolver resolver = new ObjectResolver(this, descriptor, true); - return new ResultIterator<T>() { - - @Override - public Iterator<T> iterator() { - return new ResultIteratorIterator<T>(this); - } - - @Override - public List<T> allRows() { - List<T> list = new ArrayList<>(); - - while (hasNextRow()) { - list.add(nextRow()); - } - - return list; - } - - @Override - public boolean hasNextRow() { - return rows.hasNextRow(); - } - - @Override - public T nextRow() { - DataRow row = rows.nextRow(); - List<T> objects = (List<T>) resolver - .synchronizedObjectsFromDataRows(Collections.singletonList(row)); - return (T) objects.get(0); - } + final ObjectResolver resolver = new ObjectResolver(this, md.getClassDescriptor(), true); + return new DataRowResultIterator(rows, resolver); + } + } - @Override - public void skipRow() { - rows.skipRow(); - } + /** + * This method repeats logic of DataDomainQueryAction.interceptObjectConversion() method. + * The difference is that iterator(or batchIterator) doesn't support "mixed" results. + */ + private boolean isObjectArrayResult(QueryMetadata md) { + List<Object> resultMapping = md.getResultSetMapping(); + if(resultMapping == null) { + return false; + } - @Override - public void close() { - rows.close(); - } - }; + if (md.isSingleResultSetMapping()) { + return !(resultMapping.get(0) instanceof EntityResultSegment); + } else { + return true; } } @@ -1142,7 +1118,7 @@ public class DataContext extends BaseContext { } /** - * An internal version of {@link #localObject(Object)} that operates on + * An internal version of {@link #localObject(Persistent)} that operates on * ObjectId instead of Persistent, and wouldn't attempt to look up an object * in the parent channel. * @@ -1218,4 +1194,55 @@ public class DataContext extends BaseContext { this.transactionFactory = transactionFactory; } + /** + * ResultIterator that can convert DataRow to Persistent object on the fly. + */ + static class DataRowResultIterator<T> implements ResultIterator<T> { + + final ResultIterator<?> rows; + ObjectResolver resolver; + + DataRowResultIterator(ResultIterator<?> rows, ObjectResolver resolver) { + this.rows = rows; + this.resolver = resolver; + } + + @Override + public Iterator<T> iterator() { + return new ResultIteratorIterator<>(this); + } + + @Override + public List<T> allRows() { + List<T> list = new ArrayList<>(); + while (hasNextRow()) { + list.add(nextRow()); + } + return list; + } + + @Override + public boolean hasNextRow() { + return rows.hasNextRow(); + } + + @Override + @SuppressWarnings("unchecked") + public T nextRow() { + DataRow row = (DataRow) rows.nextRow(); + List<T> objects = (List<T>) resolver.synchronizedObjectsFromDataRows(Collections.singletonList(row)); + return objects.get(0); + } + + @Override + public void skipRow() { + rows.skipRow(); + } + + @Override + public void close() { + rows.close(); + } + } + } http://git-wip-us.apache.org/repos/asf/cayenne/blob/7b90a487/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java index eb4095d..32dd23e 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java @@ -290,4 +290,61 @@ public class ObjectSelect_RunIT extends ServerCase { assertNotNull(a); assertEquals("ng1 artist1", a); } + + @Test + public void test_ColumnSelect_IterationSingleColumn() throws Exception { + ColumnSelect<String> columnSelect = ObjectSelect.query(Artist.class).column(Artist.ARTIST_NAME); + + final int[] count = new int[1]; + columnSelect.iterate(context, new ResultIteratorCallback<String>() { + @Override + public void next(String object) { + count[0]++; + assertTrue(object.startsWith("artist")); + } + }); + + assertEquals(20, count[0]); + } + + @Test + public void test_ColumnSelect_BatchIterationSingleColumn() throws Exception { + ColumnSelect<String> columnSelect = ObjectSelect.query(Artist.class).column(Artist.ARTIST_NAME); + + try(ResultBatchIterator<String> it = columnSelect.batchIterator(context, 10)) { + List<String> next = it.next(); + assertEquals(10, next.size()); + assertTrue(next.get(0).startsWith("artist")); + } + } + + @Test + public void test_ColumnSelect_IterationMultiColumns() throws Exception { + ColumnSelect<Object[]> columnSelect = ObjectSelect.query(Artist.class).columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH); + + final int[] count = new int[1]; + columnSelect.iterate(context, new ResultIteratorCallback<Object[]>() { + @Override + public void next(Object[] object) { + count[0]++; + assertTrue(object[0] instanceof String); + assertTrue(object[1] instanceof java.util.Date); + } + }); + + assertEquals(20, count[0]); + } + + @Test + public void test_ColumnSelect_BatchIterationMultiColumns() throws Exception { + ColumnSelect<Object[]> columnSelect = ObjectSelect.query(Artist.class).columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH); + + try(ResultBatchIterator<Object[]> it = columnSelect.batchIterator(context, 10)) { + List<Object[]> next = it.next(); + assertEquals(10, next.size()); + assertTrue(next.get(0)[0] instanceof String); + assertTrue(next.get(0)[1] instanceof java.util.Date); + } + } + }