Skip to content

Commit 15c8f48

Browse files
committed
Issue #22762 generate omitted SELECT clause based on result type
1 parent 38f3789 commit 15c8f48

File tree

8 files changed

+221
-149
lines changed

8 files changed

+221
-149
lines changed

dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/EntityManagerBuilder.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.ibm.websphere.ras.annotation.Trivial;
3737
import com.ibm.ws.ffdc.annotation.FFDCIgnore;
3838

39+
import io.openliberty.data.internal.persistence.cdi.DataExtensionProvider;
3940
import jakarta.persistence.EntityManager;
4041
import jakarta.persistence.metamodel.Attribute;
4142
import jakarta.persistence.metamodel.Attribute.PersistentAttributeType;
@@ -65,13 +66,19 @@ public abstract class EntityManagerBuilder implements Runnable {
6566
*/
6667
final ConcurrentHashMap<Class<?>, CompletableFuture<EntityInfo>> entityInfoMap = new ConcurrentHashMap<>();
6768

69+
/**
70+
* OSGi service component that provides the CDI extension for Data.
71+
*/
72+
final DataExtensionProvider provider;
73+
6874
/**
6975
* The class loader for repository classes.
7076
*/
7177
private final ClassLoader repositoryClassLoader;
7278

7379
@Trivial
74-
protected EntityManagerBuilder(ClassLoader repositoryClassLoader) {
80+
protected EntityManagerBuilder(DataExtensionProvider provider, ClassLoader repositoryClassLoader) {
81+
this.provider = provider;
7582
this.repositoryClassLoader = repositoryClassLoader;
7683
}
7784

dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/QueryInfo.java

Lines changed: 122 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.InvocationTargetException;
2020
import java.lang.reflect.Member;
2121
import java.lang.reflect.Method;
22+
import java.lang.reflect.RecordComponent;
2223
import java.util.ArrayList;
2324
import java.util.Arrays;
2425
import java.util.Iterator;
@@ -398,6 +399,121 @@ else if (hasIdClass && ID.equals(sort.property()))
398399
return combined;
399400
}
400401

402+
/**
403+
* Generates the SELECT clause of the JPQL.
404+
*
405+
* @return the SELECT clause.
406+
*/
407+
StringBuilder generateSelectClause() {
408+
StringBuilder q = new StringBuilder(200);
409+
String o = entityVar;
410+
String o_ = entityVar_;
411+
412+
String[] cols, selections = entityInfo.builder.provider.compat.getSelections(method);
413+
if (selections == null || selections.length == 0) {
414+
cols = null;
415+
} else if (type == QueryInfo.Type.FIND_AND_DELETE) {
416+
// TODO NLS message for error path once selections are supported function
417+
throw new UnsupportedOperationException();
418+
} else {
419+
cols = new String[selections.length];
420+
for (int i = 0; i < cols.length; i++) {
421+
String name = entityInfo.getAttributeName(selections[i], true);
422+
cols[i] = name == null ? selections[i] : name;
423+
}
424+
}
425+
426+
Class<?> singleType = getSingleResultType();
427+
428+
if (singleType.isPrimitive())
429+
singleType = QueryInfo.wrapperClassIfPrimitive(singleType);
430+
431+
q.append("SELECT ");
432+
433+
if (cols == null || cols.length == 0) {
434+
if (singleType.isAssignableFrom(entityInfo.entityClass)
435+
|| entityInfo.inheritance && entityInfo.entityClass.isAssignableFrom(singleType)) {
436+
// Whole entity
437+
q.append(o);
438+
} else {
439+
// Look for single entity attribute with the desired type:
440+
String singleAttributeName = null;
441+
for (Map.Entry<String, Class<?>> entry : entityInfo.attributeTypes.entrySet()) {
442+
Class<?> collectionElementType = entityInfo.collectionElementTypes.get(entry.getKey());
443+
Class<?> attributeType = collectionElementType == null ? entry.getValue() : collectionElementType;
444+
if (attributeType.isPrimitive())
445+
attributeType = QueryInfo.wrapperClassIfPrimitive(attributeType);
446+
if (singleType.isAssignableFrom(attributeType)) {
447+
singleAttributeName = entry.getKey();
448+
q.append(o_).append(singleAttributeName);
449+
break;
450+
}
451+
}
452+
453+
if (singleAttributeName == null) {
454+
// Construct new instance from IdClass, embeddable, or entity attributes.
455+
// It would be preferable if the spec included the Select annotation to explicitly identify parameters, but if that doesn't happen
456+
// TODO we could compare attribute types with known constructor to improve on guessing a correct order of parameters
457+
q.append("NEW ").append(singleType.getName()).append('(');
458+
List<String> relAttrNames;
459+
boolean first = true;
460+
if (entityInfo.idClassAttributeAccessors != null && singleType.equals(entityInfo.idType))
461+
for (String idClassAttributeName : entityInfo.idClassAttributeAccessors.keySet()) {
462+
String name = entityInfo.getAttributeName(idClassAttributeName, true);
463+
q.append(first ? "" : ", ").append(o).append('.').append(name);
464+
first = false;
465+
}
466+
else if ((relAttrNames = entityInfo.relationAttributeNames.get(singleType)) != null)
467+
for (String name : relAttrNames) {
468+
q.append(first ? "" : ", ").append(o).append('.').append(name);
469+
first = false;
470+
}
471+
else if (entityInfo.recordClass == null)
472+
for (String name : entityInfo.attributeTypes.keySet()) {
473+
q.append(first ? "" : ", ").append(o).append('.').append(name);
474+
first = false;
475+
}
476+
else {
477+
for (RecordComponent component : entityInfo.recordClass.getRecordComponents()) {
478+
String name = component.getName();
479+
q.append(first ? "" : ", ").append(o).append('.').append(name);
480+
first = false;
481+
}
482+
}
483+
q.append(')');
484+
}
485+
}
486+
} else { // Individual columns are requested by @Select
487+
Class<?> entityType = entityInfo.getType();
488+
boolean selectAsColumns = singleType.isAssignableFrom(entityType)
489+
|| singleType.isInterface() // NEW instance doesn't apply to interfaces
490+
|| singleType.isPrimitive() // NEW instance should not be used on primitives
491+
|| singleType.getName().startsWith("java") // NEW instance constructor is unlikely for non-user-defined classes
492+
|| entityInfo.inheritance && entityType.isAssignableFrom(singleType);
493+
if (!selectAsColumns && cols.length == 1) {
494+
String singleAttributeName = cols[0];
495+
Class<?> attributeType = entityInfo.collectionElementTypes.get(singleAttributeName);
496+
if (attributeType == null)
497+
attributeType = entityInfo.attributeTypes.get(singleAttributeName);
498+
selectAsColumns = attributeType != null && (Object.class.equals(attributeType) // JPA metamodel does not preserve the type if not an EmbeddableCollection
499+
|| singleType.isAssignableFrom(attributeType));
500+
}
501+
if (selectAsColumns) {
502+
// Specify columns without creating new instance
503+
for (int i = 0; i < cols.length; i++)
504+
q.append(i == 0 ? "" : ", ").append(o).append('.').append(cols[i]);
505+
} else {
506+
// Construct new instance from defined columns
507+
q.append("NEW ").append(singleType.getName()).append('(');
508+
for (int i = 0; i < cols.length; i++)
509+
q.append(i == 0 ? "" : ", ").append(o).append('.').append(cols[i]);
510+
q.append(')');
511+
}
512+
}
513+
514+
return q;
515+
}
516+
401517
/**
402518
* Locate the entity information for the specified result class.
403519
*
@@ -417,7 +533,7 @@ EntityInfo getEntityInfo(Class<?> entityType, Map<String, CompletableFuture<Enti
417533
failedFuture = future;
418534
} else {
419535
EntityInfo info = future.join();
420-
if (entityType.equals(info.entityClass))
536+
if (entityType.equals(info.entityClass) || entityType.equals(info.recordClass))
421537
return info;
422538
}
423539
if (failedFuture != null)
@@ -930,17 +1046,16 @@ else if (order0 >= 0 && orderLen == 0)
9301046
whereLen += 2 + (addSpace ? 1 : 0);
9311047
}
9321048

933-
StringBuilder q = new StringBuilder(ql.length() + (selectLen >= 0 ? 0 : 50) + (fromLen >= 0 ? 0 : 50) + 2);
934-
q.append("SELECT");
1049+
StringBuilder q;
9351050
if (selectLen > 0) {
1051+
q = new StringBuilder(ql.length() + (selectLen >= 0 ? 0 : 50) + (fromLen >= 0 ? 0 : 50) + 2);
1052+
q.append("SELECT");
9361053
appendWithIdentifierName(ql, select0, select0 + selectLen, q);
937-
if (fromLen == 0 && whereLen == 0 && orderLen == 0)
938-
q.append(' ');
9391054
} else {
940-
q.append(' ').append(entityVar).append(' ');
1055+
q = generateSelectClause();
9411056
}
9421057

943-
q.append("FROM");
1058+
q.append(" FROM");
9441059
if (fromLen > 0 && !lacksEntityVar)
9451060
q.append(ql.substring(from0, from0 + fromLen));
9461061
else

0 commit comments

Comments
 (0)