mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#3166 Remove InTuples literal mode
This commit is contained in:
@@ -31,11 +31,6 @@ public interface SpiExpressionRequest {
|
||||
*/
|
||||
SpiOrmQueryRequest<?> queryRequest();
|
||||
|
||||
/**
|
||||
* Return the underling buffer.
|
||||
*/
|
||||
StringBuilder buffer();
|
||||
|
||||
/**
|
||||
* Append to the expression sql without any parsing.
|
||||
*/
|
||||
|
||||
-5
@@ -109,11 +109,6 @@ public final class DefaultExpressionRequest implements SpiExpressionRequest {
|
||||
return queryRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringBuilder buffer() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiExpressionRequest append(String expression) {
|
||||
sql.append(expression);
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Adds values as literals to SQL string.
|
||||
*/
|
||||
interface InLiterals {
|
||||
|
||||
/**
|
||||
* Add the value as a SQL literal to the buffer.
|
||||
*/
|
||||
void append(StringBuilder buffer, Object value);
|
||||
|
||||
/**
|
||||
* Return the InLiterals for the type of the given value.
|
||||
*/
|
||||
static InLiterals of(Object val, Platform platform) {
|
||||
if (val instanceof Number) {
|
||||
return NumLiteral.INSTANCE;
|
||||
}
|
||||
if (val instanceof String || val instanceof UUID) {
|
||||
return StrLiteral.INSTANCE;
|
||||
}
|
||||
if (val instanceof LocalDate) {
|
||||
return platform.base() == Platform.MYSQL ? DateEscapeLiteral.INSTANCE : DateLiteral.INSTANCE;
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
final class NumLiteral implements InLiterals {
|
||||
|
||||
static NumLiteral INSTANCE = new NumLiteral();
|
||||
|
||||
@Override
|
||||
public void append(StringBuilder buffer, Object value) {
|
||||
buffer.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
final class StrLiteral implements InLiterals {
|
||||
|
||||
static StrLiteral INSTANCE = new StrLiteral();
|
||||
|
||||
@Override
|
||||
public void append(StringBuilder buffer, Object value) {
|
||||
buffer.append('\'').append(value).append('\'');
|
||||
}
|
||||
}
|
||||
|
||||
final class DateLiteral implements InLiterals {
|
||||
|
||||
static DateLiteral INSTANCE = new DateLiteral();
|
||||
|
||||
@Override
|
||||
public void append(StringBuilder buffer, Object value) {
|
||||
buffer.append("date ").append('\'').append(value).append('\'');
|
||||
}
|
||||
}
|
||||
|
||||
final class DateEscapeLiteral implements InLiterals {
|
||||
|
||||
static DateEscapeLiteral INSTANCE = new DateEscapeLiteral();
|
||||
|
||||
@Override
|
||||
public void append(StringBuilder buffer, Object value) {
|
||||
buffer.append("{d '").append(value).append("'}");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-74
@@ -1,8 +1,6 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.InTuples;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebean.service.SpiInTuples;
|
||||
import io.ebeaninternal.api.BindValuesKey;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
@@ -10,7 +8,6 @@ import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@@ -19,8 +16,6 @@ final class InTuplesExpression extends AbstractExpression {
|
||||
private final boolean not;
|
||||
private final String[] properties;
|
||||
private final List<Object[]> entries;
|
||||
private InLiterals[] literals;
|
||||
private boolean literalMode;
|
||||
|
||||
InTuplesExpression(InTuples pairs, boolean not) {
|
||||
super("");
|
||||
@@ -31,44 +26,6 @@ final class InTuplesExpression extends AbstractExpression {
|
||||
this.not = not;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareExpression(BeanQueryRequest<?> request) {
|
||||
if (entries.size() > 50) {
|
||||
// check if this should go into literal mode
|
||||
final int maxInBinding = request.database().pluginApi().databasePlatform().maxInBinding();
|
||||
final int threshold = literalThreshold(maxInBinding, properties.length);
|
||||
if (entries.size() > threshold) {
|
||||
literals = initLiterals(request.database().platform());
|
||||
literalMode = literals != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InLiterals[] initLiterals(Platform platform) {
|
||||
try {
|
||||
Object[] firstData = entries.get(0);
|
||||
final InLiterals[] literals = new InLiterals[firstData.length];
|
||||
for (int i = 0; i < firstData.length; i++) {
|
||||
literals[i] = InLiterals.of(firstData[i], platform);
|
||||
}
|
||||
return literals;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// one of the value types is not supported as a SQL literal
|
||||
// so stick to binding only
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the threshold in number of entries before literal mode is used.
|
||||
*/
|
||||
static int literalThreshold(int maxInBinding, int propertyCount) {
|
||||
if (maxInBinding == 0) {
|
||||
return 5000 / propertyCount;
|
||||
}
|
||||
return (maxInBinding / propertyCount) - 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData<?> data) {
|
||||
return false;
|
||||
@@ -81,9 +38,6 @@ final class InTuplesExpression extends AbstractExpression {
|
||||
|
||||
@Override
|
||||
public void addBindValues(SpiExpressionRequest request) {
|
||||
if (literalMode) {
|
||||
return;
|
||||
}
|
||||
for (Object[] entry : entries) {
|
||||
for (Object value : entry) {
|
||||
requireNonNull(value);
|
||||
@@ -106,32 +60,10 @@ final class InTuplesExpression extends AbstractExpression {
|
||||
request.property(properties[i]);
|
||||
}
|
||||
request.append(") in (");
|
||||
if (literalMode) {
|
||||
addSqlLiterals(request);
|
||||
} else {
|
||||
addSqlBinding(request);
|
||||
}
|
||||
addSqlBinding(request);
|
||||
request.append(')');
|
||||
}
|
||||
|
||||
private void addSqlLiterals(SpiExpressionRequest request) {
|
||||
final var buffer = request.buffer();
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
if (i > 0) {
|
||||
buffer.append(',');
|
||||
}
|
||||
buffer.append('(');
|
||||
Object[] values = entries.get(i);
|
||||
for (int v = 0; v < values.length; v++) {
|
||||
if (v > 0) {
|
||||
buffer.append(',');
|
||||
}
|
||||
literals[v].append(buffer, values[v]);
|
||||
}
|
||||
buffer.append(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void addSqlBinding(SpiExpressionRequest request) {
|
||||
final String eb = entryBinding();
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
@@ -159,11 +91,6 @@ final class InTuplesExpression extends AbstractExpression {
|
||||
*/
|
||||
@Override
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
if (literalMode) {
|
||||
builder.delete(0, builder.length());
|
||||
builder.append("$NoCache/").append(UUID.randomUUID()).append('/');
|
||||
return;
|
||||
}
|
||||
if (not) {
|
||||
builder.append("Not");
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class InLiteralsTest {
|
||||
|
||||
private static String using(Object value) {
|
||||
return using(value, Platform.GENERIC);
|
||||
}
|
||||
|
||||
private static String using(Object value, Platform platform) {
|
||||
InLiterals litInt = InLiterals.of(value, platform);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
litInt.append(sb, value);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
void of_numbers() {
|
||||
assertThat(using(42)).isEqualTo("42");
|
||||
assertThat(using(42d)).isEqualTo("42.0");
|
||||
assertThat(using(42L)).isEqualTo("42");
|
||||
assertThat(using(new BigDecimal("42.34"))).isEqualTo("42.34");
|
||||
assertThat(using(new BigInteger("42"))).isEqualTo("42");
|
||||
assertThat(using(Integer.valueOf(42))).isEqualTo("42");
|
||||
assertThat(using(Long.valueOf(42))).isEqualTo("42");
|
||||
}
|
||||
|
||||
@Test
|
||||
void of_str() {
|
||||
assertThat(using("hi")).isEqualTo("'hi'");
|
||||
assertThat(using("there")).isEqualTo("'there'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void of_uuid() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
assertThat(using(uuid)).isEqualTo("'" + uuid + "'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void of_localDate() {
|
||||
assertThat(using(LocalDate.of(2023, 3, 7))).isEqualTo("date '2023-03-07'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void of_localDate_mysql() {
|
||||
assertThat(using(LocalDate.of(2023, 3, 7), Platform.MYSQL)).isEqualTo("{d '2023-03-07'}");
|
||||
}
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class InTuplesExpressionTest {
|
||||
|
||||
@Test
|
||||
void literalThreshold_maxInBindingZero_2properties() {
|
||||
int threshold = InTuplesExpression.literalThreshold(0, 2);
|
||||
assertThat(threshold).isEqualTo(2500);
|
||||
}
|
||||
|
||||
@Test
|
||||
void literalThreshold_maxInBindingZero_3properties() {
|
||||
int threshold = InTuplesExpression.literalThreshold(0, 3);
|
||||
assertThat(threshold).isEqualTo(1666);
|
||||
}
|
||||
|
||||
@Test
|
||||
void literalThreshold_maxInBindingZero_4properties() {
|
||||
int threshold = InTuplesExpression.literalThreshold(0, 4);
|
||||
assertThat(threshold).isEqualTo(1250);
|
||||
}
|
||||
|
||||
@Test
|
||||
void literalThreshold_sqlServer_2properties() {
|
||||
int threshold = InTuplesExpression.literalThreshold(2000, 2);
|
||||
assertThat(threshold).isEqualTo(800);
|
||||
}
|
||||
|
||||
@Test
|
||||
void literalThreshold_sqlServer_3properties() {
|
||||
int threshold = InTuplesExpression.literalThreshold(2000, 3);
|
||||
assertThat(threshold).isEqualTo(466);
|
||||
}
|
||||
|
||||
@Test
|
||||
void literalThreshold_sqlServer_4properties() {
|
||||
int threshold = InTuplesExpression.literalThreshold(2000, 4);
|
||||
assertThat(threshold).isEqualTo(300);
|
||||
}
|
||||
|
||||
@Test
|
||||
void literalThreshold_5000() {
|
||||
int threshold = InTuplesExpression.literalThreshold(5000, 2);
|
||||
assertThat(threshold).isEqualTo(2300);
|
||||
}
|
||||
|
||||
}
|
||||
-5
@@ -43,11 +43,6 @@ public class TDSpiExpressionRequest implements SpiExpressionRequest {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringBuilder buffer() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiExpressionRequest append(String expression) {
|
||||
sql.append(expression);
|
||||
|
||||
+1
-24
@@ -54,7 +54,6 @@ class TestInTuplesWithLocalDate extends BaseTestCase {
|
||||
.add(today.plusDays(1), 12)
|
||||
.add(today.plusDays(2), 15);
|
||||
|
||||
|
||||
LoggedSql.start();
|
||||
|
||||
List<DMachineStats> result = DB.find(DMachineStats.class)
|
||||
@@ -65,31 +64,9 @@ class TestInTuplesWithLocalDate extends BaseTestCase {
|
||||
|
||||
assertThat(result).hasSize(5);
|
||||
|
||||
var in2 = InTuples.of("date", "hours")
|
||||
.add(today, 0);
|
||||
|
||||
for (int i = 0; i < 3_000; i++) {
|
||||
in2.add(today, 100 + i);
|
||||
}
|
||||
|
||||
List<DMachineStats> result2 = DB.find(DMachineStats.class)
|
||||
.where()
|
||||
.inTuples(in2)
|
||||
//.raw("(t0.edate,t0.hours) in ((date '2023-08-16',0),(date '2023-08-16',100))")
|
||||
.eq("machine.name", "inTuple")
|
||||
.findList();
|
||||
|
||||
List<String> sql = LoggedSql.stop();
|
||||
|
||||
assertThat(sql).hasSize(2);
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(sql.get(0)).contains("where (t0.edate,t0.hours) in ((?,?),(?,?),(?,?),(?,?),(?,?)) and t1.name = ?");
|
||||
if (isMySql()) {
|
||||
assertThat(sql.get(1)).contains("where (t0.edate,t0.hours) in (({d '2023-08-16'},0),({d '2023-08-16'},100),({d '2023-08-16'},101),(");
|
||||
} else if (isPostgresCompatible()) {
|
||||
assertThat(sql.get(1)).contains("where (t0.edate,t0.hours) in ((?,?),(?,?),(?,?),(");
|
||||
} else {
|
||||
assertThat(sql.get(1)).contains("where (t0.edate,t0.hours) in ((date '2023-08-16',0),(date '2023-08-16',100),(date '2023-08-16',101),(");
|
||||
}
|
||||
|
||||
DB.deleteAll(allStats);
|
||||
DB.delete(machine);
|
||||
|
||||
-31
@@ -454,35 +454,4 @@ public class TestCacheViaComplexNaturalKey3 extends BaseTestCase {
|
||||
assertThat(list).hasSize(3);
|
||||
assertSql(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and (t0.sku,t0.code) in ((?,?),(?,?),(?,?)) order by t0.sku desc;");
|
||||
}
|
||||
|
||||
@IgnorePlatform({Platform.SQLSERVER, Platform.DB2})
|
||||
@Test
|
||||
void inTuples_literalMode() {
|
||||
InTuples tuples = InTuples.of("sku", "code");
|
||||
tuples.add("hi", 123);
|
||||
tuples.add("bye", 121);
|
||||
// add more entries than threshold triggers literal mode
|
||||
for (int i = 0; i < 5_000; i++) {
|
||||
tuples.add("x", i);
|
||||
}
|
||||
|
||||
LoggedSql.start();
|
||||
|
||||
DB.find(OCachedNatKeyBean3.class)
|
||||
.where()
|
||||
.eq("store", "def")
|
||||
.inTuples(tuples)
|
||||
.setUseCache(false)
|
||||
.orderBy("sku desc")
|
||||
.findList();
|
||||
|
||||
List<String> sql = LoggedSql.stop();
|
||||
|
||||
if (isPostgresCompatible()) {
|
||||
// didn't exceed postgres threshold
|
||||
assertSql(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and (t0.sku,t0.code) in ((?,?),(?,?),(");
|
||||
} else {
|
||||
assertSql(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and (t0.sku,t0.code) in (('hi',123),('bye',121),('");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user