Skip to content

Commit

Permalink
Initial version of per dialect classes, support Oracle Database deleteBy
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim203 committed Sep 19, 2024
1 parent d8064f7 commit a49701d
Show file tree
Hide file tree
Showing 19 changed files with 1,144 additions and 166 deletions.
45 changes: 39 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,25 @@ However not everything is the same across dialects.
### Missing functionality for specific dialects

#### TestEntity deleteByAAndB(String, String)
This currently doesn't work on MySQL (not MariaDB), H2 and Oracle Database.
For MySQL and H2 this is caused by the lack of support for 'REPLACING' or a variant of it.
For those two dialect we have to fetch a record first and then delete it (inside a transaction).
For Oracle Database the reason is that unlike the other 'REPLACING' implementations this dialect
does not accept a wildcard.
This currently doesn't work on MySQL (not MariaDB) and H2.
This is caused by the lack of support for 'REPLACING' or a variant of it.
We have to fetch a record first and then delete it (inside a transaction).
The current codebase is not flexible enough to do these wildly different behaviours per dialect,
but will be supported in the future.

#### any deleteFirst() and any deleteTop*()
Anything with a limit projection in delete currently doesn't work for Oracle Database, PostgreSQL and SQLite.
For SQLite this is a flag that can be enabled during compile, but it's disabled by default.
For Oracle and Postgres delete with a limit doesn't exist.
We have to fetch a record first and then delete it (inside a transaction).
The current codebase is not flexible enough to do these wildly different behaviours per dialect,
but will be supported in the future.

#### any delete*OrderBy*()
Anything with an order by in delete currently doesn't work for Oracle Database, SQL Server, H2 and SQLite.
For SQLite this is a flag that can be enabled during compile, but it's disabled by default.
For the others this functionality doesn't exist.
We have to fetch a record first and then delete it (inside a transaction).
The current codebase is not flexible enough to do these wildly different behaviours per dialect,
but will be supported in the future.

Expand Down Expand Up @@ -85,4 +92,30 @@ And these are the exceptions:
| Boolean | SQL Server | bit |
| Double | SQL Server | float |
| Byte[] | PostgreSQL | bytea |
| Byte[] | SQLite | varchar |
| Byte[] | SQLite | varchar |

# Query syntax
Assuming we have the following entity called TestEntity:

| Name | Type | Is Key? |
|------|--------|---------|
| a | int | Yes |
| b | String | Yes |
| c | String | No |
| d | UUID | No |

## delete

#### int/boolean delete(TestEntity) - delete single entity
If you delete a single entity, it'll look at the

### TestEntity deleteByAAndB(a, b) - delete by key
If you delete by the key then you know for certain that there can at most be only one match.

### List<TestEntity> deleteByB(b) - delete by a non-unique column
If you delete by a non-unique column everything matching that column is deleted.
Note that for the returning collection that not every database type has a defined order.

