From c94599b89ca043c073265a1eea49a5b7cc6551a6 Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Tue, 17 Sep 2024 09:05:06 -0500 Subject: [PATCH 1/4] Issue #22762 entity where multiple attributes are the same embeddable type --- .../persistence/service/DBStoreEMBuilder.java | 314 +++++++++++------- 1 file changed, 202 insertions(+), 112 deletions(-) diff --git a/dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/service/DBStoreEMBuilder.java b/dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/service/DBStoreEMBuilder.java index 7611d362515..f37004291a9 100644 --- a/dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/service/DBStoreEMBuilder.java +++ b/dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/service/DBStoreEMBuilder.java @@ -24,6 +24,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.RecordComponent; import java.sql.Connection; +import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Collection; import java.util.Dictionary; @@ -33,6 +34,7 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; +import java.util.Map.Entry; import java.util.Queue; import java.util.Set; import java.util.SortedMap; @@ -83,6 +85,8 @@ public class DBStoreEMBuilder extends EntityManagerBuilder implements DDLGenerationParticipant { static final String EOLN = String.format("%n"); private static final long MAX_WAIT_FOR_SERVICE_NS = TimeUnit.SECONDS.toNanos(60); + private static final Entry ID_AND_VERSION_NOT_SPECIFIED = // + new SimpleImmutableEntry<>(null, null); private static final TraceComponent tc = Tr.register(DBStoreEMBuilder.class); private final ClassDefiner classDefiner = new ClassDefiner(); @@ -310,8 +314,6 @@ public DBStoreEMBuilder(DataProvider provider, ClassLoader repositoryClassLoader // XML to make all other classes into JPA entities: ArrayList entityClassInfo = new ArrayList<>(entityTypes.size()); - Queue> embeddableTypesQueue = new LinkedList<>(); - /* * Note: When creating a persistence unit, managed classes (such as entities) are declared in an * all or nothing fashion. Therefore, if we create a persistence unit with a list of entities @@ -322,6 +324,8 @@ public DBStoreEMBuilder(DataProvider provider, ClassLoader repositoryClassLoader */ Set> converterTypes = new HashSet<>(); + Set> embeddableTypes = new HashSet<>(); + for (Class c : entityTypes) { if (c.isAnnotationPresent(Entity.class)) { annotatedEntityClassQueue.add(c); @@ -351,7 +355,7 @@ public DBStoreEMBuilder(DataProvider provider, ClassLoader repositoryClassLoader xml.append(" ").append(EOLN); - writeAttributes(xml, c, false, embeddableTypesQueue); + writeAttributes(xml, c, embeddableTypes); xml.append(" ").append(EOLN); @@ -359,17 +363,21 @@ public DBStoreEMBuilder(DataProvider provider, ClassLoader repositoryClassLoader } } - Set> embeddableTypes = new HashSet<>(); - for (Class type; (type = embeddableTypesQueue.poll()) != null;) - if (embeddableTypes.add(type)) { // only write each type once - StringBuilder xml = new StringBuilder(500).append(" ").append(EOLN); - writeAttributes(xml, type, true, embeddableTypesQueue); - xml.append(" ").append(EOLN); - entityClassInfo.add(xml.toString()); - } + for (Class type : embeddableTypes) { + StringBuilder xml = new StringBuilder(500) // + .append(" ") // + .append(EOLN); + writeAttributes(xml, type, null); + xml.append(" ").append(EOLN); + entityClassInfo.add(xml.toString()); + } for (Class type : converterTypes) { - StringBuilder xml = new StringBuilder(500).append(" ").append(EOLN); + StringBuilder xml = new StringBuilder(500) // + .append(" ") // + .append(EOLN); entityClassInfo.add(xml.toString()); } @@ -428,6 +436,110 @@ public EntityManager createEntityManager() { return persistenceServiceUnit.createEntityManager(); } + /** + * Find attributes of the specified class. + * If a record, use the record components. + * Otherwise, use fields and property descriptors. + * + * @param c entity class or embedded class. + * @return attributes, sorted alphabetically. + */ + private SortedMap> findAttributes(Class c) { + SortedMap> attributes = new TreeMap<>(); + + if (c.isRecord()) { + for (RecordComponent r : c.getRecordComponents()) + attributes.put(r.getName(), r.getType()); + } else { + for (Field f : c.getFields()) + attributes.put(f.getName(), f.getType()); + + try { + PropertyDescriptor[] propertyDescriptors = Introspector // + .getBeanInfo(c).getPropertyDescriptors(); + if (propertyDescriptors != null) + for (PropertyDescriptor p : propertyDescriptors) { + Method setter = p.getWriteMethod(); + if (setter != null) + attributes.putIfAbsent(p.getName(), + p.getPropertyType()); + } + } catch (IntrospectionException x) { + throw new MappingException(x); + } + } + + return attributes; + } + + /** + * Find the Id and Version attributes (if any). + * + * @param attributes all top level entity attributes. + * @return the Id and Version attributes (if any). + * Null if there is no Id. + */ + private Entry findIdAndVersion(SortedMap> attributes) { + String idAttrName = null; + String versionAttrName = null; + + // Determine which attribute is the id and version (optional). + // Id precedence: + // (1) name is id, ignoring case. + // (2) name ends with _id, ignoring case. + // (3) name ends with Id or ID. + // (4) type is UUID. + // Version precedence (if also a valid version type): + // (1) name is version, ignoring case. + // (2) name is _version, ignoring case. + int idPrecedence = 10; + int vPrecedence = 10; + for (Map.Entry> attribute : attributes.entrySet()) { + String name = attribute.getKey(); + Class type = attribute.getValue(); + int len = name.length(); + + if (idPrecedence > 1 && + len >= 2 && + name.regionMatches(true, len - 2, "id", 0, 2)) { + if (name.length() == 2) { + idAttrName = name; + idPrecedence = 1; + } else if (idPrecedence > 2 && + name.charAt(len - 3) == '_') { + idAttrName = name; + idPrecedence = 2; + } else if (idPrecedence > 3 && + name.charAt(len - 2) == 'I') { + idAttrName = name; + idPrecedence = 3; + } + } else if (idPrecedence > 4 && UUID.class.equals(type)) { + idAttrName = name; + idPrecedence = 4; + } + + if (vPrecedence > 1 && + len == 7 && + QueryInfo.VERSION_TYPES.contains(type) && + "version".equalsIgnoreCase(name)) { + versionAttrName = name; + vPrecedence = 1; + } else if (vPrecedence > 2 && + len == 8 && + QueryInfo.VERSION_TYPES.contains(type) && + "_version".equalsIgnoreCase(name)) { + versionAttrName = name; + vPrecedence = 2; + } + } + + return idAttrName == null // + ? null // + : new SimpleImmutableEntry<>(idAttrName, versionAttrName); + } + /** * Obtains the DataSource that is used by the EntityManager. * This method is used by resource accessor methods of a repository. @@ -548,94 +660,21 @@ public String toString() { /** * Write attributes for the specified entity or embeddable to XML. * - * @param xml XML for defining the entity attributes - * @param c entity class (never a record), or - * embeddable class (can be a record) - * @param isEmbeddable indicates if the class is an embeddable type rather than an entity. - * @param embeddableTypesQueue queue of embeddable types. This method adds to the queue when an embeddable type is found. + * @param xml XML for defining the entity attributes + * @param c entity class (never a record), or + * embeddable class (can be a record) + * @param embeddableTypes embeddable types. When non-null, this method adds + * embeddable types that are found. */ - private void writeAttributes(StringBuilder xml, Class c, boolean isEmbeddable, Queue> embeddableTypesQueue) { - + private void writeAttributes(StringBuilder xml, + Class c, + Set> embeddableTypes) { // Identify attributes - SortedMap> attributes = new TreeMap<>(); + SortedMap> attributes = findAttributes(c); - if (isEmbeddable && c.isRecord()) { - for (RecordComponent r : c.getRecordComponents()) - attributes.putIfAbsent(r.getName(), r.getType()); - } else { - for (Field f : c.getFields()) - attributes.putIfAbsent(f.getName(), f.getType()); - - try { - PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(c).getPropertyDescriptors(); - if (propertyDescriptors != null) - for (PropertyDescriptor p : propertyDescriptors) { - Method setter = p.getWriteMethod(); - if (setter != null) - attributes.putIfAbsent(p.getName(), p.getPropertyType()); - } - } catch (IntrospectionException x) { - throw new MappingException(x); - } - } - - String keyAttributeName = null; - String versionAttributeName = null; - if (!isEmbeddable) { - // Determine which attribute is the id and version (optional). - // Id precedence: - // (1) name is id, ignoring case. - // (2) name ends with _id, ignoring case. - // (3) name ends with Id or ID. - // (4) type is UUID. - // Version precedence (if also a valid version type): - // (1) name is version, ignoring case. - // (2) name is _version, ignoring case. - int idPrecedence = 10; - int vPrecedence = 10; - for (Map.Entry> attribute : attributes.entrySet()) { - String name = attribute.getKey(); - Class type = attribute.getValue(); - int len = name.length(); - - if (idPrecedence > 1 && - len >= 2 && - name.regionMatches(true, len - 2, "id", 0, 2)) { - if (name.length() == 2) { - keyAttributeName = name; - idPrecedence = 1; - } else if (idPrecedence > 2 && - name.charAt(len - 3) == '_') { - keyAttributeName = name; - idPrecedence = 2; - } else if (idPrecedence > 3 && - name.charAt(len - 2) == 'I') { - keyAttributeName = name; - idPrecedence = 3; - } - } else if (idPrecedence > 4 && UUID.class.equals(type)) { - keyAttributeName = name; - idPrecedence = 4; - } - - if (vPrecedence > 1 && - len == 7 && - QueryInfo.VERSION_TYPES.contains(type) && - "version".equalsIgnoreCase(name)) { - versionAttributeName = name; - vPrecedence = 1; - } else if (vPrecedence > 2 && - len == 8 && - QueryInfo.VERSION_TYPES.contains(type) && - "_version".equalsIgnoreCase(name)) { - versionAttributeName = name; - vPrecedence = 2; - } - } - - if (keyAttributeName == null) - throw new MappingException("Entity class " + c.getName() + " lacks a public field of the form *ID or public method of the form get*ID."); // TODO NLS - } + Entry idAndVersion = embeddableTypes == null // + ? ID_AND_VERSION_NOT_SPECIFIED // + : findIdAndVersion(attributes); // Write the attributes to XML: @@ -646,20 +685,33 @@ private void writeAttributes(StringBuilder xml, Class c, boolean isEmbeddable Class attributeType = attributeInfo.getValue(); boolean isCollection = Collection.class.isAssignableFrom(attributeType); boolean isPrimitive = attributeType.isPrimitive(); - boolean isId = attributeName.equals(keyAttributeName); + boolean isId = attributeName.equals(idAndVersion.getKey()); String columnType; - if (isPrimitive || attributeType.isInterface() || Serializable.class.isAssignableFrom(attributeType)) { - columnType = isId ? "id" : // - isCollection ? "element-collection" : // - attributeName.equals(versionAttributeName) ? "version" : // - "basic"; + if (isPrimitive || // + attributeType.isInterface() || // + Serializable.class.isAssignableFrom(attributeType)) { + if (isId) + columnType = "id"; + else if (isCollection) + columnType = "element-collection"; + else if (attributeName.equals(idAndVersion.getValue())) + columnType = "version"; + else + columnType = "basic"; } else { - columnType = isId ? "embedded-id" : "embedded"; - embeddableTypesQueue.add(attributeType); + if (isId) + columnType = "embedded-id"; + else + columnType = "embedded"; + + // TODO move this to after pop from stack, change embeddableTypes to map of class to info + if (embeddableTypes != null) + embeddableTypes.add(attributeType); } - xml.append(" <").append(columnType).append(" name=\"").append(attributeName).append('"'); + xml.append(" <").append(columnType).append(" name=\"") // + .append(attributeName).append('"'); // All other queries when using un-annotated entities or record entities are eager, // element-collections should be as well. @@ -668,14 +720,52 @@ private void writeAttributes(StringBuilder xml, Class c, boolean isEmbeddable xml.append('>').append(EOLN); - if (isEmbeddable) { - if (!"embedded".equals(columnType)) - xml.append(" ").append(EOLN); - } else { - if (isPrimitive) - xml.append(" ").append(EOLN); + if (embeddableTypes != null && // top level entity attribute + columnType.charAt(1) == 'm') { // embedded or embedded-id + LinkedList>>> stack = // + new LinkedList<>(); + stack.add(new SimpleImmutableEntry<>( // + new String[] { attributeName }, // + findAttributes(attributeType))); + for (Entry>> emb; // + null != (emb = stack.pollLast());) { + String[] names = emb.getKey(); + SortedMap> embeddableAttrs = emb.getValue(); + for (Entry> a : embeddableAttrs.entrySet()) { + String name = a.getKey(); + Class type = a.getValue(); + // attribute-override is only written for leaf-level + if (type.isPrimitive() || + type.isInterface() || + Serializable.class.isAssignableFrom(type)) { + xml.append(" ").append(EOLN); + xml.append(" ").append(EOLN); + xml.append(" ").append(EOLN); + // TODO reject column name collisions? + // collisions are currently only possible if an attribute name includes _ + } else { + embeddableTypes.add(type); + String[] names2 = new String[names.length + 1]; + System.arraycopy(names, 0, names2, 0, names.length); + names2[names.length] = name; + stack.add(new SimpleImmutableEntry<>( // + names2, // + findAttributes(type))); + } + } + } } + if (isPrimitive) + xml.append(" ").append(EOLN); + xml.append(" ").append(EOLN); } From 34c652114a41d6e640728276649b0b521574bab8 Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Tue, 17 Sep 2024 09:05:44 -0500 Subject: [PATCH 2/4] Issue #22762 tests for entity where multiple attributes are the same embeddable type --- .../src/test/jakarta/data/web/Cylinder.java | 91 +++++++++++++++++++ .../src/test/jakarta/data/web/Cylinders.java | 54 +++++++++++ .../jakarta/data/web/DataTestServlet.java | 37 ++++++++ 3 files changed, 182 insertions(+) create mode 100644 dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinder.java create mode 100644 dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinders.java diff --git a/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinder.java b/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinder.java new file mode 100644 index 00000000000..b3c3e06ed81 --- /dev/null +++ b/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinder.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2024 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package test.jakarta.data.web; + +/** + * Entity that is a Java record with multiple of the same type of embeddable + * in its hierarchy. + */ +public record Cylinder( + String cylID, // TODO why does this fail if named cID ? + Coordinate center, + Side side) { + + public static class Coordinate { + public int x, y; + + public Coordinate() { + } + + public Coordinate(int x, int y) { + this.x = x; + this.y = y; + } + + public int x() { + return x; + } + + public int y() { + return y; + } + + @Override + public String toString() { + return "Coordinate {x=" + x + ", y=" + y + "}"; + } + } + + public static class Side { + public Coordinate a, b; + + public Side() { + } + + public Side(Coordinate a, Coordinate b) { + this.a = a; + this.b = b; + } + + public Coordinate a() { + return a; + } + + public Coordinate b() { + return b; + } + + @Override + public String toString() { + return "Side {a=" + a + ", b=" + b + "}"; + } + } + + // TODO switch the above to the following once #29460 is fixed + //public static record Coordinate( + // int x, + // int y) { + //} + + //public static record Side( + // Coordinate a, + // Coordinate b) { + //} + + public Cylinder(String cylinderID, + int aX, int aY, + int bX, int bY, + int centerX, int centerY) { + this(cylinderID, // + new Coordinate(centerX, centerY), // + new Side(new Coordinate(aX, aY), new Coordinate(bX, bY))); + } +} diff --git a/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinders.java b/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinders.java new file mode 100644 index 00000000000..845e3dc9ef1 --- /dev/null +++ b/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinders.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2024 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package test.jakarta.data.web; + +import static jakarta.data.repository.By.ID; + +import java.util.stream.Stream; + +import jakarta.data.repository.DataRepository; +import jakarta.data.repository.Delete; +import jakarta.data.repository.Find; +import jakarta.data.repository.OrderBy; +import jakarta.data.repository.Query; +import jakarta.data.repository.Repository; +import jakarta.data.repository.Save; + +/** + * Repository interface for the DirectedSegement entity, which is + * a Java record with multiple of the same type of embeddable. + */ +@Repository +public interface Cylinders + // TODO remove the superinterface. It should not be necessary due to @Save + extends DataRepository { + + @Find + @OrderBy("side.b.y") + @OrderBy("side_b_x") + @OrderBy(ID) + Stream centeredAt(int centerX, int center_y); + + @Query("SELECT COUNT(THIS) " + + " WHERE (side.a.y - side.b.y) * (side.a.y + side.b.y - 2 * center.y)" + + " = (side.b.x - side.a.x) * (side.a.x + side.b.x - 2 * center.x)") + int countValid(); + + @Delete + Long eraseAll(); + + Stream findBySideAXOrSideBXOrderBySideBYDesc(int x1, int x2); + + @Save + void upsert(Cylinder... s); +} diff --git a/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/DataTestServlet.java b/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/DataTestServlet.java index 874a9416c45..586e6537146 100644 --- a/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/DataTestServlet.java +++ b/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/DataTestServlet.java @@ -110,6 +110,9 @@ public class DataTestServlet extends FATServlet { @Inject Apartments apartments; + @Inject + Cylinders cylinders; + @Inject EmptyRepository emptyRepo; @@ -3474,6 +3477,40 @@ public void testMultipleAggregates() { assertEquals(8.0, Math.floor(deque.removeFirst()), 0.01); // average } + /** + * Use a repository that has multiple embeddable attributes of the same type. + */ + @Test + public void testMultipleEmbeddableAttributesOfSameType() { + Cylinder cyl1, cyl2, cyl3, cyl4, cyl5; + + // Id a.x, a.y, b.x, b.y, c.x, c.y + cylinders.upsert(cyl1 = new Cylinder("CYL1", 100, 287, 372, 833, 509, 424), + cyl2 = new Cylinder("CYL2", 790, 857, 942, 143, 509, 424), + cyl3 = new Cylinder("CYL3", 340, 101, 100, 919, 629, 630), + cyl4 = new Cylinder("CYL4", 100, 684, 974, 516, 453, 163), + cyl5 = new Cylinder("CYL5", 412, 983, 276, 413, 629, 630)); + + assertEquals(5, cylinders.countValid()); + + assertEquals(List.of(cyl5.toString(), cyl3.toString()), + cylinders.centeredAt(629, 630) + .map(Object::toString) + .collect(Collectors.toList())); + + assertEquals(List.of(cyl2.toString(), cyl1.toString()), + cylinders.centeredAt(509, 424) + .map(Object::toString) + .collect(Collectors.toList())); + + assertEquals(List.of(cyl3.toString(), cyl1.toString(), cyl4.toString()), + cylinders.findBySideAXOrSideBXOrderBySideBYDesc(100, 100) + .map(Object::toString) + .collect(Collectors.toList())); + + assertEquals(Long.valueOf(5), cylinders.eraseAll()); + } + /** * Use a repository where methods are for different entities. */ From fa2ce4587d3d88fa4279c6a7d7942961bcc8af68 Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Tue, 17 Sep 2024 10:47:15 -0500 Subject: [PATCH 3/4] Issue #22762 improvements to code --- .../persistence/service/DBStoreEMBuilder.java | 59 ++++++++++--------- .../src/test/jakarta/data/web/Cylinders.java | 7 +-- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/service/DBStoreEMBuilder.java b/dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/service/DBStoreEMBuilder.java index f37004291a9..4cc143ba647 100644 --- a/dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/service/DBStoreEMBuilder.java +++ b/dev/io.openliberty.data.internal.persistence/src/io/openliberty/data/internal/persistence/service/DBStoreEMBuilder.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; @@ -324,7 +325,8 @@ public DBStoreEMBuilder(DataProvider provider, ClassLoader repositoryClassLoader */ Set> converterTypes = new HashSet<>(); - Set> embeddableTypes = new HashSet<>(); + Map, Map>> embeddableTypes = // + new LinkedHashMap<>(); for (Class c : entityTypes) { if (c.isAnnotationPresent(Entity.class)) { @@ -355,7 +357,7 @@ public DBStoreEMBuilder(DataProvider provider, ClassLoader repositoryClassLoader xml.append("
").append(EOLN); - writeAttributes(xml, c, embeddableTypes); + writeAttributes(xml, findAttributes(c), embeddableTypes); xml.append(" ").append(EOLN); @@ -363,12 +365,15 @@ public DBStoreEMBuilder(DataProvider provider, ClassLoader repositoryClassLoader } } - for (Class type : embeddableTypes) { + for (Entry, Map>> e : embeddableTypes.entrySet()) { + Class type = e.getKey(); + Map> attrs = e.getValue(); + StringBuilder xml = new StringBuilder(500) // .append(" ") // .append(EOLN); - writeAttributes(xml, type, null); + writeAttributes(xml, attrs, null); xml.append(" ").append(EOLN); entityClassInfo.add(xml.toString()); } @@ -475,12 +480,11 @@ private SortedMap> findAttributes(Class c) { /** * Find the Id and Version attributes (if any). * - * @param attributes all top level entity attributes. + * @param attributes top level entity or embeddable attributes. * @return the Id and Version attributes (if any). * Null if there is no Id. */ - private Entry findIdAndVersion(SortedMap> attributes) { + private Entry findIdAndVersion(Map> attrs) { String idAttrName = null; String versionAttrName = null; @@ -495,7 +499,7 @@ private Entry findIdAndVersion(SortedMap> attribute : attributes.entrySet()) { + for (Map.Entry> attribute : attrs.entrySet()) { String name = attribute.getKey(); Class type = attribute.getValue(); int len = name.length(); @@ -661,16 +665,13 @@ public String toString() { * Write attributes for the specified entity or embeddable to XML. * * @param xml XML for defining the entity attributes - * @param c entity class (never a record), or - * embeddable class (can be a record) + * @param attributes top level entity or embeddable attributes. * @param embeddableTypes embeddable types. When non-null, this method adds * embeddable types that are found. */ private void writeAttributes(StringBuilder xml, - Class c, - Set> embeddableTypes) { - // Identify attributes - SortedMap> attributes = findAttributes(c); + Map> attributes, + Map, Map>> embeddableTypes) { Entry idAndVersion = embeddableTypes == null // ? ID_AND_VERSION_NOT_SPECIFIED // @@ -704,10 +705,6 @@ else if (attributeName.equals(idAndVersion.getValue())) columnType = "embedded-id"; else columnType = "embedded"; - - // TODO move this to after pop from stack, change embeddableTypes to map of class to info - if (embeddableTypes != null) - embeddableTypes.add(attributeType); } xml.append(" <").append(columnType).append(" name=\"") // @@ -722,18 +719,22 @@ else if (attributeName.equals(idAndVersion.getValue())) if (embeddableTypes != null && // top level entity attribute columnType.charAt(1) == 'm') { // embedded or embedded-id - LinkedList>>> stack = // + LinkedList>>> stack = // new LinkedList<>(); + Map> attrs = embeddableTypes.get(attributeType); + if (attrs == null) + embeddableTypes.put(attributeType, + attrs = findAttributes(attributeType)); stack.add(new SimpleImmutableEntry<>( // new String[] { attributeName }, // - findAttributes(attributeType))); - for (Entry>> emb; // + attrs)); + for (Entry>> emb; // null != (emb = stack.pollLast());) { String[] names = emb.getKey(); - SortedMap> embeddableAttrs = emb.getValue(); - for (Entry> a : embeddableAttrs.entrySet()) { - String name = a.getKey(); - Class type = a.getValue(); + Map> embeddableAttrs = emb.getValue(); + for (Entry> e : embeddableAttrs.entrySet()) { + String name = e.getKey(); + Class type = e.getValue(); // attribute-override is only written for leaf-level if (type.isPrimitive() || type.isInterface() || @@ -751,13 +752,13 @@ else if (attributeName.equals(idAndVersion.getValue())) // TODO reject column name collisions? // collisions are currently only possible if an attribute name includes _ } else { - embeddableTypes.add(type); + Map> a = embeddableTypes.get(type); + if (a == null) + embeddableTypes.put(type, a = findAttributes(type)); String[] names2 = new String[names.length + 1]; System.arraycopy(names, 0, names2, 0, names.length); names2[names.length] = name; - stack.add(new SimpleImmutableEntry<>( // - names2, // - findAttributes(type))); + stack.add(new SimpleImmutableEntry<>(names2, a)); } } } diff --git a/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinders.java b/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinders.java index 845e3dc9ef1..98cc18f49bd 100644 --- a/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinders.java +++ b/dev/io.openliberty.data.internal_fat/test-applications/DataTestApp/src/test/jakarta/data/web/Cylinders.java @@ -16,7 +16,6 @@ import java.util.stream.Stream; -import jakarta.data.repository.DataRepository; import jakarta.data.repository.Delete; import jakarta.data.repository.Find; import jakarta.data.repository.OrderBy; @@ -29,15 +28,13 @@ * a Java record with multiple of the same type of embeddable. */ @Repository -public interface Cylinders - // TODO remove the superinterface. It should not be necessary due to @Save - extends DataRepository { +public interface Cylinders { @Find @OrderBy("side.b.y") @OrderBy("side_b_x") @OrderBy(ID) - Stream centeredAt(int centerX, int center_y); + Stream centeredAt(int centerX, int center_y); @Query("SELECT COUNT(THIS) " + " WHERE (side.a.y - side.b.y) * (side.a.y + side.b.y - 2 * center.y)" + From 05a1f3a5497f868b0dc088844b17f406f3e072f4 Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Wed, 18 Sep 2024 08:49:56 -0500 Subject: [PATCH 4/4] Issue #22762 update hard coded column names in a test case per changes --- .../src/test/jakarta/data/ddlgen/web/DDLGenTestServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/io.openliberty.data.internal_fat_ddlgen/test-applications/DDLGenTestApp/src/test/jakarta/data/ddlgen/web/DDLGenTestServlet.java b/dev/io.openliberty.data.internal_fat_ddlgen/test-applications/DDLGenTestApp/src/test/jakarta/data/ddlgen/web/DDLGenTestServlet.java index 2841e1acf9e..9f0dfc39b8d 100644 --- a/dev/io.openliberty.data.internal_fat_ddlgen/test-applications/DDLGenTestApp/src/test/jakarta/data/ddlgen/web/DDLGenTestServlet.java +++ b/dev/io.openliberty.data.internal_fat_ddlgen/test-applications/DDLGenTestApp/src/test/jakarta/data/ddlgen/web/DDLGenTestServlet.java @@ -49,7 +49,7 @@ public class DDLGenTestServlet extends FATServlet { public void executeDDL() throws SQLException { // TODO read from ddlgen output file String[] ddl = new String[] { - "CREATE TABLE dbuser.TESTPartEntity (NAME VARCHAR(255), PRICE FLOAT NOT NULL, VERSION INTEGER NOT NULL, IDENTIFIERVENDOR VARCHAR(255) NOT NULL, IDENTIFIERPARTNUM VARCHAR(255) NOT NULL, PRIMARY KEY (IDENTIFIERVENDOR, IDENTIFIERPARTNUM))" + "CREATE TABLE dbuser.TESTPartEntity (NAME VARCHAR(255), PRICE FLOAT NOT NULL, VERSION INTEGER NOT NULL, ID_VENDOR VARCHAR(255) NOT NULL, ID_PARTNUM VARCHAR(255) NOT NULL, PRIMARY KEY (ID_VENDOR, ID_PARTNUM))" }; try (Connection con = adminDataSource.getConnection()) { Statement stmt = con.createStatement();