Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ebean-api/src/main/java/io/ebean/DatabaseBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,14 @@ default DatabaseBuilder namingConvention(NamingConvention namingConvention) {
@Deprecated
DatabaseBuilder setNamingConvention(NamingConvention namingConvention);

/**
* Set the AggregateFormulaContext which is used to determine if a database function
* is an aggregate function (like sum, min, max, avg etc).
* <p>
* Use this to override the default known aggregation functions.
*/
DatabaseConfig aggregateFormulaContext(AggregateFormulaContext aggregateFormulaContext);

/**
* Set to true if all DB column and table names should use quoted identifiers.
* <p>
Expand Down Expand Up @@ -2610,6 +2618,11 @@ interface Settings extends DatabaseBuilder {
*/
NamingConvention getNamingConvention();

/**
* Return the AggregateFormulaContext.
*/
AggregateFormulaContext aggregateFormulaContext();

/**
* Return true if all DB column and table names should use quoted identifiers.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.ebean.config;

import java.util.Set;

/**
* Used when parsing formulas to determine if they are aggregation formulas like
* sum, min, max, avg, count etc.
* <p>
* Ebean needs to determine if they are aggregation formulas to determine which
* properties should be included in a GROUP BY clause etc.
*/
public interface AggregateFormulaContext {

/**
* Return true if the outer function is an aggregate function (like sum, count, min, max, avg etc).
*/
boolean isAggregate(String outerFunction);

/**
* Return true if the aggregate function returns a BIGINT type.
* This is true for functions like count that return a numeric value regardless of the
* type of the property or expression inside the outer function.
*/
boolean isCount(String outerFunction);

/**
* Return true if the aggregate function returns a VARCHAR type.
* This is true for functions that return a string concatenation like group_concat etc
* regardless of the type of the property used inside the outer function.
*/
boolean isConcat(String outerFunction);

/**
* Return a builder for the AggregateFormulaContext.
*/
static Builder builder() {
return new AggregateFormulaContextBuilder();
}

/**
* A builder for the AggregateFormulaContext.
*/
interface Builder {

/**
* Override the default set of aggregation functions.
*/
Builder aggregateFunctions(Set<String> count);

/**
* Override the default set of concat functions.
*/
Builder concatFunctions(Set<String> concat);

/**
* Override the default set of count functions.
*/
Builder countFunctions(Set<String> count);

/**
* Build the AggregateFormulaContext.
*/
AggregateFormulaContext build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.ebean.config;

import java.util.Set;

final class AggregateFormulaContextBuilder implements AggregateFormulaContext.Builder {

private Set<String> aggFunctions = Set.of("count", "max", "min", "avg", "sum", "group_concat", "string_agg", "listagg");
private Set<String> concat = Set.of("concat", "group_concat", "string_agg", "listagg");
private Set<String> count = Set.of("count");

@Override
public AggregateFormulaContext.Builder aggregateFunctions(Set<String> agg) {
this.aggFunctions = agg;
return this;
}

@Override
public AggregateFormulaContext.Builder concatFunctions(Set<String> concat) {
this.concat = concat;
return this;
}

@Override
public AggregateFormulaContext.Builder countFunctions(Set<String> count) {
this.count = count;
return this;
}

@Override
public AggregateFormulaContext build() {
return new FormulaContext(aggFunctions, concat, count);
}

private static final class FormulaContext implements AggregateFormulaContext {

private final Set<String> aggFunctions;
private final Set<String> concat;
private final Set<String> count;

private FormulaContext(Set<String> aggFunctions, Set<String> concat, Set<String> count) {
this.aggFunctions = aggFunctions;
this.concat = concat;
this.count = count;
}

@Override
public boolean isAggregate(String outerFunction) {
return aggFunctions.contains(outerFunction);
}

@Override
public boolean isCount(String outerFunction) {
return count.contains(outerFunction);
}

@Override
public boolean isConcat(String outerFunction) {
return concat.contains(outerFunction);
}
}
}
13 changes: 13 additions & 0 deletions ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
*/
private NamingConvention namingConvention = new UnderscoreNamingConvention();

private AggregateFormulaContext aggregateFormulaContext = AggregateFormulaContext.builder().build();

/**
* Behaviour of updates in JDBC batch to by default include all properties.
*/
Expand Down Expand Up @@ -1279,6 +1281,17 @@ public DatabaseConfig setNamingConvention(NamingConvention namingConvention) {
return this;
}

@Override
public AggregateFormulaContext aggregateFormulaContext() {
return aggregateFormulaContext;
}

@Override
public DatabaseConfig aggregateFormulaContext(AggregateFormulaContext aggregateFormulaContext) {
this.aggregateFormulaContext = aggregateFormulaContext;
return this;
}

@Override
public boolean isAllQuotedIdentifiers() {
return platformConfig.isAllQuotedIdentifiers();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.ebean.config;

import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

class AggregateFormulaContextTest {

@Test
void defaultContext() {
var defaultContext = AggregateFormulaContext.builder().build();
for (String aggFunction : List.of("count", "max", "min", "avg", "sum", "group_concat", "string_agg", "listagg")) {
assertThat(defaultContext.isAggregate(aggFunction)).isTrue();
}
for (String c : List.of("concat", "group_concat", "string_agg", "listagg")) {
assertThat(defaultContext.isConcat(c)).isTrue();
}
for (String c : List.of("count")) {
assertThat(defaultContext.isCount(c)).isTrue();
}

assertThat(defaultContext.isConcat("junk")).isFalse();
assertThat(defaultContext.isCount("junk")).isFalse();
assertThat(defaultContext.isAggregate("junk")).isFalse();
}

@Test
void overrideAggregateFunctions() {
AggregateFormulaContext mySum = AggregateFormulaContext.builder()
.aggregateFunctions(Set.of("my_sum"))
.build();

assertThat(mySum.isAggregate("my_sum")).isTrue();
assertThat(mySum.isAggregate("avg")).isFalse();
assertThat(mySum.isCount("count")).isTrue();
assertThat(mySum.isConcat("group_concat")).isTrue();
}

@Test
void overrideConcatFunctions() {
AggregateFormulaContext myConcat = AggregateFormulaContext.builder()
.concatFunctions(Set.of("my_concat"))
.build();

assertThat(myConcat.isAggregate("avg")).isTrue();
assertThat(myConcat.isCount("count")).isTrue();
assertThat(myConcat.isConcat("group_concat")).isFalse();
assertThat(myConcat.isConcat("my_concat")).isTrue();
}

@Test
void overrideCountFunctions() {
AggregateFormulaContext myCount = AggregateFormulaContext.builder()
.countFunctions(Set.of("my_count"))
.build();

assertThat(myCount.isAggregate("avg")).isTrue();
assertThat(myCount.isCount("count")).isFalse();
assertThat(myCount.isCount("my_count")).isTrue();
assertThat(myCount.isConcat("group_concat")).isTrue();
}
}
10 changes: 10 additions & 0 deletions ebean-core/src/main/java/io/ebeaninternal/api/FormulaBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.ebeaninternal.api;

import io.ebean.config.AggregateFormulaContext;
import io.ebeaninternal.server.query.STreeProperty;

public interface FormulaBuilder {

STreeProperty create(AggregateFormulaContext context, String formula, String path);

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ public interface SpiBeanType {
* or removals from the collection.
*/
boolean isToManyDirty(EntityBean bean);

/**
* Return the FormulaBuilder for this type.
*/
FormulaBuilder formulaBuilder();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;

import io.ebeaninternal.server.query.STreeProperty;
import org.jspecify.annotations.Nullable;
import io.ebean.*;
import io.ebean.bean.BeanCollectionLoader;
Expand Down Expand Up @@ -377,4 +378,6 @@ public interface SpiEbeanServer extends SpiServer, BeanCollectionLoader {

@Nullable
SqlRow findOne(SpiSqlQuery query);

<T> STreeProperty createFormulaProperty(SpiBeanType desc, String formula, String path);
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final long slowQueryMicros;
private final SlowQueryListener slowQueryListener;
private final boolean disableL2Cache;
private final AggregateFormulaContext formulaContext;
private boolean shutdown;

/**
Expand All @@ -128,6 +129,7 @@ public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.backgroundExecutor = config.getBackgroundExecutor();
this.extraMetrics = config.getExtraMetrics();
this.serverName = this.config.getName();
this.formulaContext = config.getConfig().aggregateFormulaContext();
this.lazyLoadBatchSize = this.config.getLazyLoadBatchSize();
this.cqueryEngine = config.getCQueryEngine();
this.expressionFactory = config.getExpressionFactory();
Expand Down Expand Up @@ -928,6 +930,11 @@ public <T> T find(Class<T> beanType, Object id, @Nullable Transaction transactio
return findId(query);
}

@Override
public <T> STreeProperty createFormulaProperty(SpiBeanType desc, String formula, String path) {
return desc.formulaBuilder().create(formulaContext, formula, path);
}

<T> SpiOrmQueryRequest<T> createQueryRequest(Type type, SpiQuery<T> query) {
SpiOrmQueryRequest<T> request = buildQueryRequest(type, query);
request.prepareQuery();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2410,7 +2410,12 @@ boolean matchBaseTable(String tableName) {
*/
private STreeProperty findSqlTreeFormula(String formula, String path) {
String key = formula + "-" + path;
return dynamicProperty.computeIfAbsent(key, (fullKey) -> FormulaPropertyPath.create(this, formula, path));
return dynamicProperty.computeIfAbsent(key, (fullKey) -> ebeanServer.createFormulaProperty(this, formula, path));
}

@Override
public FormulaBuilder formulaBuilder() {
return new DFormulaBuilder(this);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.ebeaninternal.server.deploy;

import io.ebean.config.AggregateFormulaContext;
import io.ebeaninternal.api.FormulaBuilder;
import io.ebeaninternal.server.query.STreeProperty;

final class DFormulaBuilder implements FormulaBuilder {

private final BeanDescriptor<?> descriptor;

DFormulaBuilder(BeanDescriptor<?> descriptor) {
this.descriptor = descriptor;
}

@Override
public STreeProperty create(AggregateFormulaContext context, String formula, String path) {
return FormulaPropertyPath.create(descriptor, context, formula, path);
}
}
Loading