Skip to content

Commit

Permalink
Added update tests, added some delete tests among other things
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim203 committed Sep 15, 2024
1 parent 137903a commit d8064f7
Show file tree
Hide file tree
Showing 17 changed files with 484 additions and 91 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ currently examples can be found in the tests of the AP module and the tests of t

# What's left to do?
- make 'simple' actions like `insert` more flexible
- allow it to return whether there was a row added
- support adding every variable of the entity as parameter
- add `save` which either inserts the entity if it's not present or updates the already existing entity
- add `upsert` which either inserts the entity if it's not present or updates the already existing entity
- adding migrations
- and plenty more

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,7 @@ EntityInfo processEntity(TypeMirror typeMirror) {
}

for (int i = 0; i < parameters.size(); i++) {
// todo since we expose the parameters, we might have to box them everywhere to be sure
if (!typeUtils.isTypeWithBoxed(
columns.get(i).asType(), parameters.get(i).asType())) {
if (!typeUtils.isType(columns.get(i).asType(), parameters.get(i).asType())) {
continue constructors;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.List;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.geysermc.databaseutils.processor.info.ColumnInfo;
Expand Down Expand Up @@ -70,6 +71,14 @@ public TypeMirror returnType() {
return returnInfo.type();
}

public TypeMirror countableReturnType() {
if (typeUtils.isWholeNumberType(returnType())) {
return typeUtils.unboxType(returnType());
}
// for Boolean and everything else
return typeUtils.typeUtils().getPrimitiveType(TypeKind.LONG);
}

public ProjectionSection projection() {
return result.projection();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,11 @@ public QueryContextCreator(
this.info = info;
this.typeUtils = typeUtils;

TypeMirror returnType;
TypeMirror returnType = element.getReturnType();
boolean async = false;
if (MoreTypes.isTypeOf(CompletableFuture.class, element.getReturnType())) {
async = true;
returnType = typeUtils.toBoxedMirror(
((DeclaredType) element.getReturnType()).getTypeArguments().get(0));
} else {
returnType = typeUtils.toBoxedMirror(element.getReturnType());
returnType = ((DeclaredType) returnType).getTypeArguments().get(0);
}

this.returnType = returnType;
Expand Down Expand Up @@ -132,11 +129,7 @@ private QueryContext analyseValidateAndCreate() {
}

if (readResult.orderBySection() != null) {
// todo why is handledInputs increased here?
validateColumnNames(
readResult.orderBySection().factors(),
SectionType.ORDER_BY,
($, $$) -> handledInputs.incrementAndGet());
validateColumnNames(readResult.orderBySection().factors(), SectionType.ORDER_BY, null);
}

if (returnTypeInfo.isAnySelf() && !action.allowReturnAnySelfOrColumn()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.ReplaceOneModel;
import com.mongodb.client.model.Sorts;
import com.mongodb.client.model.Updates;
import com.mongodb.client.model.WriteModel;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
Expand All @@ -21,7 +22,6 @@
import java.util.List;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.databaseutils.DatabaseCategory;
Expand Down Expand Up @@ -138,32 +138,61 @@ public void addUpdate(QueryContext context, MethodSpec.Builder spec) {

if (context.parametersInfo().isSelf()) {
var name = context.parametersInfo().firstName();
spec.addStatement(
"this.collection.replaceOne($L, $L)",
createFilter(context.entityInfo().keyColumnsAsFactors(AndFactor.INSTANCE, name)),
name);
var filter = createFilter(context.entityInfo().keyColumnsAsFactors(AndFactor.INSTANCE, name));
if (context.typeUtils().isWholeNumberType(context.returnType())) {
spec.addStatement(
"return ($T) this.collection.replaceOne($L, $L).getModifiedCount()",
context.countableReturnType(),
filter,
name);
return;
}
spec.addStatement("this.collection.replaceOne($L, $L)", filter, name);
} else if (context.parametersInfo().isSelfCollection()) {
var name = context.parametersInfo().firstName();

spec.beginControlFlow("if ($L.isEmpty())", name);
if (context.typeUtils().isWholeNumberType(context.returnType())) {
spec.addStatement("return 0");
} else {
spec.addStatement("return $L", context.returnInfo().async() ? "null" : "");
}
spec.endControlFlow();

spec.addStatement(
"var __bulkOperations = new $T<$T<$T>>()",
ArrayList.class,
WriteModel.class,
context.entityType());

spec.beginControlFlow(
"for (var __entry : $L)", context.parametersInfo().firstName());
spec.beginControlFlow("for (var __entry : $L)", name);
spec.addStatement(
"__bulkOperations.add(new $T<>($L, $L))",
ReplaceOneModel.class,
createFilter(context.entityInfo().keyColumnsAsFactors(AndFactor.INSTANCE, "__entry")),
"__entry");
spec.endControlFlow();

// todo for all these casts, don't add it if it's considered redundant (compile warning)
if (context.typeUtils().isWholeNumberType(context.returnType())) {
spec.addStatement(
"return ($T) this.collection.bulkWrite(__bulkOperations).getModifiedCount()",
context.countableReturnType());
return;
}
spec.addStatement("this.collection.bulkWrite(__bulkOperations)");
} else {
spec.addStatement(
"this.collection.updateMany($L, $L)",
createFilter(context.bySectionFactors()),
createDocument(context.parametersInfo().remaining()));
var filter = createFilter(context.bySectionFactors());
var document = createUpdateDocument(context.parametersInfo().remaining());
if (context.typeUtils().isWholeNumberType(context.returnType())) {
spec.addStatement(
"return ($T) this.collection.updateMany($L, $L).getModifiedCount()",
context.countableReturnType(),
filter,
document);
return;
}
spec.addStatement("this.collection.updateMany($L, $L)", filter, document);
}

if (context.returnInfo().async()) {
Expand All @@ -179,7 +208,7 @@ public void addDelete(QueryContext context, MethodSpec.Builder spec) {
boolean needsUpdatedCount = context.typeUtils().isType(Integer.class, context.returnType())
|| context.typeUtils().isType(Boolean.class, context.returnType());
if (needsUpdatedCount) {
spec.addStatement("int __count");
spec.addStatement("$T __count", context.countableReturnType());
}

// for now, it's only either: delete a (list of) entities, or deleteByAAndB
Expand Down Expand Up @@ -218,7 +247,8 @@ public void addDelete(QueryContext context, MethodSpec.Builder spec) {
} else {
if (!context.hasProjection() && needsUpdatedCount) {
spec.addStatement(
"__count = (int) this.collection.deleteMany($L).getDeletedCount()",
"__count = ($T) this.collection.deleteMany($L).getDeletedCount()",
context.countableReturnType(),
createFilter(context.bySectionFactors()));
} else {
var filter = createFilter(context.bySectionFactors());
Expand All @@ -228,6 +258,7 @@ public void addDelete(QueryContext context, MethodSpec.Builder spec) {
} else if (context.returnInfo().isSelfCollection() || needsUpdatedCount) {
spec.addStatement("var __session = this.mongoClient.startSession()");
spec.beginControlFlow("try");
// todo add option to require majority
spec.addStatement("__session.startTransaction()");

spec.addStatement(
Expand Down Expand Up @@ -264,7 +295,7 @@ public void addDelete(QueryContext context, MethodSpec.Builder spec) {
if (context.typeUtils().isType(Boolean.class, context.returnType())) {
spec.addStatement("return __deletedCount > 0");
} else {
spec.addStatement("return (int) __deletedCount"); // todo make more flexible
spec.addStatement("return ($T) __deletedCount", context.countableReturnType());
}
} else {
spec.addStatement("return __find");
Expand Down Expand Up @@ -449,13 +480,13 @@ private CodeBlock createSort(QueryContext context) {
}

if (!ascending.isEmpty()) {
builder.add("$T.ascending($L)", ascending);
builder.add("$T.ascending($L)", Sorts.class, ascending);
}
if (!descending.isEmpty()) {
if (!ascending.isEmpty()) {
builder.add(", ", descending);
}
builder.add("$T.descending($L)", descending);
builder.add("$T.descending($L)", Sorts.class, descending);
}

return builder.add("))").build();
Expand All @@ -465,12 +496,15 @@ private CodeBlock createSort(QueryContext context) {
* Creates a document with every column appended. The key is the column name as string and the value is the column
* name as parameter/variable
*/
private CodeBlock createDocument(List<ColumnInfo> columns) {
var builder = CodeBlock.builder().add("new $T()", Document.class);
private CodeBlock createUpdateDocument(List<ColumnInfo> columns) {
var builder = CodeBlock.builder().add("$T.combine(", Updates.class);
boolean first = true;
for (ColumnInfo column : columns) {
builder.add(".append($S, $L)", column.name(), column.name());
if (!first) builder.add(", ");
first = false;
builder.add("$T.set($S, $L)", Updates.class, column.name(), column.name());
}
return builder.build();
return builder.add(")").build();
}

private CodeBlock createFindFilter(QueryContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ protected void addEntities(Collection<EntityInfo> entities, MethodSpec.Builder m
}

private CodeBlock createEntityQuery(EntityInfo entity) {
// https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html
// https://docs.oracle.com/en/database/oracle/oracle-database/23/gmswn/database-gateway-sqlserver-data-type-conversion.html

var builder = CodeBlock.builder();
// todo indexes are not added atm
builder.add("\"CREATE TABLE IF NOT EXISTS $L (\" +\n", entity.name());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,23 @@ public void addUpdate(QueryContext context, MethodSpec.Builder spec) {

@Override
public void addDelete(QueryContext context, MethodSpec.Builder spec) {
// https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/DELETE.html
// https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/ROWNUM-Pseudocolumn.html for limit
// https://learn.microsoft.com/en-us/sql/t-sql/statements/delete-transact-sql?view=sql-server-ver16
// https://www.sqlite.org/lang_delete.html
// https://h2database.com/html/commands.html#delete
// https://dev.mysql.com/doc/refman/8.4/en/delete.html
// https://mariadb.com/kb/en/delete/

// order by is not supported by: oracledb, mssql, h2, sqlite (not enabled in xerial)

var builder = new QueryBuilder(context).addRaw("delete from %s", context.tableName());
if (context.hasBySection()) {
builder.add("where %s", this::createWhereForFactors);
} else if (context.hasParameters()) {
builder.add("where %s", this::createWhereForKeys);
}
// todo use 'truncate table' for dialects supporting it

if (context.hasProjection()) {
var limit = context.projection().limit();
Expand All @@ -133,6 +144,7 @@ public void addDelete(QueryContext context, MethodSpec.Builder spec) {
return;
}

// todo if returning is needed and a projection column name is given, only request that specific column
spec.addStatement("String __sql");
spec.beginControlFlow(
"if (this.dialect == $T.POSTGRESQL || this.dialect == $T.SQLITE || this.dialect == $T.MARIADB)",
Expand All @@ -150,6 +162,7 @@ public void addDelete(QueryContext context, MethodSpec.Builder spec) {
// spec.addStatement("__sql = $S", oracleDbSqlBuilder);
spec.addStatement("throw new $T($S)", IllegalStateException.class, "This behaviour is not yet implemented!");
spec.nextControlFlow("else if (this.dialect == $T.SQL_SERVER)", SqlDialect.class);
// https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql?view=sql-server-ver16
spec.addStatement("__sql = $S", builder.copy().addRawBefore("where", "output deleted.*"));
spec.nextControlFlow(
"else if (this.dialect == $T.H2 || this.dialect == $T.MYSQL)", SqlDialect.class, SqlDialect.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
Expand Down Expand Up @@ -96,11 +97,14 @@ public boolean isType(CharSequence expected, TypeMirror actual) {
}

public boolean isType(TypeMirror expected, TypeMirror actual) {
return typeUtils.isSameType(typeUtils.erasure(expected), typeUtils.erasure(actual));
return isTypeExact(toBoxedMirror(typeUtils.erasure(expected)), toBoxedMirror(typeUtils.erasure(actual)));
}

public boolean isTypeWithBoxed(TypeMirror expected, TypeMirror actual) {
return isType(toBoxedMirror(expected), toBoxedMirror(actual));
/**
* Compared to the normal isType methods this one doesn't erase and doesn't box the types
*/
public boolean isTypeExact(TypeMirror expected, TypeMirror actual) {
return typeUtils.isSameType(expected, actual);
}

public static String packageNameFor(Name className) {
Expand Down Expand Up @@ -136,4 +140,11 @@ public boolean isWholeNumberType(TypeMirror mirror) {
|| isType(Integer.class, mirror)
|| isType(Long.class, mirror);
}

public TypeMirror unboxType(TypeMirror mirror) {
if (mirror instanceof PrimitiveType) {
return mirror;
}
return typeUtils.unboxedType(mirror);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;
import java.lang.Boolean;
import java.lang.Exception;
import java.lang.IllegalStateException;
Expand All @@ -12,7 +13,6 @@
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.geysermc.databaseutils.codec.TypeCodec;
import org.geysermc.databaseutils.codec.TypeCodecRegistry;
Expand Down Expand Up @@ -52,14 +52,14 @@ public CompletableFuture<Boolean> existsByAOrB(int a, String bb) {

@Override
public void updateByBAndC(String b, String oldC, String c) {
this.collection.updateMany(Filters.and(Filters.eq("b", b), Filters.eq("c", oldC)), new Document().append("c", c));
this.collection.updateMany(Filters.and(Filters.eq("b", b), Filters.eq("c", oldC)), Updates.combine(Updates.set("c", c)));
}

@Override
public CompletableFuture<Boolean> deleteByAAndBAndC(int a, String b, String c) {
return CompletableFuture.supplyAsync(() -> {
int __count;
__count = (int) this.collection.deleteMany(Filters.and(Filters.eq("a", a), Filters.and(Filters.eq("b", b), Filters.eq("c", c)))).getDeletedCount();
long __count;
__count = (long) this.collection.deleteMany(Filters.and(Filters.eq("a", a), Filters.and(Filters.eq("b", b), Filters.eq("c", c)))).getDeletedCount();
return __count > 0;
} , this.database.executorService());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public CompletableFuture<Boolean> existsByBEquals(String b) {

@Override
public void update(List<TestEntity> entity) {
if (entity.isEmpty()) {
return;
}
var __bulkOperations = new ArrayList<WriteModel<TestEntity>>();
for (var __entry : entity) {
__bulkOperations.add(new ReplaceOneModel<>(Filters.and(Filters.eq("a", __entry.a()), Filters.eq("b", __entry.b())), __entry));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ public <T extends IRepository<?>> Stream<DynamicTest> allTypesForBut(
return builder.build();
}

public <T extends IRepository<?>> Stream<DynamicTest> allTypesForOnly(
Class<T> repositoryClass, Consumer<T> callback, DatabaseType... includedTypes) {
var excluded = new ArrayList<>(List.of(DatabaseType.values()));
excluded.removeAll(List.of(includedTypes));
return allTypesForBut(repositoryClass, callback, excluded.toArray(DatabaseType[]::new));
}

private void addRepositoriesFor(
DatabaseType type,
String uri,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public interface DeleteRepository extends IRepository<TestEntity>, ReusableTestR

int deleteFirstByB(String b);

int deleteFirstByBOrderByA(String b);

@Query("deleteFirstByBOrderByA")
TestEntity deleteFirstWithOrderReturning(String b);

@Override
void delete();
}
Loading

0 comments on commit d8064f7

Please sign in to comment.