Skip to content

Commit

Permalink
Added type mappings, parameter name flexibility and fixed some bugs
Browse files Browse the repository at this point in the history
Parameter names can now be different from the variable names
  • Loading branch information
Tim203 committed Feb 10, 2024
1 parent ddcce6b commit c09ed2e
Show file tree
Hide file tree
Showing 21 changed files with 351 additions and 60 deletions.
25 changes: 25 additions & 0 deletions database/sql/README.md → README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
DatabaseUtils is a library that allows you to write Repository interfaces that support both SQL and No-SQL (MongoDB) databases.
The implementation of the interface is automatically generated while compiling.

This readme will be expanded in the future with for example code examples,
currently examples can be found in the tests of the AP module and the tests of the core module.

# Supported types
Every database type is responsible for providing support for at least:
- Boolean
- Byte
- Short
- Char
- Integer
- Long
- Float
- Double
- String
- Byte[]

Using TypeCodecRegistry the supported types can be expanded (each of them will be stored as a byte[]).
The following types are added out of the box:
- UUID

# SQL

This project tries to support the following SQL dialects:

- H2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private List<RepositoryGenerator> processRepository(TypeElement repository) {

var generators = RegisteredGenerators.repositoryGenerators();
for (var generator : generators) {
generator.init(repository);
generator.init(repository, entity);
}

for (Element enclosedElement : repository.getEnclosedElements()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
package org.geysermc.databaseutils.processor.query;

import java.util.List;
import java.util.stream.Stream;
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,
Expand All @@ -42,4 +44,15 @@ public ColumnInfo columnFor(CharSequence columnName) {
}
return null;
}

public List<CharSequence> variableNames() {
return sections.stream()
.flatMap(section -> {
if (section instanceof VariableSection variable) {
return Stream.of(variable.name());
}
return null;
})
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.function.BiFunction;
import javax.lang.model.element.Modifier;
import org.geysermc.databaseutils.IRepository;
import org.geysermc.databaseutils.codec.TypeCodecRegistry;
import org.geysermc.databaseutils.processor.info.EntityInfo;

public abstract class DatabaseGenerator {
Expand Down Expand Up @@ -71,13 +72,14 @@ public void addRepositories(List<String> repositoriesClassName) {
}
spec.addStaticBlock(builder.build());

// List<Function<dbClass, IRepository<?>>>
// List<BiFunction<dbClass, TypeCodecRegistry, IRepository<?>>>
spec.addField(
ParameterizedTypeName.get(
ClassName.get(List.class),
ParameterizedTypeName.get(
ClassName.get(Function.class),
ClassName.get(BiFunction.class),
ClassName.get(databaseClass()),
ClassName.get(TypeCodecRegistry.class),
ParameterizedTypeName.get(
ClassName.get(IRepository.class), WildcardTypeName.subtypeOf(Object.class)))),
"REPOSITORIES",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@
*/
package org.geysermc.databaseutils.processor.type;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
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;
import org.geysermc.databaseutils.processor.info.EntityInfo;
import org.geysermc.databaseutils.processor.query.QueryInfo;
import org.geysermc.databaseutils.processor.util.TypeUtils;
Expand All @@ -39,6 +43,7 @@ public abstract class RepositoryGenerator {
protected TypeSpec.Builder typeSpec;
protected boolean hasAsync;
private String packageName;
private EntityInfo entityInfo;

protected abstract String upperCamelCaseDatabaseType();

Expand All @@ -56,7 +61,7 @@ protected void onConstructorBuilder(MethodSpec.Builder builder) {}

public abstract void addDelete(EntityInfo info, VariableElement parameter, MethodSpec.Builder spec, boolean async);

public void init(TypeElement superType) {
public void init(TypeElement superType, EntityInfo entityInfo) {
if (this.typeSpec != null) {
throw new IllegalStateException("Cannot reinitialize RepositoryGenerator");
}
Expand All @@ -65,6 +70,7 @@ public void init(TypeElement superType) {
this.typeSpec = TypeSpec.classBuilder(className)
.addSuperinterface(ParameterizedTypeName.get(superType.asType()))
.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
this.entityInfo = entityInfo;
}

public String packageName() {
Expand All @@ -81,8 +87,21 @@ public TypeSpec.Builder finish(Class<?> databaseClass) {
var constructor = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(databaseClass, "database")
.addParameter(TypeCodecRegistry.class, "registry")
.addStatement("this.database = database");
onConstructorBuilder(constructor);

// pre-fetch TypeCodec
for (ColumnInfo column : entityInfo.columns()) {
if (!TypeUtils.needsTypeCodec(column.typeName())) {
continue;
}
var typeClassName = ClassName.bestGuess(column.typeName().toString());
var fieldType = ParameterizedTypeName.get(ClassName.get(TypeCodec.class), typeClassName);
typeSpec.addField(fieldType, "__" + column.name(), Modifier.PRIVATE, Modifier.FINAL);
constructor.addStatement("this.__$L = registry.requireCodecFor($T.class)", column.name(), typeClassName);
}

typeSpec.addMethod(constructor.build());
return typeSpec;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import static org.geysermc.databaseutils.processor.util.StringUtils.repeat;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
Expand All @@ -39,6 +40,7 @@
import java.util.List;
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.VariableElement;
import org.geysermc.databaseutils.processor.info.ColumnInfo;
Expand All @@ -49,6 +51,7 @@
import org.geysermc.databaseutils.processor.query.section.selector.AndSelector;
import org.geysermc.databaseutils.processor.query.section.selector.OrSelector;
import org.geysermc.databaseutils.processor.util.InvalidRepositoryException;
import org.geysermc.databaseutils.processor.util.TypeUtils;

public class SqlRepositoryGenerator extends RepositoryGenerator {
@Override
Expand All @@ -65,19 +68,22 @@ 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));
addActionedData(spec, async, query, info.parameterNames(), "%s", info::columnFor, () -> {
addActionedData(spec, async, query, info.variableNames(), info.parameterNames(), null, info::columnFor, () -> {
spec.beginControlFlow("if (!result.next())");
spec.addStatement("return null");
spec.endControlFlow();

var arguments = new ArrayList<String>();
for (ColumnInfo column : info.columns()) {
var columnType = ClassName.bestGuess(column.typeName().toString());
spec.addStatement(
jdbcGetFor(column.typeName(), "$T _$L = result.%s(%s)"),
columnType,
column.name(),
column.name());

var getFormat = jdbcGetFor(column.typeName(), "result.%s(%s)");
if (TypeUtils.needsTypeCodec(column.typeName())) {
getFormat = CodeBlock.of("this.__$L.decode($L)", column.name(), getFormat)
.toString();
}

spec.addStatement("$T _$L = %s".formatted(getFormat), columnType, column.name(), column.name());
arguments.add("_" + column.name());
}
spec.addStatement(
Expand All @@ -94,16 +100,17 @@ public void addExistsBy(QueryInfo info, MethodSpec.Builder spec, boolean async)
spec,
async,
query,
info.variableNames(),
info.parameterNames(),
"%s",
null,
info::columnFor,
() -> 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));
addActionedData(spec, async, query, info.parameterNames(), "%s", info::columnFor, null);
addActionedData(spec, async, query, info.variableNames(), info.parameterNames(), null, info::columnFor, null);
}

@Override
Expand All @@ -118,9 +125,9 @@ public void addInsert(EntityInfo info, VariableElement parameter, MethodSpec.Bui
@Override
public void addUpdate(EntityInfo info, VariableElement parameter, MethodSpec.Builder spec, boolean async) {
var query = "update %s set %s where %s".formatted(info.name(), createSetFor(info), createWhereFor(info));
var parameters = new ArrayList<>(info.notKeyColumns());
parameters.addAll(info.keyColumns());
addSimple(info, parameter, query, parameters, spec, async);
var variables = new ArrayList<>(info.notKeyColumns());
variables.addAll(info.keyColumns());
addSimple(info, parameter, query, variables, spec, async);
}

@Override
Expand All @@ -133,33 +140,39 @@ private void addSimple(
EntityInfo info,
VariableElement parameter,
String query,
List<ColumnInfo> parameters,
List<ColumnInfo> variables,
MethodSpec.Builder spec,
boolean async) {
var parameterNames =
parameters.stream().map(column -> column.name().toString()).toList();
var parameterFormat = parameter.getSimpleName() + ".%s()";
addActionedData(spec, async, query, parameterNames, parameterFormat, info::columnFor, null);
var variableNames =
variables.stream().map(column -> column.name().toString()).toList();
var variableFormat = parameter.getSimpleName() + ".%s()";
addActionedData(spec, async, query, variableNames, null, variableFormat, info::columnFor, null);
}

private void addActionedData(
MethodSpec.Builder spec,
boolean async,
String query,
List<? extends CharSequence> variableNames,
List<? extends CharSequence> parameterNames,
String parameterFormat,
String variableFormat,
Function<CharSequence, ColumnInfo> columnFor,
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 < parameterNames.size(); i++) {
var name = parameterNames.get(i);
for (int i = 0; i < variableNames.size(); i++) {
var name = variableNames.get(i);
var columnType = columnFor.apply(name).typeName();
spec.addStatement(
jdbcSetFor(columnType, "statement.%s($L, $L)"), i + 1, parameterFormat.formatted(name));
var input = variableFormat != null ? variableFormat.formatted(name) : parameterNames.get(i);

if (TypeUtils.needsTypeCodec(columnType)) {
input = CodeBlock.of("this.__$L.encode($L)", name, input).toString();
}

spec.addStatement(jdbcSetFor(columnType, "statement.%s($L, $L)"), i + 1, input);
}

if (content != null) {
Expand All @@ -180,22 +193,23 @@ private void addActionedData(
}

private String createSetFor(EntityInfo info) {
return createParametersForColumns(info.notKeyColumns(), ',');
return createParametersForColumns(info.notKeyColumns(), null, ',');
}

private String createWhereFor(EntityInfo info) {
return createParametersForColumns(info.keyColumns(), ' ');
return createParametersForColumns(info.keyColumns(), () -> AndSelector.INSTANCE, ' ');
}

private String createWhereFor(QueryInfo info) {
return createParametersForSections(info.sections(), ' ');
}

private String createParametersForColumns(List<ColumnInfo> columns, char separator) {
private String createParametersForColumns(
List<ColumnInfo> columns, Supplier<QuerySection> sectionSeparator, char separator) {
var sections = new ArrayList<QuerySection>();
for (ColumnInfo column : columns) {
if (!sections.isEmpty()) {
sections.add(AndSelector.INSTANCE);
if (!sections.isEmpty() && sectionSeparator != null) {
sections.add(sectionSeparator.get());
}
sections.add(new VariableSection(column.name()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ private static String jdbcTypeFor(Name typeName) {
if (mapping != null) {
return mapping;
}
return MAPPINGS.get(String.class.getCanonicalName());
return MAPPINGS.get(Byte[].class.getCanonicalName());
}

public static String jdbcGetFor(Name typeName, String format) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package org.geysermc.databaseutils.processor.util;

import com.google.auto.common.MoreTypes;
import java.util.Set;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
Expand Down Expand Up @@ -63,4 +64,23 @@ public static String packageNameFor(Name className) {
public static String packageNameFor(String className) {
return className.substring(0, className.lastIndexOf('.'));
}

public static boolean needsTypeCodec(Name className) {
// See README
return Set.of(
Boolean.class,
Byte.class,
Short.class,
Character.class,
Integer.class,
Long.class,
Float.class,
Double.class,
String.class,
Byte[].class)
.stream()
.filter(clazz -> className.contentEquals(clazz.getCanonicalName()))
.findAny()
.isEmpty();
}
}
4 changes: 2 additions & 2 deletions ap/src/test/resources/test/basic/BasicRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

@Repository
public interface BasicRepository extends IRepository<TestEntity> {
CompletableFuture<TestEntity> findByAAndB(int a, String b);
CompletableFuture<TestEntity> findByAAndB(int aa, String b);

CompletableFuture<Boolean> existsByAOrB(int a, String b);
CompletableFuture<Boolean> existsByAOrB(int a, String bb);

CompletableFuture<Void> update(TestEntity entity);

Expand Down
Loading

0 comments on commit c09ed2e

Please sign in to comment.