diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/DeleteAction.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/DeleteAction.java index d20c75d..0f27c76 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/DeleteAction.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/action/DeleteAction.java @@ -1,25 +1,6 @@ /* - * Copyright (c) 2024 GeyserMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC + * Copyright (c) 2024 GeyserMC + * Licensed under the MIT license * @link https://github.com/GeyserMC/DatabaseUtils */ package org.geysermc.databaseutils.processor.action; @@ -45,9 +26,11 @@ protected void addToSingle(RepositoryGenerator generator, QueryContext context, @Override protected boolean validateSingle( EntityInfo info, CharSequence methodName, TypeMirror returnType, TypeUtils typeUtils) { - // todo does it also support saying how many items were deleted? - if (!typeUtils.isType(Void.class, returnType)) { - throw new InvalidRepositoryException("Expected Void as return type for %s, got %s", methodName, returnType); + if (!typeUtils.isType(Void.class, returnType) + && !typeUtils.isType(Integer.class, returnType) + && !typeUtils.isType(Boolean.class, returnType)) { + throw new InvalidRepositoryException( + "Expected Void, Integer or Boolean as return type for %s, got %s", methodName, returnType); } return true; } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/type/mongo/MongoRepositoryGenerator.java b/ap/src/main/java/org/geysermc/databaseutils/processor/type/mongo/MongoRepositoryGenerator.java index 98894c0..e97b334 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/type/mongo/MongoRepositoryGenerator.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/type/mongo/MongoRepositoryGenerator.java @@ -156,12 +156,23 @@ public void addUpdate(QueryContext context, MethodSpec.Builder spec) { @Override public void addDelete(QueryContext context, MethodSpec.Builder spec) { wrapInCompletableFuture(spec, context.returnInfo().async(), () -> { + boolean needsUpdatedCount = context.typeUtils().isType(Integer.class, context.returnType()) + || context.typeUtils().isType(Boolean.class, context.returnType()); + if (needsUpdatedCount) { + spec.addStatement("int __count"); + } + // for now, it's only either: delete a (list of) entities, or deleteByAAndB if (context.parametersInfo().isSelf()) { var filter = createFilter(context.entityInfo() .keyColumnsAsFactors( AndFactor.INSTANCE, context.parametersInfo().firstName())); - spec.addStatement("this.collection.deleteOne($L)", filter); + + if (needsUpdatedCount) { + spec.addStatement("__count = this.collection.deleteOne($L).getDeletedCount()", filter); + } else { + spec.addStatement("this.collection.deleteOne($L)", filter); + } } else if (context.parametersInfo().isSelfCollection()) { spec.addStatement( "var __bulkOperations = new $T<$T<$T>>()", @@ -177,13 +188,31 @@ public void addDelete(QueryContext context, MethodSpec.Builder spec) { createFilter(context.entityInfo().keyColumnsAsFactors(AndFactor.INSTANCE, "__entry"))); spec.endControlFlow(); - spec.addStatement("this.collection.bulkWrite(__bulkOperations)"); + if (needsUpdatedCount) { + spec.addStatement("__count = this.collection.bulkWrite(__bulkOperations).getDeletedCount()"); + } else { + spec.addStatement("this.collection.bulkWrite(__bulkOperations)"); + } } else { - spec.addStatement("this.collection.deleteMany($L)", createFilter(context.bySectionFactors())); + if (needsUpdatedCount) { + spec.addStatement( + "__count = (int) this.collection.deleteMany($L).getDeletedCount()", + createFilter(context.bySectionFactors())); + } else { + //todo technically it can be a deleteOne if the factors contain all the key columns + spec.addStatement("this.collection.deleteMany($L)", createFilter(context.bySectionFactors())); + } } - if (context.returnInfo().async()) { + if (context.returnInfo().async() && !needsUpdatedCount) { spec.addStatement("return null"); + return; + } + + if (context.typeUtils().isType(Integer.class, context.returnType())) { + spec.addStatement("return __count"); + } else if (context.typeUtils().isType(Boolean.class, context.returnType())) { + spec.addStatement("return __count > 0"); } }); typeSpec.addMethod(spec.build()); diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/type/sql/SqlRepositoryGenerator.java b/ap/src/main/java/org/geysermc/databaseutils/processor/type/sql/SqlRepositoryGenerator.java index f02968a..977d8f9 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/type/sql/SqlRepositoryGenerator.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/type/sql/SqlRepositoryGenerator.java @@ -40,7 +40,7 @@ import org.geysermc.databaseutils.processor.util.TypeUtils; public final class SqlRepositoryGenerator extends RepositoryGenerator { - private static final int BATCH_SIZE = 250; + private static final int BATCH_SIZE = 500; public SqlRepositoryGenerator() { super(DatabaseCategory.SQL); @@ -160,7 +160,15 @@ private void addExecuteQueryData( private void addUpdateQueryData(MethodSpec.Builder spec, QueryContext context, QueryBuilder builder) { addBySectionData(spec, context, builder, () -> { if (!context.parametersInfo().isSelfCollection()) { - spec.addStatement("__statement.executeUpdate()"); + if (context.typeUtils().isType(Integer.class, context.returnType())) { + spec.addStatement("return __statement.executeUpdate()"); + return; + } else if (context.typeUtils().isType(Boolean.class, context.returnType())) { + spec.addStatement("return __statement.executeUpdate() > 0"); + return; + } else { + spec.addStatement("__statement.executeUpdate()"); + } } if (context.typeUtils().isType(Void.class, context.returnType())) { @@ -168,6 +176,10 @@ private void addUpdateQueryData(MethodSpec.Builder spec, QueryContext context, Q } else if (context.typeUtils().isType(context.entityType().asType(), context.returnType())) { // todo support also creating an entity type from the given parameters spec.addStatement("return $L", context.parametersInfo().firstName()); + } else if (context.typeUtils().isType(Integer.class, context.returnType())) { + spec.addStatement("return __updateCount"); + } else if (context.typeUtils().isType(Boolean.class, context.returnType())) { + spec.addStatement("return __updateCount > 0"); } else { throw new InvalidRepositoryException( "Return type can be either void or %s but got %s", @@ -180,6 +192,11 @@ private void addBySectionData( MethodSpec.Builder spec, QueryContext context, QueryBuilder builder, Runnable execute) { wrapInCompletableFuture(spec, context.returnInfo().async(), () -> { spec.beginControlFlow("try ($T __connection = this.dataSource.getConnection())", Connection.class); + + if (context.parametersInfo().isSelfCollection()) { + spec.addStatement("__connection.setAutoCommit(false)"); + } + spec.beginControlFlow( "try ($T __statement = __connection.prepareStatement($S))", PreparedStatement.class, @@ -190,8 +207,14 @@ private void addBySectionData( parameterName = context.parametersInfo().firstName(); } + boolean needsUpdatedCount = context.typeUtils().isType(Integer.class, context.returnType()) + || context.typeUtils().isType(Boolean.class, context.returnType()); + if (context.parametersInfo().isSelfCollection()) { spec.addStatement("int __count = 0"); + if (needsUpdatedCount) { + spec.addStatement("int __updateCount = 0"); + } spec.beginControlFlow("for (var __element : $L)", parameterName); parameterName = "__element"; } @@ -216,12 +239,13 @@ private void addBySectionData( if (context.parametersInfo().isSelfCollection()) { spec.addStatement("__statement.addBatch()"); - spec.beginControlFlow("if (__count % $L == 0)", BATCH_SIZE); - spec.addStatement("__statement.executeBatch()"); + spec.beginControlFlow("if (++__count % $L == 0)", BATCH_SIZE); + executeBatchAndUpdateUpdateCount(spec, needsUpdatedCount); spec.endControlFlow(); spec.endControlFlow(); - spec.addStatement("__statement.executeBatch()"); + + executeBatchAndUpdateUpdateCount(spec, needsUpdatedCount); spec.addStatement("__connection.commit()"); } @@ -241,6 +265,23 @@ private void addBySectionData( typeSpec.addMethod(spec.build()); } + private void executeBatchAndUpdateUpdateCount(MethodSpec.Builder spec, boolean needsUpdatedCount) { + if (!needsUpdatedCount) { + spec.addStatement("__statement.executeBatch()"); + return; + } + + // todo this can also be used to check which items were and weren't inserted etc. + spec.addStatement("int[] __affected = __statement.executeBatch()"); + spec.beginControlFlow("for (int __updated : __affected)"); + + spec.beginControlFlow("if (__updated > 0)"); + spec.addStatement("__updateCount += __updated"); + spec.endControlFlow(); + + spec.endControlFlow(); + } + private String createSetFor(QueryContext context, QueryBuilder builder) { if (context.projection() != null && context.projection().columnName() != null) { List columns = diff --git a/ap/src/test/resources/test/advanced/AdvancedRepository.java b/ap/src/test/resources/test/advanced/AdvancedRepository.java index 0b4e2a3..4c47e0d 100644 --- a/ap/src/test/resources/test/advanced/AdvancedRepository.java +++ b/ap/src/test/resources/test/advanced/AdvancedRepository.java @@ -12,4 +12,8 @@ public interface AdvancedRepository extends IRepository { CompletableFuture existsByAOrB(int a, String bb); void updateCByBAndC(String newValue, String b, String c); + + CompletableFuture deleteByAAndB(int a, String b); + + Integer deleteByAAndC(int a, String c); } \ No newline at end of file diff --git a/ap/src/test/resources/test/advanced/AdvancedRepositoryMongoImpl.java b/ap/src/test/resources/test/advanced/AdvancedRepositoryMongoImpl.java index 242f1f6..1617cf8 100644 --- a/ap/src/test/resources/test/advanced/AdvancedRepositoryMongoImpl.java +++ b/ap/src/test/resources/test/advanced/AdvancedRepositoryMongoImpl.java @@ -3,6 +3,7 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.model.Filters; import java.lang.Boolean; +import java.lang.Integer; import java.lang.Override; import java.lang.String; import java.util.UUID; @@ -43,4 +44,20 @@ public CompletableFuture existsByAOrB(int a, String bb) { public void updateCByBAndC(String newValue, String b, String c) { this.collection.updateMany(Filters.and(Filters.eq("b", b), Filters.eq("c", c)), new Document("c", newValue)); } + + @Override + public CompletableFuture deleteByAAndB(int a, String b) { + return CompletableFuture.supplyAsync(() -> { + int __count; + __count = (int) this.collection.deleteMany(Filters.and(Filters.eq("a", a), Filters.eq("b", b))).getDeletedCount(); + return __count > 0; + } , this.database.executorService()); + } + + @Override + public Integer deleteByAAndC(int a, String c) { + int __count; + __count = (int) this.collection.deleteMany(Filters.and(Filters.eq("a", a), Filters.eq("c", c))).getDeletedCount(); + return __count; + } } \ No newline at end of file diff --git a/ap/src/test/resources/test/advanced/AdvancedRepositorySqlImpl.java b/ap/src/test/resources/test/advanced/AdvancedRepositorySqlImpl.java index f2a0980..07537e7 100644 --- a/ap/src/test/resources/test/advanced/AdvancedRepositorySqlImpl.java +++ b/ap/src/test/resources/test/advanced/AdvancedRepositorySqlImpl.java @@ -84,4 +84,32 @@ public void updateCByBAndC(String newValue, String b, String c) { throw new CompletionException("Unexpected error occurred", __exception); } } + + @Override + public CompletableFuture deleteByAAndB(int a, String b) { + return CompletableFuture.supplyAsync(() -> { + try (Connection __connection = this.dataSource.getConnection()) { + try (PreparedStatement __statement = __connection.prepareStatement("delete from hello where a=? and b=?")) { + __statement.setInt(1, a); + __statement.setString(2, b); + return __statement.executeUpdate() > 0; + } + } catch (SQLException __exception) { + throw new CompletionException("Unexpected error occurred", __exception); + } + } , this.database.executorService()); + } + + @Override + public Integer deleteByAAndC(int a, String c) { + try (Connection __connection = this.dataSource.getConnection()) { + try (PreparedStatement __statement = __connection.prepareStatement("delete from hello where a=? and c=?")) { + __statement.setInt(1, a); + __statement.setString(2, c); + return __statement.executeUpdate(); + } + } catch (SQLException __exception) { + throw new CompletionException("Unexpected error occurred", __exception); + } + } } diff --git a/ap/src/test/resources/test/basic/BasicRepositorySqlImpl.java b/ap/src/test/resources/test/basic/BasicRepositorySqlImpl.java index 675d4fd..3acca24 100644 --- a/ap/src/test/resources/test/basic/BasicRepositorySqlImpl.java +++ b/ap/src/test/resources/test/basic/BasicRepositorySqlImpl.java @@ -112,6 +112,7 @@ public CompletableFuture existsByB(String b) { @Override public void update(List entity) { try (Connection __connection = this.dataSource.getConnection()) { + __connection.setAutoCommit(false); try (PreparedStatement __statement = __connection.prepareStatement("update hello set c=?,d=? where a=? and b=?")) { int __count = 0; for (var __element : entity) { @@ -120,7 +121,7 @@ public void update(List entity) { __statement.setInt(3, __element.a()); __statement.setString(4, __element.b()); __statement.addBatch(); - if (__count % 250 == 0) { + if (++__count % 500 == 0) { __statement.executeBatch(); } } @@ -173,6 +174,7 @@ public CompletableFuture insert(TestEntity entity) { @Override public void insert(List entities) { try (Connection __connection = this.dataSource.getConnection()) { + __connection.setAutoCommit(false); try (PreparedStatement __statement = __connection.prepareStatement("insert into hello (a,b,c,d) values (?,?,?,?)")) { int __count = 0; for (var __element : entities) { @@ -181,7 +183,7 @@ public void insert(List entities) { __statement.setString(3, __element.c()); __statement.setBytes(4, this.__d.encode(__element.d())); __statement.addBatch(); - if (__count % 250 == 0) { + if (++__count % 500 == 0) { __statement.executeBatch(); } } @@ -216,13 +218,14 @@ public CompletableFuture delete(TestEntity entity) { @Override public void delete(List entities) { try (Connection __connection = this.dataSource.getConnection()) { + __connection.setAutoCommit(false); try (PreparedStatement __statement = __connection.prepareStatement("delete from hello where a=? and b=?")) { int __count = 0; for (var __element : entities) { __statement.setInt(1, __element.a()); __statement.setString(2, __element.b()); __statement.addBatch(); - if (__count % 250 == 0) { + if (++__count % 500 == 0) { __statement.executeBatch(); } }