Skip to content

Commit bbbf46b

Browse files
committed
Fix delete method matching
1 parent b83597b commit bbbf46b

File tree

4 files changed

+100
-35
lines changed

4 files changed

+100
-35
lines changed

.github/workflows/graalvm-latest.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
branches:
1010
- master
1111
- '[0-9]+.[0-9]+.x'
12+
- fix-delete-matcher
1213
pull_request:
1314
branches:
1415
- master

.github/workflows/gradle.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
branches:
1010
- master
1111
- '[0-9]+.[0-9]+.x'
12+
- fix-delete-matcher
1213
pull_request:
1314
branches:
1415
- master

data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/TypeUtils.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,35 +137,52 @@ public static boolean isEntity(@Nullable ClassElement type) {
137137
}
138138

139139
/**
140-
* Does the given type have an {@link MappedEntity} and is of given type.
141-
* @param type The type
142-
* @param entityType The type to check equality with
143-
* @return True if it does
140+
* Checks if the given type is an entity of the specified type.
141+
*
142+
* A type is considered an entity if it is annotated with {@link MappedEntity} and is not an array.
143+
* The type is considered to be of the given entity type if its name matches or if it is assignable to the given entity type.
144+
*
145+
* @param type The type to check. May be null.
146+
* @param entityType The expected entity type. Must not be null.
147+
* @return true if the type is an entity of the given type, false otherwise.
144148
*/
145149
public static boolean isEntityOfType(@Nullable ClassElement type, @NonNull ClassElement entityType) {
146150
if (type == null) {
147151
return false;
148152
}
149-
if (!isEntity(type)) {
153+
if (type.isArray() || !type.hasStereotype(MappedEntity.class)) {
150154
return false;
151155
}
152156
// Ensure the entity matches the expected type
153157
String expected = entityType.getName();
154158
return type.getName().equals(expected) || type.isAssignable(expected);
155159
}
156160

161+
/**
162+
* Checks if the given type is an iterable (or array) of entities of the specified type.
163+
*
164+
* A type is considered an iterable of entities if it is an array or implements {@link Iterable} and its element type
165+
* is an entity of the given type.
166+
*
167+
* @param type The type to check. May be null.
168+
* @param entityType The expected entity type. Must not be null.
169+
* @return true if the type is an iterable of entities of the given type, false otherwise.
170+
*/
157171
public static boolean isIterableOfEntityType(@Nullable ClassElement type, @NonNull ClassElement entityType) {
158172
if (type == null) {
159173
return false;
160174
}
161-
if (!isIterableOfEntity(type)) {
162-
return false;
175+
ClassElement actualElementType = null;
176+
if (type.isArray() && isEntity(type.fromArray())) {
177+
actualElementType = type.fromArray();
178+
}
179+
if (actualElementType == null) {
180+
actualElementType = type.getFirstTypeArgument().orElse(null);
163181
}
164-
ClassElement elementType = type.getFirstTypeArgument().orElse(null);
165-
if (elementType == null) {
182+
if (actualElementType == null) {
166183
return false;
167184
}
168-
return isEntityOfType(elementType, entityType);
185+
return isEntityOfType(actualElementType, entityType);
169186
}
170187

171188
// /**

data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildDeleteSpec.groovy

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -121,33 +121,8 @@ interface BookRepository extends CrudRepository<Book, Long> {
121121
int deleteAllByIdAndAuthorId(Long id, Long authorId);
122122
123123
int deleteAllByAuthor(Author author);
124-
125-
//void remove(Author author);
126-
127-
//void removeAll(List<Author> authors);
128-
129-
void deleteByAuthor(Author author);
130-
131124
}
132125
""")
133-
/*when:
134-
def removeAuthorMethod = repository.findMethod("remove", Author).get()
135-
then:
136-
removeAuthorMethod
137-
getDataInterceptor(removeAuthorMethod) == "io.micronaut.data.intercept.DeleteAllInterceptor"
138-
getQuery(removeAuthorMethod) == "DELETE FROM `book` WHERE (`author_id` = ?)"
139-
when:
140-
def removeAllAuthorsMethod = repository.findPossibleMethods("removeAll").findFirst().get()
141-
then:
142-
removeAllAuthorsMethod
143-
getDataInterceptor(removeAllAuthorsMethod) == "io.micronaut.data.intercept.DeleteAllInterceptor"
144-
getQuery(removeAllAuthorsMethod) == "DELETE FROM `book` WHERE (`id` IN (?))"*/
145-
when:
146-
def deleteByAuthorMethod = repository.findMethod("deleteByAuthor", Author).get()
147-
then:
148-
deleteByAuthorMethod
149-
getDataInterceptor(deleteByAuthorMethod) == "io.micronaut.data.intercept.DeleteAllInterceptor"
150-
getQuery(deleteByAuthorMethod) == "DELETE FROM `book` WHERE (`author_id` = ?)"
151126
when:
152127
def deleteByIdAndAuthorIdMethod = repository.findPossibleMethods("deleteByIdAndAuthorId").findFirst().get()
153128
then:
@@ -219,6 +194,77 @@ interface BookRepository extends GenericRepository<Book, Long> {
219194
e.message.contains('Cannot delete entities of type: author')
220195
}
221196

197+
void "test build delete with embedded id"() {
198+
given:
199+
def repository = buildRepository('test.CustomerRepository', """
200+
import io.micronaut.data.annotation.Embeddable;
201+
import io.micronaut.data.annotation.EmbeddedId;
202+
import io.micronaut.data.annotation.MappedEntity;
203+
import io.micronaut.data.jdbc.annotation.JdbcRepository;
204+
import io.micronaut.data.model.query.builder.sql.Dialect;
205+
206+
@MappedEntity
207+
class Customer {
208+
@EmbeddedId
209+
private CustomerId customerId;
210+
private String fullName;
211+
private String email;
212+
public CustomerId getCustomerId() {
213+
return customerId;
214+
}
215+
public void setCustomerId(CustomerId customerId) {
216+
this.customerId = customerId;
217+
}
218+
public String getFullName() {
219+
return fullName;
220+
}
221+
public void setFullName(String fullName) {
222+
this.fullName = fullName;
223+
}
224+
public String getEmail() {
225+
return email;
226+
}
227+
public void setEmail(String email) {
228+
this.email = email;
229+
}
230+
}
231+
232+
@Embeddable
233+
@MappedEntity // To test deleteById matching
234+
class CustomerId {
235+
private String regionCode;
236+
private String tenantId;
237+
public String getRegionCode() {
238+
return regionCode;
239+
}
240+
public void setRegionCode(String regionCode) {
241+
this.regionCode = regionCode;
242+
}
243+
public String getTenantId() {
244+
return tenantId;
245+
}
246+
public void setTenantId(String tenantId) {
247+
this.tenantId = tenantId;
248+
}
249+
}
250+
251+
@JdbcRepository(dialect = Dialect.H2)
252+
interface CustomerRepository extends CrudRepository<Customer, CustomerId> {
253+
int deleteAllByCustomerIdRegionCode(String regionCode);
254+
}
255+
""")
256+
when:
257+
def deleteAllByCustomerIdRegionCodeMethod = repository.findPossibleMethods("deleteAllByCustomerIdRegionCode").findFirst().get()
258+
then:
259+
getQuery(deleteAllByCustomerIdRegionCodeMethod) == 'DELETE FROM `customer` WHERE (`customer_id_region_code` = ?)'
260+
getDataInterceptor(deleteAllByCustomerIdRegionCodeMethod) == "io.micronaut.data.intercept.DeleteAllInterceptor"
261+
when:
262+
def deleteByIdMethod = repository.findPossibleMethods("deleteById").findFirst().get()
263+
then:
264+
getQuery(deleteByIdMethod) == 'DELETE FROM `customer` WHERE (`customer_id_region_code` = ? AND `customer_id_tenant_id` = ?)'
265+
getDataInterceptor(deleteByIdMethod) == "io.micronaut.data.intercept.DeleteAllInterceptor"
266+
}
267+
222268
void "test build delete query with DataTransformer"() {
223269
given:
224270
def repository = buildRepository('test.UuidEntityRepository', """

0 commit comments

Comments
 (0)