You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2008/02/09 15:10:26 UTC
svn commit: r620119 - in
/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src:
main/java/org/apache/cayenne/access/ main/java/org/apache/cayenne/query/
test/java/org/apache/cayenne/access/
Author: aadamchik
Date: Sat Feb 9 06:10:25 2008
New Revision: 620119
URL: http://svn.apache.org/viewvc?rev=620119&view=rev
Log:
CAY-839 Support combination of Persistent objects and scalars in query results
(backend processing of full SQLResultSetMapping)
(also fixes CAY-983 'POST_LOAD called twice' as a side effect of the algorithm change)
Modified:
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/SQLResultSetMapping.java
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextSQLTemplateTest.java
Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java?rev=620119&r1=620118&r2=620119&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java Sat Feb 9 06:10:25 2008
@@ -23,7 +23,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -41,6 +40,8 @@
import org.apache.cayenne.map.LifecycleEvent;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.query.EntityResult;
+import org.apache.cayenne.query.FieldResult;
import org.apache.cayenne.query.ObjectIdQuery;
import org.apache.cayenne.query.PrefetchSelectQuery;
import org.apache.cayenne.query.PrefetchTreeNode;
@@ -124,37 +125,12 @@
}
if (!noObjectConversion) {
-
- if (interceptMappedConversion() != DONE) {
- interceptObjectConversion();
- }
-
- invokePostLoad();
+ interceptObjectConversion();
}
return response;
}
- private void invokePostLoad() {
- // TODO: andrus, 9/21/2006 - this method incorrectly calls "postLoad" when query
- // refresh flag is set to false and object is already there.
-
- LifecycleCallbackRegistry callbackRegistry = domain
- .getEntityResolver()
- .getCallbackRegistry();
-
- if (!callbackRegistry.isEmpty(LifecycleEvent.POST_LOAD)) {
-
- List list = response.firstList();
- if (list != null
- && !list.isEmpty()
- && !(query.getMetaData(domain.getEntityResolver()))
- .isFetchingDataRows()) {
- callbackRegistry.performCallbacks(LifecycleEvent.POST_LOAD, list);
- }
- }
- }
-
private boolean interceptDataDomainQuery() {
if (query instanceof DataDomainQuery) {
response = new ListResponse(domain);
@@ -280,7 +256,8 @@
return DONE;
}
- Collection<Persistent> objects = (Collection<Persistent>) refreshQuery.getObjects();
+ Collection<Persistent> objects = (Collection<Persistent>) refreshQuery
+ .getObjects();
if (objects != null && !objects.isEmpty()) {
Collection<ObjectId> ids = new ArrayList<ObjectId>(objects.size());
@@ -446,113 +423,32 @@
if (context != null && !metadata.isFetchingDataRows()) {
- List mainRows = response.firstList();
+ List<DataRow> mainRows = response.firstList();
if (mainRows != null && !mainRows.isEmpty()) {
- List objects;
- ClassDescriptor descriptor = metadata.getClassDescriptor();
- PrefetchTreeNode prefetchTree = metadata.getPrefetchTree();
-
- // take a shortcut when no prefetches exist...
- if (prefetchTree == null) {
- objects = new ObjectResolver(context, descriptor, metadata
- .isRefreshingObjects(), metadata.isResolvingInherited())
- .synchronizedObjectsFromDataRows(mainRows);
- }
- else {
-
- ObjectTreeResolver resolver = new ObjectTreeResolver(
- context,
- metadata);
- objects = resolver.synchronizedObjectsFromDataRows(
- prefetchTree,
- mainRows,
- prefetchResultsByPath);
- }
+ ObjectConversionStrategy converter;
- if (response instanceof GenericResponse) {
- ((GenericResponse) response).replaceResult(mainRows, objects);
- }
- else if (response instanceof ListResponse) {
- this.response = new ListResponse(objects);
+ SQLResultSetMapping rsMapping = metadata.getResultSetMapping();
+ if (rsMapping == null) {
+ converter = new SingleObjectConversionStrategy();
+ }
+ else if (rsMapping.getEntityResults().size() == 1
+ && rsMapping.getColumnResults().size() == 0) {
+ converter = new SingleObjectConversionStrategy();
+ }
+ else if (rsMapping.getEntityResults().size() == 0
+ && rsMapping.getColumnResults().size() == 1) {
+ converter = new SingleScalarConversionStrategy();
}
else {
- throw new IllegalStateException("Unknown response object: "
- + this.response);
+ converter = new MixedConversionStrategy();
}
- // apply POST_LOAD callback
- LifecycleCallbackRegistry callbackRegistry = context
- .getEntityResolver()
- .getCallbackRegistry();
-
- if (!callbackRegistry.isEmpty(LifecycleEvent.POST_LOAD)) {
- callbackRegistry.performCallbacks(LifecycleEvent.POST_LOAD, objects);
- }
+ converter.convert(mainRows);
}
}
}
- private boolean interceptMappedConversion() {
- SQLResultSetMapping rsMapping = metadata.getResultSetMapping();
-
- if (rsMapping == null) {
- return !DONE;
- }
-
- List mainRows = response.firstList();
- if (mainRows != null && !mainRows.isEmpty()) {
-
- Collection<String> columns = rsMapping.getColumnResults();
- if (columns.isEmpty()) {
- throw new CayenneRuntimeException(
- "Invalid result set mapping, no columns mapped.");
- }
-
- Object[] columnsArray = columns.toArray();
- int rowsLen = mainRows.size();
- int rowWidth = columnsArray.length;
-
- List objects = new ArrayList(rowsLen);
-
- // add scalars to the result
- if (rowWidth == 1) {
-
- for (int i = 0; i < rowsLen; i++) {
- Map row = (Map) mainRows.get(i);
- objects.add(row.get(columnsArray[0]));
- }
- }
- // add Object[]'s to the result
- else {
- for (int i = 0; i < rowsLen; i++) {
- Map row = (Map) mainRows.get(i);
- Object[] rowDecoded = new Object[rowWidth];
-
- for (int j = 0; j < rowWidth; j++) {
- rowDecoded[j] = row.get(columnsArray[j]);
- }
-
- objects.add(rowDecoded);
- }
- }
-
- if (response instanceof GenericResponse) {
- ((GenericResponse) response).replaceResult(mainRows, objects);
- }
- else if (response instanceof ListResponse) {
- this.response = new ListResponse(objects);
- }
- else {
- throw new IllegalStateException("Unknown response object: "
- + this.response);
- }
-
- }
-
- return DONE;
- }
-
public void route(QueryEngine engine, Query query, Query substitutedQuery) {
Collection<Query> queries = null;
@@ -642,5 +538,178 @@
public boolean isIteratedResult() {
return false;
+ }
+
+ abstract class ObjectConversionStrategy {
+
+ abstract void convert(List<DataRow> mainRows);
+
+ protected List<Persistent> toObjects(
+ ClassDescriptor descriptor,
+ PrefetchTreeNode prefetchTree,
+ List<DataRow> normalizedRows) {
+ List<Persistent> objects;
+
+ // take a shortcut when no prefetches exist...
+ if (prefetchTree == null) {
+ objects = new ObjectResolver(context, descriptor, metadata
+ .isRefreshingObjects(), metadata.isResolvingInherited())
+ .synchronizedObjectsFromDataRows(normalizedRows);
+ }
+ else {
+ ObjectTreeResolver resolver = new ObjectTreeResolver(context, metadata);
+ objects = resolver.synchronizedObjectsFromDataRows(
+ prefetchTree,
+ normalizedRows,
+ prefetchResultsByPath);
+ }
+ return objects;
+ }
+
+ protected List<DataRow> toNormalizedDataRows(
+ EntityResult entityMapping,
+ List<DataRow> dataRows) {
+ List<DataRow> normalized = new ArrayList<DataRow>(dataRows.size());
+
+ FieldResult[] fields = entityMapping.getDbFields(domain.getEntityResolver());
+ int rowCapacity = (int) Math.ceil(fields.length / 0.75);
+
+ for (DataRow src : dataRows) {
+ DataRow target = new DataRow(rowCapacity);
+
+ for (FieldResult columnMapping : fields) {
+ target.put(columnMapping.getAttributeName(), src.get(columnMapping
+ .getColumn()));
+ }
+
+ normalized.add(target);
+ }
+
+ return normalized;
+ }
+
+ protected void updateResponse(List sourceObjects, List targetObjects) {
+ if (response instanceof GenericResponse) {
+ ((GenericResponse) response).replaceResult(sourceObjects, targetObjects);
+ }
+ else if (response instanceof ListResponse) {
+ response = new ListResponse(targetObjects);
+ }
+ else {
+ throw new IllegalStateException("Unknown response object: " + response);
+ }
+ }
+ }
+
+ class SingleObjectConversionStrategy extends ObjectConversionStrategy {
+
+ @Override
+ void convert(List<DataRow> mainRows) {
+
+ // convert data rows to standardized format...
+ SQLResultSetMapping rsMapping = metadata.getResultSetMapping();
+ if (rsMapping != null) {
+ // expect 1 and only 1 entityMapping...
+ EntityResult entityMapping = rsMapping.getEntityResults().get(0);
+ mainRows = toNormalizedDataRows(entityMapping, mainRows);
+ }
+
+ ClassDescriptor descriptor = metadata.getClassDescriptor();
+ PrefetchTreeNode prefetchTree = metadata.getPrefetchTree();
+
+ List<Persistent> objects = toObjects(descriptor, prefetchTree, mainRows);
+ updateResponse(mainRows, objects);
+
+ // apply POST_LOAD callback
+ LifecycleCallbackRegistry callbackRegistry = context
+ .getEntityResolver()
+ .getCallbackRegistry();
+
+ if (!callbackRegistry.isEmpty(LifecycleEvent.POST_LOAD)) {
+ callbackRegistry.performCallbacks(LifecycleEvent.POST_LOAD, objects);
+ }
+ }
+ }
+
+ class SingleScalarConversionStrategy extends ObjectConversionStrategy {
+
+ @Override
+ void convert(List<DataRow> mainRows) {
+
+ SQLResultSetMapping rsMapping = metadata.getResultSetMapping();
+
+ List<String> columns = rsMapping.getColumnResults();
+
+ int rowsLen = mainRows.size();
+
+ List objects = new ArrayList(rowsLen);
+ String column = columns.get(0);
+
+ // add scalars to the result
+ for (DataRow row : mainRows) {
+ objects.add(row.get(column));
+ }
+
+ updateResponse(mainRows, objects);
+ }
+ }
+
+ class MixedConversionStrategy extends ObjectConversionStrategy {
+
+ @Override
+ void convert(List<DataRow> mainRows) {
+
+ int rowsLen = mainRows.size();
+ List<Object[]> objects = new ArrayList<Object[]>(rowsLen);
+
+ SQLResultSetMapping rsMapping = metadata.getResultSetMapping();
+
+ List<EntityResult> entities = rsMapping.getEntityResults();
+ List<String> columns = rsMapping.getColumnResults();
+
+ // pass 1 - init Object[]'s and resolve scalars
+
+ int resultWidth = entities.size() + columns.size();
+ int scalarOffset = entities.size();
+
+ for (DataRow row : mainRows) {
+ Object[] resultRow = new Object[resultWidth];
+ for (int i = 0; i < columns.size(); i++) {
+ resultRow[scalarOffset + i] = row.get(columns.get(i));
+ }
+ objects.add(resultRow);
+ }
+
+ // pass 2 - resolve individual object lists
+ List[] resultLists = new List[scalarOffset];
+ for (int i = 0; i < scalarOffset; i++) {
+ EntityResult entityMapping = entities.get(i);
+ List<DataRow> normalized = toNormalizedDataRows(entityMapping, mainRows);
+
+ List<Persistent> nextResult = toObjects(entityMapping
+ .getClassDescriptor(domain.getEntityResolver()), null, normalized);
+
+ for (int j = 0; j < rowsLen; j++) {
+ objects.get(j)[i] = nextResult.get(j);
+ }
+
+ resultLists[i] = nextResult;
+ }
+
+ updateResponse(mainRows, objects);
+
+ // invoke callbacks now that all objects are resolved...
+ LifecycleCallbackRegistry callbackRegistry = context
+ .getEntityResolver()
+ .getCallbackRegistry();
+
+ if (!callbackRegistry.isEmpty(LifecycleEvent.POST_LOAD)) {
+ for (int i = 0; i < scalarOffset; i++) {
+ callbackRegistry.performCallbacks(
+ LifecycleEvent.POST_LOAD,
+ resultLists[i]);
+ }
+ }
+ }
}
}
Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/SQLResultSetMapping.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/SQLResultSetMapping.java?rev=620119&r1=620118&r2=620119&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/SQLResultSetMapping.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/SQLResultSetMapping.java Sat Feb 9 06:10:25 2008
@@ -35,6 +35,7 @@
public class SQLResultSetMapping {
protected String name;
+ protected List<EntityResult> entityResults;
protected List<String> columnResults;
public SQLResultSetMapping() {
@@ -51,6 +52,18 @@
public void setName(String name) {
this.name = name;
+ }
+
+ public List<EntityResult> getEntityResults() {
+ return entityResults != null ? entityResults : Collections.EMPTY_LIST;
+ }
+
+ public void addEntityResult(EntityResult entityResult) {
+ if (entityResults == null) {
+ entityResults = new ArrayList<EntityResult>(3);
+ }
+
+ entityResults.add(entityResult);
}
/**
Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextSQLTemplateTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextSQLTemplateTest.java?rev=620119&r1=620118&r2=620119&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextSQLTemplateTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextSQLTemplateTest.java Sat Feb 9 06:10:25 2008
@@ -34,6 +34,7 @@
import org.apache.cayenne.dba.frontbase.FrontBaseAdapter;
import org.apache.cayenne.dba.openbase.OpenBaseAdapter;
import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.query.EntityResult;
import org.apache.cayenne.query.SQLResultSetMapping;
import org.apache.cayenne.query.SQLTemplate;
import org.apache.cayenne.unit.CayenneCase;
@@ -50,6 +51,49 @@
super.setUp();
deleteTestData();
context = createDataContext();
+ }
+
+ public void testSQLResultSetMappingMixed() throws Exception {
+ createTestData("prepare");
+
+ String sql = "SELECT t0.ARTIST_ID AS X, t0.ARTIST_NAME AS Y, t0.DATE_OF_BIRTH AS Z, count(t1.PAINTING_ID) AS C "
+ + "FROM ARTIST t0 LEFT JOIN PAINTING t1 ON (t0.ARTIST_ID = t1.ARTIST_ID) "
+ + "GROUP BY t0.ARTIST_ID, t0.ARTIST_NAME, t0.DATE_OF_BIRTH "
+ + "ORDER BY t0.ARTIST_ID";
+
+ DataMap map = getDomain().getMap("testmap");
+ SQLTemplate query = new SQLTemplate(map, sql);
+ query.setColumnNamesCapitalization(SQLTemplate.UPPERCASE_COLUMN_NAMES);
+
+ EntityResult artistResult = new EntityResult(Artist.class);
+ artistResult.addDbField(Artist.ARTIST_ID_PK_COLUMN, "X");
+ artistResult.addObjectField(Artist.ARTIST_NAME_PROPERTY, "Y");
+ artistResult.addObjectField(Artist.DATE_OF_BIRTH_PROPERTY, "Z");
+
+ SQLResultSetMapping rsMap = new SQLResultSetMapping();
+ rsMap.addEntityResult(artistResult);
+ rsMap.addColumnResult("C");
+ query.setResultSetMapping(rsMap);
+
+ List objects = createDataContext().performQuery(query);
+ assertEquals(4, objects.size());
+
+ Object o1 = objects.get(0);
+ assertTrue("Expected Object[]: " + o1, o1 instanceof Object[]);
+ Object[] array1 = (Object[]) o1;
+ assertEquals(2, array1.length);
+ Object[] array2 = (Object[]) objects.get(1);
+ assertEquals(2, array2.length);
+ Object[] array3 = (Object[]) objects.get(2);
+ assertEquals(2, array3.length);
+ Object[] array4 = (Object[]) objects.get(3);
+ assertEquals(2, array3.length);
+
+ assertEquals(new Integer(1), array1[1]);
+ assertEquals(new Integer(1), array2[1]);
+ assertEquals(new Integer(0), array3[1]);
+ assertEquals(new Integer(0), array4[1]);
+ assertTrue("Unexpected DataObject: " + array1[0], array1[0] instanceof Artist);
}
public void testSQLResultSetMappingScalar() throws Exception {