### TestEntity deleteFirstByB(b) - delete by a non-unique column
If you delete by a non-unique column you have to specify first if your return type is a single row.
Only use this if any match is fine, because not every database type has a defined order.
4 changes: 2 additions & 2 deletions ap/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
dependencies {
implementation(projects.core)

implementation(projects.databaseSql)

implementation(projects.databaseMongo)

implementation(libs.oracle) // OracleTypes

implementation(libs.javapoet)
implementation(libs.auto.service)
annotationProcessor(libs.auto.service)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
package org.geysermc.databaseutils.processor.query.type;

import static org.geysermc.databaseutils.processor.util.CollectionUtils.join;
import static org.geysermc.databaseutils.processor.util.CollectionUtils.map;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import org.geysermc.databaseutils.processor.action.Action;
Expand Down Expand Up @@ -72,6 +74,10 @@ public CharSequence firstName() {
return name(0);
}

public List<CharSequence> names() {
return map(element.getParameters(), VariableElement::getSimpleName);
}

public boolean hasParameters() {
return !element.getParameters().isEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public abstract class RepositoryGenerator {
protected TypeSpec.Builder typeSpec;
protected boolean hasAsync;
protected EntityInfo entityInfo;

private String packageName;
private String className;

protected RepositoryGenerator(DatabaseCategory category) {
this.category = category;
Expand All @@ -49,7 +51,7 @@ public void init(TypeElement superType, EntityInfo entityInfo) {
throw new IllegalStateException("Cannot reinitialize RepositoryGenerator");
}
this.packageName = TypeUtils.packageNameFor(superType.getQualifiedName());
var className = superType.getSimpleName() + category.upperCamelCaseName() + "Impl";
this.className = superType.getSimpleName() + category.upperCamelCaseName() + "Impl";
this.typeSpec = TypeSpec.classBuilder(className)
.addSuperinterface(ParameterizedTypeName.get(superType.asType()))
.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
Expand All @@ -60,6 +62,10 @@ public String packageName() {
return packageName;
}

public String className() {
return className;
}

public boolean hasAsync() {
return hasAsync;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@

public final class JdbcTypeMappingRegistry {
private static final Map<String, String> MAPPINGS = new HashMap<>();
private static final Map<String, String> CONVERT_GET_FORMAT = new HashMap<>();
private static final Map<String, String> CONVERT_SET_FORMAT = new HashMap<>();
private static final Map<String, String> CONVERT_FORMAT = new HashMap<>();

private JdbcTypeMappingRegistry() {}

Expand All @@ -26,15 +25,19 @@ private static String jdbcTypeFor(Name typeName) {
return MAPPINGS.get(Byte[].class.getCanonicalName());
}

public static String jdbcReadFor(Name typeName, String format) {
return CONVERT_FORMAT
.getOrDefault(String.valueOf(typeName), "%s")
.formatted(format.formatted("read" + jdbcTypeFor(typeName) + "()"));
}

public static String jdbcGetFor(Name typeName, String format) {
return format.formatted("get" + jdbcTypeFor(typeName) + "("
+ CONVERT_GET_FORMAT
.getOrDefault(String.valueOf(typeName), "%s")
.formatted("$S") + ")");
+ CONVERT_FORMAT.getOrDefault(String.valueOf(typeName), "%s").formatted("$S") + ")");
}

public static String jdbcSetFor(Name typeName, String format) {
return CONVERT_SET_FORMAT
return CONVERT_FORMAT
.getOrDefault(String.valueOf(typeName), "%s")
.formatted(format.formatted("set" + jdbcTypeFor(typeName)));
}
Expand All @@ -49,18 +52,7 @@ private static void addMapping(Class<?> type, String mapping) {

private static void add(Class<?> type, String mapping, String convertBothFormat) {
addMapping(type, mapping);
CONVERT_GET_FORMAT.put(type.getCanonicalName(), convertBothFormat);
CONVERT_SET_FORMAT.put(type.getCanonicalName(), convertBothFormat);
}

private static void add(Class<?> type, String mapping, String convertGetFormat, String convertSetFormat) {
addMapping(type, mapping);
if (convertGetFormat != null) {
CONVERT_GET_FORMAT.put(type.getCanonicalName(), convertGetFormat);
}
if (convertSetFormat != null) {
CONVERT_SET_FORMAT.put(type.getCanonicalName(), convertSetFormat);
}
CONVERT_FORMAT.put(type.getCanonicalName(), convertBothFormat);
}

static {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public class QueryBuilder {
private final StringBuilder endQuery = new StringBuilder();
private final List<QueryBuilderColumn> columns = new ArrayList<>();
private int parameterIndex = 0;
private boolean dialectDepending = false;

public QueryBuilder(QueryContext queryContext) {
this.queryContext = queryContext;
Expand All @@ -36,15 +35,6 @@ public List<QueryBuilderColumn> columns() {
return columns;
}

public boolean dialectDepending() {
return dialectDepending;
}

public QueryBuilder dialectDepending(boolean dialectDepending) {
this.dialectDepending = dialectDepending;
return this;
}

public QueryBuilder add(String queryPart, BiFunction<QueryContext, QueryBuilder, String> function) {
return addRaw(queryPart, function.apply(queryContext, this));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Locale;
import org.geysermc.databaseutils.DatabaseCategory;
import org.geysermc.databaseutils.processor.info.ColumnInfo;
import org.geysermc.databaseutils.processor.info.EntityInfo;
Expand Down Expand Up @@ -39,6 +40,9 @@ protected void addEntities(Collection<EntityInfo> entities, MethodSpec.Builder m

for (EntityInfo entity : entities) {
method.addStatement("statement.executeUpdate($L)", createEntityQuery(entity));
method.beginControlFlow("if (dialect == $T.$L)", SqlDialect.class, SqlDialect.ORACLE_DATABASE);
createRowTypes(entity, method);
method.endControlFlow();
}

method.endControlFlow();
Expand All @@ -52,7 +56,37 @@ private CodeBlock createEntityQuery(EntityInfo entity) {
var builder = CodeBlock.builder();
// todo indexes are not added atm
builder.add("\"CREATE TABLE IF NOT EXISTS $L (\" +\n", entity.name());
createEntityQueryBody(entity, builder);
builder.add("+\n\")\"");
return builder.build();
}

private void createRowTypes(EntityInfo entity, MethodSpec.Builder method) {
var rowObject = CodeBlock.builder();
rowObject.add("$S +\n", "CREATE TYPE " + entity.name() + "_row AS OBJECT(");
createEntityQueryBody(entity, rowObject);
rowObject.add("+\n$S", ")");

method.addStatement("boolean rowExists = false");
method.beginControlFlow(
"try (var rs = statement.executeQuery($S))",
"SELECT COUNT(*) FROM USER_OBJECTS WHERE OBJECT_NAME = '%s' AND STATUS = 'VALID'"
.formatted((entity.name() + "_row").toUpperCase(Locale.ROOT)));
method.beginControlFlow("if (rs.next())");
method.addStatement("rowExists = rs.getInt(1) > 0");
method.endControlFlow();
method.endControlFlow();

// todo when adding versions take a more proper look into this
method.beginControlFlow("if (!rowExists)");
method.addStatement("statement.executeUpdate($L)", rowObject.build());
method.addStatement(
"statement.executeUpdate($S)",
"CREATE TYPE %s_table AS TABLE OF %s_row".formatted(entity.name(), entity.name()));
method.endControlFlow();
}

private void createEntityQueryBody(EntityInfo entity, CodeBlock.Builder builder) {
boolean first = true;
for (ColumnInfo column : entity.columns()) {
if (first) {
Expand All @@ -67,7 +101,5 @@ private CodeBlock createEntityQuery(EntityInfo entity) {
SqlTypeMappingRegistry.class,
column.asType());
}
builder.add("+\n\")\"");
return builder.build();
}
}
Loading

0 comments on commit a49701d

Please sign in to comment.