From ee361e68978fee3f8af08a02b8fafe45946439f4 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 16 Jun 2024 16:18:47 +0200 Subject: [PATCH] Started working on a more flexible section reader Much still has to be done, like: - making sure all actions support both the by section variant and the non-by section variant - Adding stuff like limit and offset - Support returning collections --- README.md | 1 + ap/build.gradle.kts | 2 +- .../processor/EntityManager.java | 13 +- .../processor/RepositoryProcessor.java | 28 +- .../processor/action/Action.java | 90 +++++- .../processor/action/ActionRegistry.java | 18 +- .../processor/action/ByAction.java | 75 ----- .../processor/action/DeleteAction.java | 25 +- .../processor/action/DeleteByAction.java | 54 ---- ...{ExistsByAction.java => ExistsAction.java} | 24 +- .../{FindByAction.java => FindAction.java} | 24 +- .../processor/action/InsertAction.java | 14 +- .../processor/action/SimpleAction.java | 76 ----- .../processor/action/UpdateAction.java | 24 +- .../processor/info/EntityInfo.java | 8 + .../processor/query/KeywordsReadResult.java | 32 ++ .../processor/query/KeywordsReader.java | 251 ++++++++++++++++ .../processor/query/QueryInfo.java | 58 +++- .../processor/query/QueryInfoCreator.java | 112 +++++++ .../{VariableSection.java => BySection.java} | 6 +- ...ctionRegistry.java => FactorRegistry.java} | 17 +- .../query/section/QuerySectionsReader.java | 149 ---------- .../section/by/InputKeywordRegistry.java | 54 ++++ .../OrSelector.java => by/Keyword.java} | 11 +- .../query/section/by/MultiInputKeyword.java | 93 ++++++ .../query/section/by/SingleInputKeyword.java | 41 +++ .../section/by/keyword/EqualsKeyword.java | 41 +++ .../section/by/keyword/LessThanKeyword.java | 41 +++ .../AndFactor.java} | 11 +- .../{QuerySection.java => factor/Factor.java} | 4 +- .../query/section/factor/OrFactor.java | 32 ++ .../section/factor/VariableByFactor.java | 35 +++ .../section/factor/VariableOrderByFactor.java | 35 +++ .../query/section/order/OrderBySection.java | 31 ++ .../query/section/order/OrderDirection.java | 54 ++++ .../processor/type/RepositoryGenerator.java | 16 +- .../processor/type/SqlDatabaseGenerator.java | 4 +- .../type/SqlRepositoryGenerator.java | 278 +++++++++--------- .../processor/util/CollectionUtils.java | 37 +++ .../processor/util/StringUtils.java | 35 ++- .../processor/util/TypeUtils.java | 56 +++- .../processor/query/KeywordsReaderTests.java | 270 +++++++++++++++++ core/build.gradle.kts | 2 +- .../geysermc/databaseutils/DatabaseType.java | 24 ++ .../DatabaseWithDialectType.java | 24 ++ .../databaseutils/sql/SqlDialect.java | 2 +- gradle/libs.versions.toml | 8 +- 47 files changed, 1696 insertions(+), 644 deletions(-) delete mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/action/ByAction.java delete mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/action/DeleteByAction.java rename ap/src/main/java/org/geysermc/databaseutils/processor/action/{ExistsByAction.java => ExistsAction.java} (76%) rename ap/src/main/java/org/geysermc/databaseutils/processor/action/{FindByAction.java => FindAction.java} (75%) delete mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/action/SimpleAction.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/KeywordsReadResult.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/KeywordsReader.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/QueryInfoCreator.java rename ap/src/main/java/org/geysermc/databaseutils/processor/query/section/{VariableSection.java => BySection.java} (85%) rename ap/src/main/java/org/geysermc/databaseutils/processor/query/section/{QuerySectionRegistry.java => FactorRegistry.java} (71%) delete mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySectionsReader.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/InputKeywordRegistry.java rename ap/src/main/java/org/geysermc/databaseutils/processor/query/section/{selector/OrSelector.java => by/Keyword.java} (80%) create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/MultiInputKeyword.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/SingleInputKeyword.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/keyword/EqualsKeyword.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/keyword/LessThanKeyword.java rename ap/src/main/java/org/geysermc/databaseutils/processor/query/section/{selector/AndSelector.java => factor/AndFactor.java} (80%) rename ap/src/main/java/org/geysermc/databaseutils/processor/query/section/{QuerySection.java => factor/Factor.java} (92%) create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/OrFactor.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/VariableByFactor.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/VariableOrderByFactor.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/order/OrderBySection.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/query/section/order/OrderDirection.java create mode 100644 ap/src/main/java/org/geysermc/databaseutils/processor/util/CollectionUtils.java create mode 100644 ap/src/test/java/org/geysermc/databaseutils/processor/query/KeywordsReaderTests.java diff --git a/README.md b/README.md index 7ca5274..ca5890e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ currently examples can be found in the tests of the AP module and the tests of t # What's left to do? - 'complex' updates like `updateCByAAndB` which would update every row's C to the specified value where A and B match the specified value +- add distinct and things like limit and offset - make 'simple' actions like `insert` more flexible - allow it to return something else than void, e.g. ~~the input entity~~ or whether there was a row added - support adding every variable of the entity as parameter diff --git a/ap/build.gradle.kts b/ap/build.gradle.kts index 18138b9..f6c6f52 100644 --- a/ap/build.gradle.kts +++ b/ap/build.gradle.kts @@ -10,7 +10,7 @@ dependencies { annotationProcessor(libs.auto.service) testImplementation(libs.compile.testing) - testImplementation(libs.junit.api) + testImplementation(libs.bundles.junit) testRuntimeOnly(libs.junit.engine) } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/EntityManager.java b/ap/src/main/java/org/geysermc/databaseutils/processor/EntityManager.java index 1db2e16..30ae18b 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/EntityManager.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/EntityManager.java @@ -25,7 +25,6 @@ package org.geysermc.databaseutils.processor; import static org.geysermc.databaseutils.processor.util.AnnotationUtils.hasAnnotation; -import static org.geysermc.databaseutils.processor.util.TypeUtils.toBoxedTypeElement; import java.util.ArrayList; import java.util.Arrays; @@ -39,18 +38,18 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.util.Types; import org.geysermc.databaseutils.meta.Entity; import org.geysermc.databaseutils.meta.Key; import org.geysermc.databaseutils.processor.info.ColumnInfo; import org.geysermc.databaseutils.processor.info.EntityInfo; import org.geysermc.databaseutils.processor.info.IndexInfo; +import org.geysermc.databaseutils.processor.util.TypeUtils; final class EntityManager { private final Map entityInfoByClassName = new HashMap<>(); - private final Types typeUtils; + private final TypeUtils typeUtils; - EntityManager(final Types typeUtils) { + EntityManager(final TypeUtils typeUtils) { this.typeUtils = Objects.requireNonNull(typeUtils); } @@ -100,7 +99,7 @@ EntityInfo processEntity(TypeElement type) { continue; } - TypeElement typeElement = toBoxedTypeElement(field.asType(), typeUtils); + TypeElement typeElement = typeUtils.toBoxedTypeElement(field.asType()); columns.add(new ColumnInfo(field.getSimpleName(), typeElement.getQualifiedName())); if (hasAnnotation(field, Key.class)) { @@ -122,8 +121,8 @@ EntityInfo processEntity(TypeElement type) { } for (int i = 0; i < parameters.size(); i++) { - var parameterType = toBoxedTypeElement(parameters.get(i).asType(), typeUtils) - .getQualifiedName(); + var parameterType = + typeUtils.toBoxedTypeElement(parameters.get(i).asType()).getQualifiedName(); if (!columns.get(i).typeName().equals(parameterType)) { continue constructors; } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/RepositoryProcessor.java b/ap/src/main/java/org/geysermc/databaseutils/processor/RepositoryProcessor.java index 0fe7e42..cbc59fa 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/RepositoryProcessor.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/RepositoryProcessor.java @@ -46,28 +46,29 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; import javax.tools.Diagnostic; import org.geysermc.databaseutils.IRepository; import org.geysermc.databaseutils.meta.Repository; import org.geysermc.databaseutils.processor.action.ActionRegistry; +import org.geysermc.databaseutils.processor.query.KeywordsReader; +import org.geysermc.databaseutils.processor.query.QueryInfoCreator; import org.geysermc.databaseutils.processor.type.RepositoryGenerator; import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; import org.geysermc.databaseutils.processor.util.TypeUtils; @AutoService(Processor.class) public final class RepositoryProcessor extends AbstractProcessor { + private TypeUtils typeUtils; private EntityManager entityManager; private Filer filer; - private Types typeUtils; private Messager messager; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); - this.entityManager = new EntityManager(processingEnv.getTypeUtils()); + this.typeUtils = new TypeUtils(processingEnv.getTypeUtils(), processingEnv.getElementUtils()); + this.entityManager = new EntityManager(typeUtils); this.filer = processingEnv.getFiler(); - this.typeUtils = processingEnv.getTypeUtils(); this.messager = processingEnv.getMessager(); } @@ -169,7 +170,7 @@ private void writeGeneratedTypes(List generatedTypes) { private List processRepository(TypeElement repository) { TypeMirror entityType = null; for (TypeMirror mirror : repository.getInterfaces()) { - if (TypeUtils.isTypeOf(IRepository.class, MoreTypes.asTypeElement(mirror))) { + if (TypeUtils.isType(IRepository.class, MoreTypes.asTypeElement(mirror))) { entityType = MoreTypes.asDeclared(mirror).getTypeArguments().get(0); } } @@ -198,22 +199,23 @@ private List processRepository(TypeElement repository) { boolean async = false; if (MoreTypes.isTypeOf(CompletableFuture.class, element.getReturnType())) { async = true; - returnType = TypeUtils.toBoxedTypeElement( - MoreTypes.asDeclared(element.getReturnType()) - .getTypeArguments() - .get(0), - typeUtils); + returnType = typeUtils.toBoxedTypeElement(MoreTypes.asDeclared(element.getReturnType()) + .getTypeArguments() + .get(0)); } else { - returnType = TypeUtils.toBoxedTypeElement(element.getReturnType(), typeUtils); + returnType = typeUtils.toBoxedTypeElement(element.getReturnType()); } var name = element.getSimpleName().toString(); - var action = ActionRegistry.actionMatching(name); + var result = new KeywordsReader(name, entity).read(); + var action = ActionRegistry.actionMatching(result); if (action == null) { throw new InvalidRepositoryException("No available actions for %s", name); } - action.addTo(generators, name, element, returnType, entity, typeUtils, async); + var queryInfo = new QueryInfoCreator(result, element, entity, typeUtils).create(); + + action.addTo(generators, queryInfo, returnType, async, typeUtils); } return generators; diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/Action.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/Action.java index 44de38b..c169783 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/Action.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/action/Action.java @@ -24,37 +24,97 @@ */ package org.geysermc.databaseutils.processor.action; +import com.squareup.javapoet.MethodSpec; +import java.util.Collection; import java.util.List; -import java.util.regex.Pattern; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Types; -import org.geysermc.databaseutils.processor.info.EntityInfo; +import org.geysermc.databaseutils.processor.query.QueryInfo; import org.geysermc.databaseutils.processor.type.RepositoryGenerator; +import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; +import org.geysermc.databaseutils.processor.util.TypeUtils; public abstract class Action { private final String actionType; - private final Pattern actionPattern; + private final boolean supportsFilter; - protected Action(String actionType, String actionRegex) { + protected Action(String actionType) { + this(actionType, true); + } + + protected Action(String actionType, boolean supportsFilter) { this.actionType = actionType; - this.actionPattern = Pattern.compile(actionRegex); + this.supportsFilter = supportsFilter; } public String actionType() { return actionType; } - public Pattern actionPattern() { - return actionPattern; + public boolean supportsFilter() { + return supportsFilter; } - public abstract void addTo( - List generators, - String fullName, - ExecutableElement element, + protected abstract void addToSingle( + RepositoryGenerator generator, + QueryInfo info, + MethodSpec.Builder spec, TypeElement returnType, - EntityInfo info, - Types typeUtils, boolean async); + + protected boolean validateSingle(QueryInfo info, TypeElement returnType, TypeUtils typeUtils) { + return TypeUtils.isType(Void.class, returnType) || TypeUtils.isWholeNumberType(returnType); + } + + protected boolean validateCollection(QueryInfo info, TypeElement elementType, TypeUtils typeUtils) { + return false; + } + + protected boolean validateEither(QueryInfo info, TypeElement elementType, boolean collection, TypeUtils typeUtils) { + return false; + } + + protected void validate(QueryInfo info, TypeElement returnType, TypeUtils typeUtils) { + var elementType = returnType; + if (typeUtils.isAssignable(Collection.class, returnType.asType())) { + elementType = (TypeElement) returnType.getTypeParameters().get(0); + + if (!supportsFilter) { + throw new InvalidRepositoryException("%s does not support a By section", actionType); + } + + if (validateCollection(info, elementType, typeUtils) + || validateEither(info, elementType, true, typeUtils)) { + return; + } + } else { + if (validateEither(info, elementType, false, typeUtils)) { + return; + } + if (validateSingle(info, returnType, typeUtils)) { + return; + } + } + + if (!typeUtils.isAssignable(info.entityType(), elementType.asType())) { + throw new InvalidRepositoryException( + "Unsupported return type %s for %s", + returnType.getSimpleName(), info.element().getSimpleName()); + } + } + + public void addTo( + List generators, + QueryInfo info, + TypeElement returnType, + boolean async, + TypeUtils typeUtils) { + if (!info.hasBySection() && info.element().getParameters().size() != 1) { + throw new InvalidRepositoryException("Expected one parameter with type %s", info.entityType()); + } + validate(info, returnType, typeUtils); + + for (RepositoryGenerator generator : generators) { + addToSingle(generator, info, MethodSpec.overriding(info.element()), returnType, async); + } + } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/ActionRegistry.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/ActionRegistry.java index 34bc8a0..2b9cc20 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/ActionRegistry.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/action/ActionRegistry.java @@ -25,19 +25,19 @@ package org.geysermc.databaseutils.processor.action; import java.util.Set; +import org.geysermc.databaseutils.processor.query.KeywordsReadResult; public final class ActionRegistry { - private static final Set REGISTERED_ACTIONS = Set.of( - new FindByAction(), - new ExistsByAction(), - new DeleteByAction(), - new InsertAction(), - new UpdateAction(), - new DeleteAction()); + private static final Set REGISTERED_ACTIONS = + Set.of(new FindAction(), new ExistsAction(), new InsertAction(), new UpdateAction(), new DeleteAction()); - public static Action actionMatching(String name) { + public static Action actionMatching(KeywordsReadResult result) { for (Action action : REGISTERED_ACTIONS) { - if (action.actionPattern().matcher(name).matches()) { + var hasFilter = result.bySection() != null; + if (result.actionName().equals(action.actionType())) { + if (hasFilter && !action.supportsFilter()) { + continue; + } return action; } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/ByAction.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/ByAction.java deleted file mode 100644 index d12f62c..0000000 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/ByAction.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 - * @link https://github.com/GeyserMC/DatabaseUtils - */ -package org.geysermc.databaseutils.processor.action; - -import com.squareup.javapoet.MethodSpec; -import java.util.List; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.util.Types; -import org.geysermc.databaseutils.processor.info.EntityInfo; -import org.geysermc.databaseutils.processor.query.QueryInfo; -import org.geysermc.databaseutils.processor.query.section.QuerySection; -import org.geysermc.databaseutils.processor.query.section.QuerySectionsReader; -import org.geysermc.databaseutils.processor.type.RepositoryGenerator; - -abstract class ByAction extends Action { - protected ByAction(String actionType) { - super(actionType, '^' + actionType + ".*"); - } - - protected abstract void validate(ExecutableElement element, TypeElement returnType, EntityInfo info); - - protected abstract void addToSingle( - RepositoryGenerator generator, QueryInfo queryInfo, MethodSpec.Builder spec, boolean async); - - @Override - public void addTo( - List generators, - String fullName, - ExecutableElement element, - TypeElement returnType, - EntityInfo info, - Types typeUtils, - boolean async) { - var sections = querySectionsFor(fullName, element, returnType, info, typeUtils); - var parameterNames = element.getParameters().stream() - .map(VariableElement::getSimpleName) - .toList(); - var queryInfo = new QueryInfo(info.name(), info.className(), info.columns(), sections, parameterNames); - for (RepositoryGenerator generator : generators) { - addToSingle(generator, queryInfo, MethodSpec.overriding(element), async); - } - } - - protected List querySectionsFor( - String fullName, ExecutableElement element, TypeElement returnType, EntityInfo info, Types typeUtils) { - validate(element, returnType, info); - return new QuerySectionsReader( - actionType(), fullName.substring(actionType().length()), element, info, typeUtils) - .readBySections(); - } -} 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 9b7595c..7822cef 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 @@ -26,23 +26,34 @@ import com.squareup.javapoet.MethodSpec; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import org.geysermc.databaseutils.processor.info.EntityInfo; +import org.geysermc.databaseutils.processor.query.QueryInfo; import org.geysermc.databaseutils.processor.type.RepositoryGenerator; +import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; +import org.geysermc.databaseutils.processor.util.TypeUtils; -final class DeleteAction extends SimpleAction { +final class DeleteAction extends Action { DeleteAction() { super("delete"); } + @Override + protected boolean validateSingle(QueryInfo info, TypeElement 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", + info.element().getSimpleName(), returnType); + } + return true; + } + @Override protected void addToSingle( RepositoryGenerator generator, - EntityInfo info, - TypeElement returnType, - VariableElement parameter, + QueryInfo info, MethodSpec.Builder spec, + TypeElement returnType, boolean async) { - generator.addDelete(info, returnType, parameter, spec, async); + generator.addDelete(info, spec, returnType, async); } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/DeleteByAction.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/DeleteByAction.java deleted file mode 100644 index 8d853e9..0000000 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/DeleteByAction.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 - * @link https://github.com/GeyserMC/DatabaseUtils - */ -package org.geysermc.databaseutils.processor.action; - -import com.squareup.javapoet.MethodSpec; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import org.geysermc.databaseutils.processor.info.EntityInfo; -import org.geysermc.databaseutils.processor.query.QueryInfo; -import org.geysermc.databaseutils.processor.type.RepositoryGenerator; -import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; -import org.geysermc.databaseutils.processor.util.TypeUtils; - -final class DeleteByAction extends ByAction { - DeleteByAction() { - super("deleteBy"); - } - - @Override - protected void validate(ExecutableElement element, TypeElement returnType, EntityInfo info) { - if (!TypeUtils.isTypeOf(Void.class, returnType)) { - throw new InvalidRepositoryException( - "Expected Void as return type for %s, got %s", element.getSimpleName(), returnType); - } - } - - @Override - protected void addToSingle( - RepositoryGenerator generator, QueryInfo queryInfo, MethodSpec.Builder spec, boolean async) { - generator.addDeleteBy(queryInfo, spec, async); - } -} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/ExistsByAction.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/ExistsAction.java similarity index 76% rename from ap/src/main/java/org/geysermc/databaseutils/processor/action/ExistsByAction.java rename to ap/src/main/java/org/geysermc/databaseutils/processor/action/ExistsAction.java index 6bfb3d0..27b5658 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/ExistsByAction.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/action/ExistsAction.java @@ -25,30 +25,34 @@ package org.geysermc.databaseutils.processor.action; import com.squareup.javapoet.MethodSpec; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import org.geysermc.databaseutils.processor.info.EntityInfo; import org.geysermc.databaseutils.processor.query.QueryInfo; import org.geysermc.databaseutils.processor.type.RepositoryGenerator; import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; import org.geysermc.databaseutils.processor.util.TypeUtils; -final class ExistsByAction extends ByAction { - ExistsByAction() { - super("existsBy"); +final class ExistsAction extends Action { + ExistsAction() { + super("exists"); } @Override - protected void validate(ExecutableElement element, TypeElement returnType, EntityInfo info) { - if (!TypeUtils.isTypeOf(Boolean.class, returnType)) { + protected boolean validateSingle(QueryInfo info, TypeElement returnType, TypeUtils typeUtils) { + if (!TypeUtils.isType(Boolean.class, returnType)) { throw new InvalidRepositoryException( - "Expected Boolean as return type for %s, got %s", element.getSimpleName(), returnType); + "Expected Boolean as return type for %s, got %s", + info.element().getSimpleName(), returnType); } + return true; } @Override public void addToSingle( - RepositoryGenerator generator, QueryInfo queryInfo, MethodSpec.Builder spec, boolean async) { - generator.addExistsBy(queryInfo, spec, async); + RepositoryGenerator generator, + QueryInfo info, + MethodSpec.Builder spec, + TypeElement returnType, + boolean async) { + generator.addExists(info, spec, returnType, async); } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/FindByAction.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/FindAction.java similarity index 75% rename from ap/src/main/java/org/geysermc/databaseutils/processor/action/FindByAction.java rename to ap/src/main/java/org/geysermc/databaseutils/processor/action/FindAction.java index dce2066..fd43e36 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/FindByAction.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/action/FindAction.java @@ -25,30 +25,34 @@ package org.geysermc.databaseutils.processor.action; import com.squareup.javapoet.MethodSpec; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import org.geysermc.databaseutils.processor.info.EntityInfo; import org.geysermc.databaseutils.processor.query.QueryInfo; import org.geysermc.databaseutils.processor.type.RepositoryGenerator; import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; import org.geysermc.databaseutils.processor.util.TypeUtils; -final class FindByAction extends ByAction { - FindByAction() { - super("findBy"); +final class FindAction extends Action { + FindAction() { + super("find"); } @Override - protected void validate(ExecutableElement element, TypeElement returnType, EntityInfo info) { - if (!TypeUtils.isTypeOf(info.className(), returnType)) { + protected boolean validateEither(QueryInfo info, TypeElement returnType, boolean collection, TypeUtils typeUtils) { + if (!TypeUtils.isType(info.entityType(), returnType)) { throw new InvalidRepositoryException( - "Expected %s as return type for %s, got %s", info.className(), element.getSimpleName(), returnType); + "Expected %s as return type for %s, got %s", + info.entityType(), info.element().getSimpleName(), returnType); } + return true; } @Override public void addToSingle( - RepositoryGenerator generator, QueryInfo queryInfo, MethodSpec.Builder spec, boolean async) { - generator.addFindBy(queryInfo, spec, async); + RepositoryGenerator generator, + QueryInfo info, + MethodSpec.Builder spec, + TypeElement returnType, + boolean async) { + generator.addFind(info, spec, returnType, async); } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/InsertAction.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/InsertAction.java index 2e0a4a8..518e658 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/InsertAction.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/action/InsertAction.java @@ -26,23 +26,21 @@ import com.squareup.javapoet.MethodSpec; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import org.geysermc.databaseutils.processor.info.EntityInfo; +import org.geysermc.databaseutils.processor.query.QueryInfo; import org.geysermc.databaseutils.processor.type.RepositoryGenerator; -final class InsertAction extends SimpleAction { +final class InsertAction extends Action { InsertAction() { - super("insert"); + super("insert", false); } @Override protected void addToSingle( RepositoryGenerator generator, - EntityInfo info, - TypeElement returnType, - VariableElement parameter, + QueryInfo info, MethodSpec.Builder spec, + TypeElement returnType, boolean async) { - generator.addInsert(info, returnType, parameter, spec, async); + generator.addInsert(info, spec, returnType, async); } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/SimpleAction.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/SimpleAction.java deleted file mode 100644 index 7c067b3..0000000 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/SimpleAction.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 - * @link https://github.com/GeyserMC/DatabaseUtils - */ -package org.geysermc.databaseutils.processor.action; - -import com.squareup.javapoet.MethodSpec; -import java.util.List; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.util.Types; -import org.geysermc.databaseutils.processor.info.EntityInfo; -import org.geysermc.databaseutils.processor.type.RepositoryGenerator; -import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; -import org.geysermc.databaseutils.processor.util.TypeUtils; - -abstract class SimpleAction extends Action { - protected SimpleAction(String actionType) { - super(actionType, '^' + actionType + '$'); - } - - protected abstract void addToSingle( - RepositoryGenerator generator, - EntityInfo info, - TypeElement returnType, - VariableElement parameter, - MethodSpec.Builder spec, - boolean async); - - @Override - public void addTo( - List generators, - String fullName, - ExecutableElement element, - TypeElement returnType, - EntityInfo info, - Types typeUtils, - boolean async) { - if (!TypeUtils.isTypeOf(Void.class, returnType) && !TypeUtils.isTypeOf(info.className(), returnType)) { - throw new InvalidRepositoryException( - "Expected either Void or %s as return type for %s, got %s", - info.className(), element.getSimpleName(), returnType); - } - if (element.getParameters().size() == 1) { - var parameter = element.getParameters().get(0); - if (TypeUtils.isTypeOf(info.className(), parameter.asType())) { - for (RepositoryGenerator generator : generators) { - addToSingle(generator, info, returnType, parameter, MethodSpec.overriding(element), async); - } - return; - } - } - throw new InvalidRepositoryException("Expected one parameter with type %s", info.className()); - } -} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/action/UpdateAction.java b/ap/src/main/java/org/geysermc/databaseutils/processor/action/UpdateAction.java index cb3ea4c..533f8c4 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/action/UpdateAction.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/action/UpdateAction.java @@ -26,11 +26,12 @@ import com.squareup.javapoet.MethodSpec; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import org.geysermc.databaseutils.processor.info.EntityInfo; +import org.geysermc.databaseutils.processor.query.QueryInfo; import org.geysermc.databaseutils.processor.type.RepositoryGenerator; +import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; +import org.geysermc.databaseutils.processor.util.TypeUtils; -final class UpdateAction extends SimpleAction { +final class UpdateAction extends Action { UpdateAction() { super("update"); } @@ -38,11 +39,20 @@ final class UpdateAction extends SimpleAction { @Override protected void addToSingle( RepositoryGenerator generator, - EntityInfo info, - TypeElement returnType, - VariableElement parameter, + QueryInfo info, MethodSpec.Builder spec, + TypeElement returnType, boolean async) { - generator.addUpdate(info, returnType, parameter, spec, async); + generator.addUpdate(info, spec, returnType, async); + } + + @Override + protected boolean validateEither(QueryInfo info, TypeElement returnType, boolean collection, TypeUtils typeUtils) { + if (!collection) { + return false; + } + throw new InvalidRepositoryException( + "Unsupported return type %s for %s", + returnType.getSimpleName(), info.element().getSimpleName()); } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/info/EntityInfo.java b/ap/src/main/java/org/geysermc/databaseutils/processor/info/EntityInfo.java index 6e56adc..3140dba 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/info/EntityInfo.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/info/EntityInfo.java @@ -24,6 +24,7 @@ */ package org.geysermc.databaseutils.processor.info; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -51,4 +52,11 @@ public List notKeyColumns() { .filter(column -> !keys.contains(column.name())) .toList(); } + + public List notKeyFirstColumns() { + var combined = new ArrayList(); + combined.addAll(notKeyColumns()); + combined.addAll(keyColumns()); + return combined; + } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/KeywordsReadResult.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/KeywordsReadResult.java new file mode 100644 index 0000000..0387dc3 --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/KeywordsReadResult.java @@ -0,0 +1,32 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.databaseutils.processor.query.section.BySection; +import org.geysermc.databaseutils.processor.query.section.order.OrderBySection; + +public record KeywordsReadResult( + String actionName, boolean distinct, @Nullable BySection bySection, @Nullable OrderBySection orderBySection) {} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/KeywordsReader.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/KeywordsReader.java new file mode 100644 index 0000000..f3e3212 --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/KeywordsReader.java @@ -0,0 +1,251 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query; + +import static org.geysermc.databaseutils.processor.util.CollectionUtils.join; + +import java.util.ArrayList; +import java.util.List; +import org.geysermc.databaseutils.processor.info.EntityInfo; +import org.geysermc.databaseutils.processor.query.section.BySection; +import org.geysermc.databaseutils.processor.query.section.FactorRegistry; +import org.geysermc.databaseutils.processor.query.section.by.InputKeywordRegistry; +import org.geysermc.databaseutils.processor.query.section.factor.Factor; +import org.geysermc.databaseutils.processor.query.section.factor.VariableByFactor; +import org.geysermc.databaseutils.processor.query.section.factor.VariableOrderByFactor; +import org.geysermc.databaseutils.processor.query.section.order.OrderBySection; +import org.geysermc.databaseutils.processor.query.section.order.OrderDirection; +import org.geysermc.databaseutils.processor.util.StringUtils; + +public class KeywordsReader { + private final String name; + private final List variableNames; + + public KeywordsReader(String name, EntityInfo info) { + this( + name, + info.columns().stream() + .map(columnInfo -> columnInfo.name().toString()) + .toList()); + } + + public KeywordsReader(String name, List variableNames) { + this.name = name; + // It has to be a List of Strings and not CharSequences! + // javax.lang.model.element.Name and java.lang.String can have the same value, + // but they will not be equal to each + this.variableNames = variableNames; + } + + public KeywordsReadResult read() { + // First split everything into sections (find By Unique Id And Username). + // We can expect every query to follow the same format: keyword (e.g. find) - By and/or OrderBy + // after both By and OrderBy comes the first variable, after that we can't know what the structure is. + // We try to form the biggest variable name that still matches: + // - uniqueId exists + // - uniqueIdAnd doesn't exist + // - uniqueIdAndUsername doesn't exist + // In this result uniqueId matches, after that we look for something to do with the data. + // In this example there is nothing, so it'll default to the 'equals' keyword + + String action = null; + boolean isOrderBy = false; + boolean hadOrder = false; + + var bySections = new ArrayList(); + var orderBySections = new ArrayList(); + var workingSection = new StringBuilder(); + + for (int i = 0; i < name.length(); i++) { + char current = name.charAt(i); + + if (Character.isUpperCase(current)) { + var finishedSection = workingSection.toString(); + workingSection = new StringBuilder(); + var shouldAdd = true; + + if (action == null) { + action = finishedSection; + } else { + if ("By".equals(finishedSection)) { + if (hadOrder) { + isOrderBy = true; + hadOrder = false; + shouldAdd = false; + bySections.remove(bySections.size() - 1); // Remove Order + if (!orderBySections.isEmpty()) { + throw new IllegalStateException("Can only have one OrderBy definition!"); + } + } else if (bySections.isEmpty()) { + // usually it starts with By (e.g. findByUsername), so ignore it + shouldAdd = false; + } + } else { + hadOrder = "Order".equals(finishedSection); + } + + if (shouldAdd) { + if (isOrderBy) { + orderBySections.add(finishedSection); + } else { + bySections.add(finishedSection); + } + } + } + } + workingSection.append(current); + } + + if (!workingSection.isEmpty()) { + var finishedSection = workingSection.toString(); + if (action == null) { + action = finishedSection; + } else { + if (isOrderBy) { + orderBySections.add(finishedSection); + } else { + bySections.add(finishedSection); + } + } + } + + return new KeywordsReadResult(action, false, formBySection(bySections), formOrderBySection(orderBySections)); + } + + public BySection formBySection(List sections) { + if (sections.isEmpty()) { + return null; + } + var variables = new ArrayList(); + formBySections(sections, 0, variables); + return new BySection(variables); + } + + private void formBySections(List bySections, int offset, List factors) { + var variableMatch = StringUtils.largestMatch(bySections, offset, input -> { + var variableName = StringUtils.uncapitalize(input); + return variableNames.contains(variableName) ? variableName : null; + }); + + if (variableMatch == null) { + var factor = FactorRegistry.factorFor(bySections.get(offset)); + if (factor == null) { + throw new IllegalStateException( + "Expected a variable to match, but none did for " + join(bySections, offset)); + } + factors.add(factor); + // After a factor was found, restart the process to follow the correct variable -> keyword format + formBySections(bySections, offset + 1, factors); + return; + } + + // findByUsername = username, no keywords after the variable + if (variableMatch.offset() + 1 == bySections.size()) { + factors.add(new VariableByFactor(variableMatch.match())); + return; + } + + // findByUsernameIsNotNull = IsNotNull + var keywordMatch = + StringUtils.largestMatch(bySections, variableMatch.offset() + 1, InputKeywordRegistry::findByName); + if (keywordMatch == null) { + var factor = FactorRegistry.factorFor(bySections.get(variableMatch.offset() + 1)); + if (factor == null) { + throw new IllegalStateException( + "Expected a keyword to match for " + join(bySections, variableMatch.offset())); + } + + // just like above, if no keyword is provided, assume equals + factors.add(new VariableByFactor(variableMatch.match())); + factors.add(factor); + + // We have to assume that after the factor something comes next + formBySections(bySections, variableMatch.offset() + 2, factors); + return; + } + + factors.add(new VariableByFactor(variableMatch.match(), keywordMatch.match())); + if (keywordMatch.offset() + 1 < bySections.size()) { + formBySections(bySections, keywordMatch.offset() + 1, factors); + } + } + + public OrderBySection formOrderBySection(List sections) { + if (sections.isEmpty()) { + return null; + } + var variables = new ArrayList(); + formOrderBySections(sections, 0, variables); + return new OrderBySection(variables); + } + + private void formOrderBySections(List orderBySections, int offset, List factors) { + var variableMatch = StringUtils.largestMatch(orderBySections, offset, input -> { + var variableName = StringUtils.uncapitalize(input); + return variableNames.contains(variableName) ? variableName : null; + }); + + if (variableMatch == null) { + var factor = FactorRegistry.factorFor(orderBySections.get(offset)); + if (factor == null) { + throw new IllegalStateException( + "Expected a variable to match, but none did for " + join(orderBySections, offset)); + } + factors.add(factor); + // After a factor was found restart the process to follow the correct variable -> direction format + formOrderBySections(orderBySections, offset + 1, factors); + return; + } + + // use the default direction if none is provided + if (variableMatch.offset() + 1 == orderBySections.size()) { + factors.add(new VariableOrderByFactor(variableMatch.match(), OrderDirection.DEFAULT)); + return; + } + + var directionString = StringUtils.uncapitalize(orderBySections.get(variableMatch.offset() + 1)); + var direction = OrderDirection.byName(directionString); + if (direction == null) { + var factor = FactorRegistry.factorFor(orderBySections.get(variableMatch.offset() + 1)); + if (factor == null) { + throw new IllegalStateException("Unknown order by direction " + directionString); + } + + // just like above, if no direction is provided, use default + factors.add(new VariableOrderByFactor(variableMatch.match(), OrderDirection.DEFAULT)); + factors.add(factor); + + // We have to assume that after the factor something comes next + formOrderBySections(orderBySections, variableMatch.offset() + 2, factors); + return; + } + + factors.add(new VariableOrderByFactor(variableMatch.match(), direction)); + // +2 because of the direction + if (variableMatch.offset() + 2 < orderBySections.size()) { + formOrderBySections(orderBySections, variableMatch.offset() + 2, factors); + } + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/QueryInfo.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/QueryInfo.java index 884fdcd..ed059f3 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/query/QueryInfo.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/QueryInfo.java @@ -26,18 +26,28 @@ import java.util.List; import java.util.stream.Stream; +import javax.lang.model.element.ExecutableElement; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.geysermc.databaseutils.processor.info.ColumnInfo; -import org.geysermc.databaseutils.processor.query.section.QuerySection; -import org.geysermc.databaseutils.processor.query.section.VariableSection; - -public record QueryInfo( - String tableName, - CharSequence entityType, - List columns, - List sections, - List parameterNames) { +import org.geysermc.databaseutils.processor.info.EntityInfo; +import org.geysermc.databaseutils.processor.query.section.factor.Factor; +import org.geysermc.databaseutils.processor.query.section.factor.VariableByFactor; + +public record QueryInfo(EntityInfo entityInfo, KeywordsReadResult result, ExecutableElement element) { + public String tableName() { + return entityInfo.name(); + } + + public CharSequence entityType() { + return entityInfo.className(); + } + + public List columns() { + return entityInfo.columns(); + } + public ColumnInfo columnFor(CharSequence columnName) { - for (ColumnInfo column : columns) { + for (ColumnInfo column : columns()) { if (column.name().contentEquals(columnName)) { return column; } @@ -45,14 +55,34 @@ public ColumnInfo columnFor(CharSequence columnName) { return null; } - public List variableNames() { - return sections.stream() + public boolean hasBySection() { + return result.bySection() != null; + } + + public @MonotonicNonNull List bySectionFactors() { + return result.bySection() != null ? result.bySection().factors() : null; + } + + public void requireBySection() { + if (bySectionFactors() == null || bySectionFactors().isEmpty()) { + throw new IllegalStateException("This query requires a By section"); + } + } + + public List byVariables() { + requireBySection(); + + return bySectionFactors().stream() .flatMap(section -> { - if (section instanceof VariableSection variable) { - return Stream.of(variable.name()); + if (section instanceof VariableByFactor variable) { + return Stream.of(variable); } return null; }) .toList(); } + + public CharSequence parameterName(int index) { + return element.getParameters().get(index).getSimpleName(); + } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/QueryInfoCreator.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/QueryInfoCreator.java new file mode 100644 index 0000000..adf40b6 --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/QueryInfoCreator.java @@ -0,0 +1,112 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import org.geysermc.databaseutils.processor.info.EntityInfo; +import org.geysermc.databaseutils.processor.query.section.factor.Factor; +import org.geysermc.databaseutils.processor.query.section.factor.VariableByFactor; +import org.geysermc.databaseutils.processor.query.section.factor.VariableOrderByFactor; +import org.geysermc.databaseutils.processor.util.TypeUtils; + +/** + * Analyses and validates the read Keywords and converts it into QueryInfo. + * Note that this will edit the provided readResult. It doesn't create a new instance. + */ +public class QueryInfoCreator { + private final KeywordsReadResult readResult; + private final ExecutableElement element; + private final EntityInfo info; + private final TypeUtils typeUtils; + + public QueryInfoCreator( + KeywordsReadResult readResult, ExecutableElement element, EntityInfo info, TypeUtils typeUtils) { + this.readResult = readResult; + this.element = element; + this.info = info; + this.typeUtils = typeUtils; + } + + public QueryInfo create() { + analyse(); + return new QueryInfo(info, readResult, element); + } + + private void analyse() { + var parameterTypes = + element.getParameters().stream().map(VariableElement::asType).toList(); + var parameterNames = element.getParameters().stream() + .map(VariableElement::getSimpleName) + .toList(); + var handledInputs = 0; + + if (readResult.bySection() != null) { + var section = readResult.bySection(); + for (Factor factor : section.factors()) { + if (factor instanceof VariableByFactor variable) { + var column = info.columnFor(variable.name()); + if (column == null) { + throw new IllegalStateException( + "Could not find column %s for entity %s".formatted(variable.name(), info.name())); + } + + var keyword = variable.keyword(); + keyword.validateTypes(column, parameterTypes, parameterNames, handledInputs, typeUtils); + handledInputs += keyword.inputCount(); + } + } + } + + if (readResult.orderBySection() != null) { + var section = readResult.orderBySection(); + for (Factor factor : section.factors()) { + if (factor instanceof VariableOrderByFactor variable) { + var column = info.columnFor(variable.name()); + if (column == null) { + throw new IllegalStateException( + "Could not find column %s for entity %s".formatted(variable.name(), info.name())); + } + } + } + } + + // if there is no By section and there are parameters, it should be the entity + if (readResult.bySection() == null && parameterTypes.size() == 1) { + if (!typeUtils.isAssignable(parameterTypes.get(0), info.className())) { + throw new IllegalStateException(String.format( + "Expected the only parameter %s to be assignable from entity %s with type %s!", + parameterTypes.get(0), info.name(), info.className())); + } + return; + } + + // Otherwise the expected parameter count should equal the actual + if (parameterTypes.size() != handledInputs) { + throw new IllegalStateException( + "Expected %s parameters, received %s".formatted(handledInputs, parameterTypes)); + } + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/VariableSection.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/BySection.java similarity index 85% rename from ap/src/main/java/org/geysermc/databaseutils/processor/query/section/VariableSection.java rename to ap/src/main/java/org/geysermc/databaseutils/processor/query/section/BySection.java index 8b719d3..85ad650 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/VariableSection.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/BySection.java @@ -24,4 +24,8 @@ */ package org.geysermc.databaseutils.processor.query.section; -public record VariableSection(CharSequence name) implements QuerySection {} +import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.databaseutils.processor.query.section.factor.Factor; + +public record BySection(@NonNull List<@NonNull Factor> factors) {} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySectionRegistry.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/FactorRegistry.java similarity index 71% rename from ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySectionRegistry.java rename to ap/src/main/java/org/geysermc/databaseutils/processor/query/section/FactorRegistry.java index 15262af..ac34006 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySectionRegistry.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/FactorRegistry.java @@ -25,16 +25,17 @@ package org.geysermc.databaseutils.processor.query.section; import java.util.Map; -import org.geysermc.databaseutils.processor.query.section.selector.AndSelector; -import org.geysermc.databaseutils.processor.query.section.selector.OrSelector; +import org.geysermc.databaseutils.processor.query.section.factor.AndFactor; +import org.geysermc.databaseutils.processor.query.section.factor.Factor; +import org.geysermc.databaseutils.processor.query.section.factor.OrFactor; -public final class QuerySectionRegistry { - private static final Map SELECTORS = - Map.of("And", AndSelector.INSTANCE, "Or", OrSelector.INSTANCE); +public final class FactorRegistry { + private static final Map FACTORS = + Map.of(AndFactor.NAME, AndFactor.INSTANCE, OrFactor.NAME, OrFactor.INSTANCE); - private QuerySectionRegistry() {} + private FactorRegistry() {} - public static QuerySection selectorFor(String name) { - return SELECTORS.get(name); + public static Factor factorFor(final String name) { + return FACTORS.get(name); } } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySectionsReader.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySectionsReader.java deleted file mode 100644 index e023400..0000000 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySectionsReader.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 - * @link https://github.com/GeyserMC/DatabaseUtils - */ -package org.geysermc.databaseutils.processor.query.section; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Types; -import org.geysermc.databaseutils.processor.info.EntityInfo; -import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; -import org.geysermc.databaseutils.processor.util.TypeUtils; - -public final class QuerySectionsReader { - private final String actionName; - private final String name; - private final ExecutableElement element; - private final EntityInfo info; - private final Types typeUtils; - - public QuerySectionsReader( - String actionName, String name, ExecutableElement element, EntityInfo info, Types typeUtils) { - this.actionName = actionName; - this.name = name; - this.element = element; - this.info = info; - this.typeUtils = typeUtils; - } - - public List readBySections() { - return readSections((currentSections, currentSection, parameterCount) -> { - var selector = QuerySectionRegistry.selectorFor(currentSection); - if (selector != null) { - return new StringToSectionsResult( - List.of(createVariable(currentSections, parameterCount++), selector), parameterCount); - } - return StringToSectionsResult.empty(); - }); - } - - private List readSections(StringToSections stringToSections) { - if (name.isEmpty()) { - throw new InvalidRepositoryException("Cannot %s nothing!", actionName); - } - - // - Takes readBySection()'s stringToSections as example. - - // (By)UniqueIdAndUsername will handled like: - // U -> no sections for *empty string* - // UniqueI -> no sections for 'unique' - // UniqueIdA -> no sections for 'Id' - // UniqueIdAndU -> section for 'And', adds 'uniqueId' as var and 'And' as and-selector - // * after loop * assume remaining is var, adds 'username' as var - - var sections = new ArrayList(); - var parameterCount = 0; - StringBuilder currentSections = new StringBuilder(); - StringBuilder currentSection = new StringBuilder(); - - for (int i = 0; i < name.length(); i++) { - char current = name.charAt(i); - if (Character.isUpperCase(current)) { - // this can be a new section! - var result = stringToSections.sectionsFor( - currentSections.toString(), currentSection.toString(), parameterCount); - - if (!result.sections().isEmpty()) { - sections.addAll(result.sections()); - parameterCount = result.parameterCount(); - currentSections = new StringBuilder(); - } else { - currentSections.append(currentSection); - } - currentSection = new StringBuilder(); - - // UpperCamelCase -> camelCase - if (currentSections.isEmpty()) { - current = Character.toLowerCase(current); - } - } - currentSection.append(current); - } - - // cannot have a selector as the last action - if (currentSection.isEmpty() && currentSections.isEmpty()) { - throw new InvalidRepositoryException("Cannot end a %s with a selector", actionName); - } - - // assume everything remaining is a variable - currentSections.append(currentSection); - sections.add(createVariable(currentSections.toString(), parameterCount++)); - - if (element.getParameters().size() != parameterCount) { - throw new InvalidRepositoryException( - "Expected %s parameters for %s, got %s", - parameterCount, - element.getSimpleName(), - element.getParameters().size()); - } - - return sections; - } - - private VariableSection createVariable(String variableName, int parameterCount) { - var column = info.columnFor(variableName); - if (column == null) { - throw new InvalidRepositoryException("Cannot find column '%s' in %s", variableName, info.className()); - } - var parameter = element.getParameters().get(parameterCount); - var parameterType = - TypeUtils.toBoxedTypeElement(parameter.asType(), typeUtils).getQualifiedName(); - if (!parameterType.contentEquals(column.typeName())) { - throw new InvalidRepositoryException("Column '%s' type %s doesn't match parameter type %s"); - } - return new VariableSection(variableName); - } - - @FunctionalInterface - private interface StringToSections { - StringToSectionsResult sectionsFor(String currentSections, String currentSection, int parameterCount); - } - - private record StringToSectionsResult(List sections, int parameterCount) { - private static StringToSectionsResult empty() { - return new StringToSectionsResult(Collections.emptyList(), -1); - } - } -} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/InputKeywordRegistry.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/InputKeywordRegistry.java new file mode 100644 index 0000000..a112969 --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/InputKeywordRegistry.java @@ -0,0 +1,54 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.by; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.databaseutils.processor.query.section.by.keyword.EqualsKeyword; +import org.geysermc.databaseutils.processor.query.section.by.keyword.LessThanKeyword; + +public class InputKeywordRegistry { + private static final Map> REGISTRY = new HashMap<>(); + + public static @Nullable MultiInputKeyword findByName(String keyword) { + var supplier = REGISTRY.get(keyword); + return supplier != null ? supplier.get() : null; + } + + private static void register(Supplier keywordSupplier) { + var instance = keywordSupplier.get(); + for (@NonNull String name : instance.names()) { + REGISTRY.put(name, keywordSupplier); + } + } + + static { + register(EqualsKeyword::new); + register(LessThanKeyword::new); + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/selector/OrSelector.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/Keyword.java similarity index 80% rename from ap/src/main/java/org/geysermc/databaseutils/processor/query/section/selector/OrSelector.java rename to ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/Keyword.java index 3fcb03b..a780872 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/selector/OrSelector.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/Keyword.java @@ -22,12 +22,11 @@ * @author GeyserMC * @link https://github.com/GeyserMC/DatabaseUtils */ -package org.geysermc.databaseutils.processor.query.section.selector; +package org.geysermc.databaseutils.processor.query.section.by; -import org.geysermc.databaseutils.processor.query.section.QuerySection; +import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; -public final class OrSelector implements QuerySection { - public static final OrSelector INSTANCE = new OrSelector(); - - private OrSelector() {} +public interface Keyword { + @NonNull List<@NonNull String> names(); } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/MultiInputKeyword.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/MultiInputKeyword.java new file mode 100644 index 0000000..982c017 --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/MultiInputKeyword.java @@ -0,0 +1,93 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.by; + +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.databaseutils.processor.info.ColumnInfo; +import org.geysermc.databaseutils.processor.util.CollectionUtils; +import org.geysermc.databaseutils.processor.util.TypeUtils; + +/** + * A keyword that requires multiple inputs from the user. + */ +public abstract class MultiInputKeyword implements Keyword { + private final List parameterNames = new ArrayList<>(); + + /** + * Returns for each input it's supported types + */ + public abstract List>> acceptedInputs(); + + public int inputCount() { + return acceptedInputs().size(); + } + + public void validateTypes( + ColumnInfo column, + List inputTypes, + List inputNames, + int typeOffset, + TypeUtils typeUtils) { + + if (typeOffset + inputCount() > inputTypes.size()) { + throw new IllegalStateException(String.format( + "Expected (at least) %s inputs, got %s", typeOffset + inputCount(), inputTypes.size())); + } + + for (int i = 0; i < inputCount(); i++) { + var type = inputTypes.get(typeOffset + i); + var name = inputNames.get(typeOffset + i); + + if (!typeUtils.isAssignable(column.typeName(), type)) { + throw new IllegalStateException(String.format( + "Expected a type assignable from column %s as %s with type %s, got %s", + column.name(), name, column.typeName(), typeUtils.canonicalName(type))); + } + + var acceptedTypes = acceptedInputs().get(i); + if (acceptedTypes.stream().noneMatch(clazz -> typeUtils.isAssignable(type, clazz))) { + throw new IllegalStateException(String.format( + "Unsupported type provided for parameter %s of %s. Received %s, expected %s", + name, + getClass().getName(), + typeUtils.canonicalName(type), + CollectionUtils.join(acceptedTypes))); + } + + addParameterName(name); + } + } + + public @NonNull List<@NonNull CharSequence> parameterNames() { + return parameterNames; + } + + public void addParameterName(@NonNull CharSequence parameterName) { + parameterNames.add(parameterName); + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/SingleInputKeyword.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/SingleInputKeyword.java new file mode 100644 index 0000000..40cd06b --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/SingleInputKeyword.java @@ -0,0 +1,41 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.by; + +import java.util.List; + +/** + * A keyword that only supports a single input + */ +public abstract class SingleInputKeyword extends MultiInputKeyword { + /** + * Returns the types the input supports + */ + public abstract List> acceptedInput(); + + public List>> acceptedInputs() { + return List.of(acceptedInput()); + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/keyword/EqualsKeyword.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/keyword/EqualsKeyword.java new file mode 100644 index 0000000..823d2d7 --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/keyword/EqualsKeyword.java @@ -0,0 +1,41 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.by.keyword; + +import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.databaseutils.processor.query.section.by.SingleInputKeyword; + +public final class EqualsKeyword extends SingleInputKeyword { + @Override + public List> acceptedInput() { + return List.of(Object.class); + } + + @Override + public @NonNull List<@NonNull String> names() { + return List.of("Equals", "Is"); + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/keyword/LessThanKeyword.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/keyword/LessThanKeyword.java new file mode 100644 index 0000000..1e1349c --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/by/keyword/LessThanKeyword.java @@ -0,0 +1,41 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.by.keyword; + +import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.databaseutils.processor.query.section.by.SingleInputKeyword; + +public final class LessThanKeyword extends SingleInputKeyword { + @Override + public List> acceptedInput() { + return List.of(Number.class); + } + + @Override + public @NonNull List<@NonNull String> names() { + return List.of("LessThan"); + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/selector/AndSelector.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/AndFactor.java similarity index 80% rename from ap/src/main/java/org/geysermc/databaseutils/processor/query/section/selector/AndSelector.java rename to ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/AndFactor.java index 6f6a8a1..064c224 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/selector/AndSelector.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/AndFactor.java @@ -22,12 +22,11 @@ * @author GeyserMC * @link https://github.com/GeyserMC/DatabaseUtils */ -package org.geysermc.databaseutils.processor.query.section.selector; +package org.geysermc.databaseutils.processor.query.section.factor; -import org.geysermc.databaseutils.processor.query.section.QuerySection; +public final class AndFactor implements Factor { + public static final AndFactor INSTANCE = new AndFactor(); + public static final String NAME = "And"; -public final class AndSelector implements QuerySection { - public static final AndSelector INSTANCE = new AndSelector(); - - private AndSelector() {} + private AndFactor() {} } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySection.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/Factor.java similarity index 92% rename from ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySection.java rename to ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/Factor.java index 4b1f798..c8f9e33 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/QuerySection.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/Factor.java @@ -22,6 +22,6 @@ * @author GeyserMC * @link https://github.com/GeyserMC/DatabaseUtils */ -package org.geysermc.databaseutils.processor.query.section; +package org.geysermc.databaseutils.processor.query.section.factor; -public interface QuerySection {} +public interface Factor {} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/OrFactor.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/OrFactor.java new file mode 100644 index 0000000..3d58fe7 --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/OrFactor.java @@ -0,0 +1,32 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.factor; + +public final class OrFactor implements Factor { + public static final OrFactor INSTANCE = new OrFactor(); + public static final String NAME = "Or"; + + private OrFactor() {} +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/VariableByFactor.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/VariableByFactor.java new file mode 100644 index 0000000..dc1d071 --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/VariableByFactor.java @@ -0,0 +1,35 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.factor; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.databaseutils.processor.query.section.by.MultiInputKeyword; +import org.geysermc.databaseutils.processor.query.section.by.keyword.EqualsKeyword; + +public record VariableByFactor(@NonNull CharSequence name, @NonNull MultiInputKeyword keyword) implements Factor { + public VariableByFactor(CharSequence name) { + this(name, new EqualsKeyword()); + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/VariableOrderByFactor.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/VariableOrderByFactor.java new file mode 100644 index 0000000..3a4a9e4 --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/factor/VariableOrderByFactor.java @@ -0,0 +1,35 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.factor; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.databaseutils.processor.query.section.order.OrderDirection; + +public record VariableOrderByFactor(CharSequence name, OrderDirection direction) implements Factor { + public VariableOrderByFactor(@NonNull CharSequence name, @NonNull OrderDirection direction) { + this.name = name; + this.direction = direction; + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/order/OrderBySection.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/order/OrderBySection.java new file mode 100644 index 0000000..f6d4efe --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/order/OrderBySection.java @@ -0,0 +1,31 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.order; + +import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.databaseutils.processor.query.section.factor.Factor; + +public record OrderBySection(@NonNull List<@NonNull Factor> factors) {} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/order/OrderDirection.java b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/order/OrderDirection.java new file mode 100644 index 0000000..636459f --- /dev/null +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/query/section/order/OrderDirection.java @@ -0,0 +1,54 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query.section.order; + +import java.util.List; + +public enum OrderDirection { + ASCENDING("asc", "ascending"), + DESCENDING("desc", "descending"); + + public static final OrderDirection DEFAULT = ASCENDING; + private static final OrderDirection[] VALUES = values(); + + private final List names; + + OrderDirection(String... names) { + this.names = List.of(names); + } + + public static OrderDirection byName(String name) { + for (OrderDirection value : VALUES) { + if (value.names.contains(name)) { + return value; + } + } + return null; + } + + public List names() { + return names; + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/type/RepositoryGenerator.java b/ap/src/main/java/org/geysermc/databaseutils/processor/type/RepositoryGenerator.java index 8df6446..421c607 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/type/RepositoryGenerator.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/type/RepositoryGenerator.java @@ -31,7 +31,6 @@ import java.util.concurrent.CompletableFuture; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; import org.geysermc.databaseutils.codec.TypeCodec; import org.geysermc.databaseutils.codec.TypeCodecRegistry; import org.geysermc.databaseutils.processor.info.ColumnInfo; @@ -49,20 +48,15 @@ public abstract class RepositoryGenerator { protected void onConstructorBuilder(MethodSpec.Builder builder) {} - public abstract void addFindBy(QueryInfo info, MethodSpec.Builder spec, boolean async); + public abstract void addFind(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async); - public abstract void addExistsBy(QueryInfo info, MethodSpec.Builder spec, boolean async); + public abstract void addExists(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async); - public abstract void addDeleteBy(QueryInfo info, MethodSpec.Builder spec, boolean async); + public abstract void addInsert(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async); - public abstract void addInsert( - EntityInfo info, TypeElement returnType, VariableElement parameter, MethodSpec.Builder spec, boolean async); + public abstract void addUpdate(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async); - public abstract void addUpdate( - EntityInfo info, TypeElement returnType, VariableElement parameter, MethodSpec.Builder spec, boolean async); - - public abstract void addDelete( - EntityInfo info, TypeElement returnType, VariableElement parameter, MethodSpec.Builder spec, boolean async); + public abstract void addDelete(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async); public void init(TypeElement superType, EntityInfo entityInfo) { if (this.typeSpec != null) { diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/type/SqlDatabaseGenerator.java b/ap/src/main/java/org/geysermc/databaseutils/processor/type/SqlDatabaseGenerator.java index 063d750..eeefb5c 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/type/SqlDatabaseGenerator.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/type/SqlDatabaseGenerator.java @@ -46,7 +46,7 @@ public Class databaseClass() { @Override protected void addEntities(Collection entities, MethodSpec.Builder method) { method.addException(SQLException.class); - method.addStatement("$T type = database.type()", SqlDialect.class); + method.addStatement("$T dialect = database.dialect()", SqlDialect.class); method.beginControlFlow("try ($T connection = database.dataSource().getConnection())", Connection.class); method.beginControlFlow("try ($T statement = connection.createStatement())", Statement.class); @@ -73,7 +73,7 @@ private CodeBlock.Builder addEntityQuery(EntityInfo entity) { } builder.add( - "\"$L \" + $T.sqlTypeFor($T.class, type) ", + "\"$L \" + $T.sqlTypeFor($T.class, dialect) ", column.name(), SqlTypeMappingRegistry.class, ClassName.bestGuess(column.typeName().toString())); diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/type/SqlRepositoryGenerator.java b/ap/src/main/java/org/geysermc/databaseutils/processor/type/SqlRepositoryGenerator.java index 52c7b70..af47e81 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/type/SqlRepositoryGenerator.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/type/SqlRepositoryGenerator.java @@ -38,19 +38,20 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletionException; -import java.util.function.Function; import java.util.function.Supplier; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.databaseutils.processor.info.ColumnInfo; -import org.geysermc.databaseutils.processor.info.EntityInfo; import org.geysermc.databaseutils.processor.query.QueryInfo; -import org.geysermc.databaseutils.processor.query.section.QuerySection; -import org.geysermc.databaseutils.processor.query.section.VariableSection; -import org.geysermc.databaseutils.processor.query.section.selector.AndSelector; -import org.geysermc.databaseutils.processor.query.section.selector.OrSelector; +import org.geysermc.databaseutils.processor.query.section.by.keyword.EqualsKeyword; +import org.geysermc.databaseutils.processor.query.section.by.keyword.LessThanKeyword; +import org.geysermc.databaseutils.processor.query.section.factor.AndFactor; +import org.geysermc.databaseutils.processor.query.section.factor.Factor; +import org.geysermc.databaseutils.processor.query.section.factor.OrFactor; +import org.geysermc.databaseutils.processor.query.section.factor.VariableByFactor; import org.geysermc.databaseutils.processor.util.InvalidRepositoryException; import org.geysermc.databaseutils.processor.util.TypeUtils; @@ -67,9 +68,10 @@ protected void onConstructorBuilder(MethodSpec.Builder builder) { } @Override - public void addFindBy(QueryInfo info, MethodSpec.Builder spec, boolean async) { - var query = "select * from %s where %s".formatted(info.tableName(), createWhereFor(info)); - addActionedQueryData(spec, async, query, info.variableNames(), info.parameterNames(), info::columnFor, () -> { + public void addFind(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async) { + // todo make it work without a By section + var query = "select * from %s where %s".formatted(info.tableName(), createWhereForAll(info)); + addExecuteQueryData(spec, async, query, info, () -> { spec.beginControlFlow("if (!result.next())"); spec.addStatement("return null"); spec.endControlFlow(); @@ -95,157 +97,151 @@ public void addFindBy(QueryInfo info, MethodSpec.Builder spec, boolean async) { } @Override - public void addExistsBy(QueryInfo info, MethodSpec.Builder spec, boolean async) { - var query = "select 1 from %s where %s".formatted(info.tableName(), createWhereFor(info)); - addActionedQueryData( - spec, - async, - query, - info.variableNames(), - info.parameterNames(), - info::columnFor, - () -> spec.addStatement("return result.next()")); + public void addExists(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async) { + // todo make it work without a By section + var query = "select 1 from %s where %s".formatted(info.tableName(), createWhereForAll(info)); + addExecuteQueryData(spec, async, query, info, () -> spec.addStatement("return result.next()")); } @Override - public void addDeleteBy(QueryInfo info, MethodSpec.Builder spec, boolean async) { - var query = "delete from %s where %s".formatted(info.tableName(), createWhereFor(info)); - addActionedUpdateData( - spec, - info.entityType(), - Void.class.getCanonicalName(), - async, - query, - info.variableNames(), - info.parameterNames(), - null, - info::columnFor); - } - - @Override - public void addInsert( - EntityInfo info, - TypeElement returnType, - VariableElement parameter, - MethodSpec.Builder spec, - boolean async) { + public void addInsert(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async) { var columnNames = String.join(",", info.columns().stream().map(ColumnInfo::name).toList()); var columnParameters = String.join(",", repeat("?", info.columns().size())); - var query = "insert into %s (%s) values (%s)".formatted(info.name(), columnNames, columnParameters); - addSimple(info, returnType, parameter, query, info.columns(), spec, async); + var query = "insert into %s (%s) values (%s)".formatted(info.tableName(), columnNames, columnParameters); + addUpdateQueryData(spec, returnType, async, query, info, info.columns()); } @Override - public void addUpdate( - EntityInfo info, - TypeElement returnType, - VariableElement parameter, - MethodSpec.Builder spec, - boolean async) { - var query = "update %s set %s where %s".formatted(info.name(), createSetFor(info), createWhereFor(info)); - var variables = new ArrayList<>(info.notKeyColumns()); - variables.addAll(info.keyColumns()); - addSimple(info, returnType, parameter, query, variables, spec, async); + public void addUpdate(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async) { + // todo make it work with By section + var query = + "update %s set %s where %s".formatted(info.tableName(), createSetFor(info), createWhereForKeys(info)); + addUpdateQueryData( + spec, returnType, async, query, info, info.entityInfo().notKeyFirstColumns()); } @Override - public void addDelete( - EntityInfo info, - TypeElement returnType, - VariableElement parameter, - MethodSpec.Builder spec, - boolean async) { - var query = "delete from %s where %s".formatted(info.name(), createWhereFor(info)); - addSimple(info, returnType, parameter, query, info.keyColumns(), spec, async); - } + public void addDelete(QueryInfo info, MethodSpec.Builder spec, TypeElement returnType, boolean async) { + if (info.hasBySection()) { + var query = "delete from %s where %s".formatted(info.tableName(), createWhereForAll(info)); + addUpdateQueryData(spec, Void.class.getCanonicalName(), async, query, info); + return; + } - private void addSimple( - EntityInfo info, - TypeElement returnType, - VariableElement parameter, - String query, - List variables, - MethodSpec.Builder spec, - boolean async) { - var variableNames = - variables.stream().map(column -> column.name().toString()).toList(); - var variableFormat = parameter.getSimpleName() + ".%s()"; - addActionedUpdateData( - spec, - info.className(), - returnType.getQualifiedName(), - async, - query, - variableNames, - List.of(parameter.getSimpleName()), - variableFormat, - info::columnFor); + var query = "delete from %s where %s".formatted(info.tableName(), createWhereForKeys(info)); + addUpdateQueryData( + spec, returnType, async, query, info, info.entityInfo().keyColumns()); } - private void addActionedQueryData( - MethodSpec.Builder spec, - boolean async, - String query, - List variableNames, - List parameterNames, - Function columnFor, - Runnable content) { - addActionedData(spec, async, query, variableNames, parameterNames, null, columnFor, () -> { + private void addExecuteQueryData( + MethodSpec.Builder spec, boolean async, String query, QueryInfo info, Runnable content) { + addBySectionData(spec, async, query, info, () -> { spec.beginControlFlow("try ($T result = statement.executeQuery())", ResultSet.class); content.run(); spec.endControlFlow(); }); } - private void addActionedUpdateData( + private void addUpdateQueryData( MethodSpec.Builder spec, - CharSequence entityType, - CharSequence returnType, + TypeElement returnType, boolean async, String query, - List variableNames, - List parameterNames, - String variableFormat, - Function columnFor) { - addActionedData(spec, async, query, variableNames, parameterNames, variableFormat, columnFor, () -> { + QueryInfo info, + List columns) { + addNoBySectionData(spec, async, query, info, columns, () -> { spec.addStatement("statement.executeUpdate()"); - if (TypeUtils.isTypeOf(Void.class, returnType)) { + if (TypeUtils.isType(Void.class, returnType)) { spec.addStatement("return null"); - } else if (TypeUtils.isTypeOf(entityType, returnType)) { + } else if (TypeUtils.isType(info.entityType(), returnType)) { // todo support also creating an entity type from the given parameters - spec.addStatement("return $L", parameterNames.get(0)); + spec.addStatement("return $L", info.parameterName(0)); } else { throw new InvalidRepositoryException( - "Return type can be either void or %s but got %s", entityType, returnType); + "Return type can be either void or %s but got %s", + info.entityType(), returnType.getQualifiedName()); } }); } - private void addActionedData( + private void addUpdateQueryData( + MethodSpec.Builder spec, CharSequence returnType, boolean async, String query, QueryInfo info) { + addBySectionData(spec, async, query, info, () -> { + spec.addStatement("statement.executeUpdate()"); + if (TypeUtils.isType(Void.class, returnType)) { + spec.addStatement("return null"); + } else if (TypeUtils.isType(info.entityType(), returnType)) { + // todo support also creating an entity type from the given parameters + spec.addStatement("return $L", info.parameterName(0)); + } else { + throw new InvalidRepositoryException( + "Return type can be either void or %s but got %s", info.entityType(), returnType); + } + }); + } + + private void addNoBySectionData( MethodSpec.Builder spec, boolean async, String query, - List variableNames, - List parameterNames, - String variableFormat, - Function columnFor, + QueryInfo info, + List columns, Runnable content) { wrapInCompletableFuture(spec, async, () -> { spec.beginControlFlow("try ($T connection = dataSource.getConnection())", Connection.class); spec.beginControlFlow( "try ($T statement = connection.prepareStatement($S))", PreparedStatement.class, query); - for (int i = 0; i < variableNames.size(); i++) { - var name = variableNames.get(i); - var columnType = columnFor.apply(name).typeName(); - var input = variableFormat != null ? variableFormat.formatted(name) : parameterNames.get(i); + // if it doesn't have a By section, we add all the requested columns + var parameterName = info.parameterName(0); + int variableIndex = 0; + for (ColumnInfo column : columns) { + var columnName = column.name(); + var columnType = column.typeName(); + var input = "%s.%s()".formatted(parameterName, columnName); if (TypeUtils.needsTypeCodec(columnType)) { - input = CodeBlock.of("this.__$L.encode($L)", name, input).toString(); + input = CodeBlock.of("this.__$L.encode($L)", columnName, input) + .toString(); } + // jdbc index starts at 1 + spec.addStatement(jdbcSetFor(columnType, "statement.%s($L, $L)"), ++variableIndex, input); + } + + content.run(); + + spec.endControlFlow(); + spec.nextControlFlow("catch ($T exception)", SQLException.class); + spec.addStatement("throw new $T($S, exception)", CompletionException.class, "Unexpected error occurred"); + spec.endControlFlow(); + }); + typeSpec.addMethod(spec.build()); + } - spec.addStatement(jdbcSetFor(columnType, "statement.%s($L, $L)"), i + 1, input); + private void addBySectionData( + MethodSpec.Builder spec, boolean async, String query, QueryInfo info, Runnable content) { + wrapInCompletableFuture(spec, async, () -> { + spec.beginControlFlow("try ($T connection = dataSource.getConnection())", Connection.class); + spec.beginControlFlow( + "try ($T statement = connection.prepareStatement($S))", PreparedStatement.class, query); + + // if it has a By section, everything is handled through the parameters + int variableIndex = 0; + for (VariableByFactor variable : info.byVariables()) { + var columnName = variable.name(); + var columnType = + Objects.requireNonNull(info.columnFor(columnName)).typeName(); + + for (@NonNull CharSequence parameterName : variable.keyword().parameterNames()) { + var input = parameterName; + if (TypeUtils.needsTypeCodec(columnType)) { + input = CodeBlock.of("this.__$L.encode($L)", columnName, input) + .toString(); + } + // jdbc index starts at 1 + spec.addStatement(jdbcSetFor(columnType, "statement.%s($L, $L)"), ++variableIndex, input); + } } content.run(); @@ -258,46 +254,58 @@ private void addActionedData( typeSpec.addMethod(spec.build()); } - private String createSetFor(EntityInfo info) { - return createParametersForColumns(info.notKeyColumns(), null, ','); + private String createSetFor(QueryInfo info) { + return createParametersForColumns(info.entityInfo().notKeyColumns(), null, ','); } - private String createWhereFor(EntityInfo info) { - return createParametersForColumns(info.keyColumns(), () -> AndSelector.INSTANCE, ' '); + private String createWhereForKeys(QueryInfo info) { + return createParametersForColumns(info.entityInfo().keyColumns(), () -> AndFactor.INSTANCE, ' '); } - private String createWhereFor(QueryInfo info) { - return createParametersForSections(info.sections(), ' '); + private String createWhereForAll(QueryInfo info) { + return createParametersForFactors(info.bySectionFactors(), ' '); } private String createParametersForColumns( - List columns, Supplier sectionSeparator, char separator) { - var sections = new ArrayList(); + List columns, Supplier factorSeparator, char separator) { + var factors = new ArrayList(); for (ColumnInfo column : columns) { - if (!sections.isEmpty() && sectionSeparator != null) { - sections.add(sectionSeparator.get()); + if (!factors.isEmpty() && factorSeparator != null) { + factors.add(factorSeparator.get()); } - sections.add(new VariableSection(column.name())); + factors.add(new VariableByFactor(column.name())); } - return createParametersForSections(sections, separator); + return createParametersForFactors(factors, separator); } - private String createParametersForSections(List sections, char separator) { + private String createParametersForFactors(List factors, char separator) { var builder = new StringBuilder(); - for (QuerySection section : sections) { + for (Factor factor : factors) { if (!builder.isEmpty()) { builder.append(separator); } - if (section instanceof VariableSection variable) { - builder.append(variable.name()).append("=?"); - } else if (section instanceof AndSelector) { + if (factor instanceof AndFactor) { builder.append("and"); - } else if (section instanceof OrSelector) { + continue; + } + if (factor instanceof OrFactor) { builder.append("or"); - } else { + continue; + } + if (!(factor instanceof VariableByFactor variable)) { throw new InvalidRepositoryException( - "Unknown action type %s", section.getClass().getCanonicalName()); + "Unknown factor type %s", factor.getClass().getCanonicalName()); + } + var keyword = variable.keyword(); + + builder.append(variable.name()); + if (keyword instanceof EqualsKeyword) { + builder.append("=?"); + } else if (keyword instanceof LessThanKeyword) { + builder.append(" + * + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.util; + +import java.util.Collection; + +public class CollectionUtils { + public static String join(Collection items, int offset) { + return join(items.stream().skip(offset).toList()); + } + + public static String join(Collection items) { + return String.join(", ", items.stream().map(Object::toString).toList()); + } +} diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/util/StringUtils.java b/ap/src/main/java/org/geysermc/databaseutils/processor/util/StringUtils.java index 7a3fff3..41b3281 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/util/StringUtils.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/util/StringUtils.java @@ -26,17 +26,17 @@ import java.util.ArrayList; import java.util.List; -import java.util.Locale; +import java.util.function.Function; public class StringUtils { private StringUtils() {} - public static String capitalize(String string) { - return string.substring(0, 1).toUpperCase(Locale.ROOT) + string.substring(1); + public static String capitalize(CharSequence input) { + return String.valueOf(Character.toUpperCase(input.charAt(0))) + input.subSequence(1, input.length()); } - public static String uncapitalize(String string) { - return string.substring(0, 1).toLowerCase(Locale.ROOT) + string.substring(1); + public static String uncapitalize(CharSequence input) { + return String.valueOf(Character.toLowerCase(input.charAt(0))) + input.subSequence(1, input.length()); } public static List repeat(String string, int count) { @@ -46,4 +46,29 @@ public static List repeat(String string, int count) { } return list; } + + public static LargestMatchResult largestMatch(List parts, int offset, Function matcher) { + var greatestMatch = offset; + T response = null; + var workingVariable = new StringBuilder(); + + for (int i = offset; i < parts.size(); i++) { + var current = parts.get(i); + workingVariable.append(current); + + var finalVariable = workingVariable.toString(); + var finalResult = matcher.apply(finalVariable); + if (finalResult != null) { + greatestMatch = i; + response = finalResult; + } + } + + if (response == null) { + return null; + } + return new LargestMatchResult<>(response, greatestMatch); + } + + public record LargestMatchResult(T match, int offset) {} } diff --git a/ap/src/main/java/org/geysermc/databaseutils/processor/util/TypeUtils.java b/ap/src/main/java/org/geysermc/databaseutils/processor/util/TypeUtils.java index a2e5808..2c60ab9 100644 --- a/ap/src/main/java/org/geysermc/databaseutils/processor/util/TypeUtils.java +++ b/ap/src/main/java/org/geysermc/databaseutils/processor/util/TypeUtils.java @@ -29,35 +29,66 @@ import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; import javax.lang.model.util.Types; public final class TypeUtils { - private TypeUtils() {} + private final Types typeUtils; + private final Elements elementUtils; - public static TypeElement toBoxedTypeElement(TypeMirror mirror, Types typeUtils) { + public TypeUtils(Types typeUtils, Elements elementUtils) { + this.typeUtils = typeUtils; + this.elementUtils = elementUtils; + } + + public TypeElement toBoxedTypeElement(TypeMirror mirror) { if (mirror.getKind().isPrimitive()) { return typeUtils.boxedClass(MoreTypes.asPrimitiveType(mirror)); } return MoreTypes.asTypeElement(mirror); } - public static boolean isTypeOf(Class clazz, TypeElement element) { - return isTypeOf(clazz, element.getQualifiedName()); + public boolean isAssignable(TypeMirror impl, TypeMirror base) { + return typeUtils.isAssignable(impl, base); + } + + public boolean isAssignable(CharSequence impl, TypeMirror base) { + return isAssignable(elementUtils.getTypeElement(impl).asType(), base); + } + + public boolean isAssignable(Class impl, TypeMirror base) { + return isAssignable(impl.getCanonicalName(), base); + } + + public boolean isAssignable(TypeMirror impl, CharSequence base) { + return isAssignable(impl, elementUtils.getTypeElement(base).asType()); } - public static boolean isTypeOf(Class clazz, CharSequence canonicalName) { + public boolean isAssignable(TypeMirror impl, Class base) { + return isAssignable(impl, base.getCanonicalName()); + } + + public CharSequence canonicalName(TypeMirror mirror) { + return toBoxedTypeElement(mirror).getQualifiedName(); + } + + public static boolean isType(Class clazz, TypeElement element) { + return isType(clazz, element.getQualifiedName()); + } + + public static boolean isType(Class clazz, CharSequence canonicalName) { return clazz.getCanonicalName().contentEquals(canonicalName); } - public static boolean isTypeOf(CharSequence name, TypeElement element) { + public static boolean isType(CharSequence name, TypeElement element) { return element.getQualifiedName().contentEquals(name); } - public static boolean isTypeOf(CharSequence expected, TypeMirror actual) { - return isTypeOf(expected, MoreTypes.asTypeElement(actual)); + public static boolean isType(CharSequence expected, TypeMirror actual) { + return isType(expected, MoreTypes.asTypeElement(actual)); } - public static boolean isTypeOf(CharSequence expected, CharSequence actual) { + public static boolean isType(CharSequence expected, CharSequence actual) { return CharSequence.compare(expected, actual) == 0; } @@ -87,4 +118,11 @@ public static boolean needsTypeCodec(Name className) { .findAny() .isEmpty(); } + + public static boolean isWholeNumberType(TypeElement element) { + return isType(Byte.class, element) + || isType(Short.class, element) + || isType(Integer.class, element) + || isType(Long.class, element); + } } diff --git a/ap/src/test/java/org/geysermc/databaseutils/processor/query/KeywordsReaderTests.java b/ap/src/test/java/org/geysermc/databaseutils/processor/query/KeywordsReaderTests.java new file mode 100644 index 0000000..8a60fb2 --- /dev/null +++ b/ap/src/test/java/org/geysermc/databaseutils/processor/query/KeywordsReaderTests.java @@ -0,0 +1,270 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ +package org.geysermc.databaseutils.processor.query; + +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import org.geysermc.databaseutils.processor.query.section.by.keyword.EqualsKeyword; +import org.geysermc.databaseutils.processor.query.section.by.keyword.LessThanKeyword; +import org.geysermc.databaseutils.processor.query.section.factor.AndFactor; +import org.geysermc.databaseutils.processor.query.section.factor.Factor; +import org.geysermc.databaseutils.processor.query.section.factor.OrFactor; +import org.geysermc.databaseutils.processor.query.section.factor.VariableByFactor; +import org.geysermc.databaseutils.processor.query.section.factor.VariableOrderByFactor; +import org.geysermc.databaseutils.processor.query.section.order.OrderDirection; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class KeywordsReaderTests { + @ParameterizedTest + @MethodSource("okSimpleInputs") + void okSimple( + String input, List variables, String action, List byFactors, List orderByFactors) { + commonOkLogic(input, variables, action, byFactors, orderByFactors); + } + + @ParameterizedTest + @MethodSource("okComplexInputs") + void okComplex( + String input, List variables, String action, List byFactors, List orderByFactors) { + commonOkLogic(input, variables, action, byFactors, orderByFactors); + } + + private void commonOkLogic( + String input, List variables, String action, List byFactors, List orderByFactors) { + var result = new KeywordsReader(input, variables).read(); + Assertions.assertEquals(action, result.actionName()); + + if (byFactors == null) { + Assertions.assertNull(result.bySection()); + } else { + Assertions.assertNotNull(result.bySection()); + var actualVariables = result.bySection().factors(); + Assertions.assertEquals(byFactors.size(), actualVariables.size()); + + for (int i = 0; i < byFactors.size(); i++) { + Assertions.assertEquals(byFactors.get(i), actualVariables.get(i)); + } + } + + if (orderByFactors == null) { + Assertions.assertNull(result.orderBySection()); + } else { + Assertions.assertNotNull(result.orderBySection()); + var actualVariables = result.orderBySection().factors(); + Assertions.assertEquals(orderByFactors.size(), actualVariables.size()); + + for (int i = 0; i < orderByFactors.size(); i++) { + Assertions.assertEquals(orderByFactors.get(i), actualVariables.get(i)); + } + } + } + + static Stream okSimpleInputs() { + return Stream.of( + arguments("update", Collections.emptyList(), "update", null, null), + arguments( + "updateByUsername", + List.of("username"), + "update", + List.of(new VariableByFactor("username", new EqualsKeyword())), + null), + arguments("find", Collections.emptyList(), "find", null, null), + arguments( + "findByUsername", + List.of("username"), + "find", + List.of(new VariableByFactor("username", new EqualsKeyword())), + null), + arguments( + "findByUniqueId", + List.of("uniqueId"), + "find", + List.of(new VariableByFactor("uniqueId", new EqualsKeyword())), + null), + arguments( + "findByUsernameLessThan", + List.of("username"), + "find", + List.of(new VariableByFactor("username", new LessThanKeyword())), + null), + arguments( + "findByUniqueIdLessThan", + List.of("uniqueId"), + "find", + List.of(new VariableByFactor("uniqueId", new LessThanKeyword())), + null), + arguments( + "findOrderByUsername", + List.of("username"), + "find", + null, + List.of(new VariableOrderByFactor("username", OrderDirection.DEFAULT))), + arguments( + "findOrderByUsernameAsc", + List.of("username"), + "find", + null, + List.of(new VariableOrderByFactor("username", OrderDirection.ASCENDING))), + arguments( + "findByUsernameOrderByUsername", + List.of("username"), + "find", + List.of(new VariableByFactor("username", new EqualsKeyword())), + List.of(new VariableOrderByFactor("username", OrderDirection.DEFAULT))), + arguments( + "findByUsernameOrderByUsernameAsc", + List.of("username"), + "find", + List.of(new VariableByFactor("username", new EqualsKeyword())), + List.of(new VariableOrderByFactor("username", OrderDirection.ASCENDING))), + arguments( + "findByUsernameOrderByUsernameDesc", + List.of("username"), + "find", + List.of(new VariableByFactor("username", new EqualsKeyword())), + List.of(new VariableOrderByFactor("username", OrderDirection.DESCENDING))), + arguments( + "findByUniqueIdOrderByUsername", + List.of("uniqueId", "username"), + "find", + List.of(new VariableByFactor("uniqueId", new EqualsKeyword())), + List.of(new VariableOrderByFactor("username", OrderDirection.DEFAULT))), + arguments( + "findByUsernameLessThanOrderByUniqueId", + List.of("username", "uniqueId"), + "find", + List.of(new VariableByFactor("username", new LessThanKeyword())), + List.of(new VariableOrderByFactor("uniqueId", OrderDirection.DEFAULT))), + arguments( + "findByUniqueIdLessThanOrderByUniqueId", + List.of("uniqueId"), + "find", + List.of(new VariableByFactor("uniqueId", new LessThanKeyword())), + List.of(new VariableOrderByFactor("uniqueId", OrderDirection.DEFAULT)))); + } + + static Stream okComplexInputs() { + return Stream.of( + arguments( + "findByUsernameAndPassword", + List.of("username", "password"), + "find", + List.of( + new VariableByFactor("username", new EqualsKeyword()), + AndFactor.INSTANCE, + new VariableByFactor("password", new EqualsKeyword())), + null), + arguments( + "findByUniqueIdAndPassword", + List.of("uniqueId", "password"), + "find", + List.of( + new VariableByFactor("uniqueId", new EqualsKeyword()), + AndFactor.INSTANCE, + new VariableByFactor("password", new EqualsKeyword())), + null), + arguments( + "findByUniqueIdAndUniqueName", + List.of("uniqueId", "uniqueName"), + "find", + List.of( + new VariableByFactor("uniqueId", new EqualsKeyword()), + AndFactor.INSTANCE, + new VariableByFactor("uniqueName", new EqualsKeyword())), + null), + arguments( + "findByUsernameLessThanAndPassword", + List.of("username", "password"), + "find", + List.of( + new VariableByFactor("username", new LessThanKeyword()), + AndFactor.INSTANCE, + new VariableByFactor("password", new EqualsKeyword())), + null), + arguments( + "findByUniqueIdLessThanOrMyHash", + List.of("uniqueId", "myHash"), + "find", + List.of( + new VariableByFactor("uniqueId", new LessThanKeyword()), + OrFactor.INSTANCE, + new VariableByFactor("myHash", new EqualsKeyword())), + null), + arguments( + "findOrderByUsernameOrEmail", + List.of("username", "email"), + "find", + null, + List.of( + new VariableOrderByFactor("username", OrderDirection.DEFAULT), + OrFactor.INSTANCE, + new VariableOrderByFactor("email", OrderDirection.DEFAULT))), + arguments( + "findOrderByUsernameAscOrPassword", + List.of("username", "password"), + "find", + null, + List.of( + new VariableOrderByFactor("username", OrderDirection.ASCENDING), + OrFactor.INSTANCE, + new VariableOrderByFactor("password", OrderDirection.DEFAULT))), + arguments( + "findByUsernameOrderByUsernameAndPassword", + List.of("username", "password"), + "find", + List.of(new VariableByFactor("username", new EqualsKeyword())), + List.of( + new VariableOrderByFactor("username", OrderDirection.DEFAULT), + AndFactor.INSTANCE, + new VariableOrderByFactor("password", OrderDirection.DEFAULT))), + arguments( + "findByUsernameOrderByUsernameAscOrPasswordDesc", + List.of("username", "password"), + "find", + List.of(new VariableByFactor("username", new EqualsKeyword())), + List.of( + new VariableOrderByFactor("username", OrderDirection.ASCENDING), + OrFactor.INSTANCE, + new VariableOrderByFactor("password", OrderDirection.DESCENDING))), + arguments( + "findByUniqueIdLessThanOrUsernameOrderByUniqueIdAndPingDesc", + List.of("uniqueId", "username", "ping"), + "find", + List.of( + new VariableByFactor("uniqueId", new LessThanKeyword()), + OrFactor.INSTANCE, + new VariableByFactor("username", new EqualsKeyword())), + List.of( + new VariableOrderByFactor("uniqueId", OrderDirection.DEFAULT), + AndFactor.INSTANCE, + new VariableOrderByFactor("ping", OrderDirection.DESCENDING)))); + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index d63ed36..0faacba 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -6,6 +6,6 @@ dependencies { testRuntimeOnly(libs.h2) testAnnotationProcessor(projects.ap) - testImplementation(libs.junit.api) + testImplementation(libs.bundles.junit) testRuntimeOnly(libs.junit.engine) } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/databaseutils/DatabaseType.java b/core/src/main/java/org/geysermc/databaseutils/DatabaseType.java index 7ccab7c..0b709c1 100644 --- a/core/src/main/java/org/geysermc/databaseutils/DatabaseType.java +++ b/core/src/main/java/org/geysermc/databaseutils/DatabaseType.java @@ -1,3 +1,27 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ package org.geysermc.databaseutils; public enum DatabaseType { diff --git a/core/src/main/java/org/geysermc/databaseutils/DatabaseWithDialectType.java b/core/src/main/java/org/geysermc/databaseutils/DatabaseWithDialectType.java index ab06838..2742151 100644 --- a/core/src/main/java/org/geysermc/databaseutils/DatabaseWithDialectType.java +++ b/core/src/main/java/org/geysermc/databaseutils/DatabaseWithDialectType.java @@ -1,3 +1,27 @@ +/* + * 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 + * @link https://github.com/GeyserMC/DatabaseUtils + */ package org.geysermc.databaseutils; import java.util.Locale; diff --git a/core/src/main/java/org/geysermc/databaseutils/sql/SqlDialect.java b/core/src/main/java/org/geysermc/databaseutils/sql/SqlDialect.java index 57a9d07..bbfac6f 100644 --- a/core/src/main/java/org/geysermc/databaseutils/sql/SqlDialect.java +++ b/core/src/main/java/org/geysermc/databaseutils/sql/SqlDialect.java @@ -30,7 +30,7 @@ public enum SqlDialect { MYSQL("org.mariadb.jdbc.Driver"), ORACLE_DATABASE("oracle.jdbc.driver.OracleDriver"), POSTGRESQL("org.postgresql.Driver"), - SQLITE("org.sqlite.JDBC"),; + SQLITE("org.sqlite.JDBC"); private final String driverName; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2b242e6..9832326 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ h2 = "2.2.224" javapoet = "1.10.0" auto-service = "1.1.1" compile-testing = "0.21.0" -junit = "5.9.1" +junit = "5.10.0" indra = "3.1.3" @@ -22,8 +22,12 @@ compile-testing = { module = "com.google.testing.compile:compile-testing", versi junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } +junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } [plugins] indra = { id = "net.kyori.indra", version.ref = "indra" } indra-publishing = { id = "net.kyori.indra.publishing", version.ref = "indra" } -indra-licenser-spotless = { id = "net.kyori.indra.licenser.spotless", version.ref = "indra" } \ No newline at end of file +indra-licenser-spotless = { id = "net.kyori.indra.licenser.spotless", version.ref = "indra" } + +[bundles] +junit = ["junit-api", "junit-params"] \ No newline at end of file