diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/CQueryPlanKey.java b/ebean-core/src/main/java/io/ebeaninternal/api/CQueryPlanKey.java index f23aa4edb..0b55df4d7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/CQueryPlanKey.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/CQueryPlanKey.java @@ -5,6 +5,11 @@ package io.ebeaninternal.api; */ public interface CQueryPlanKey { + /** + * Return true if the query plan should be cached. + */ + boolean useCache(); + /** * Used by read audit such that we can log read audit entries without the full sql * (which would make the read audit logs verbose). diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index f1a47a02f..22a4e816e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -1503,7 +1503,9 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { } public void queryPlan(CQueryPlanKey key, CQueryPlan plan) { - queryPlanCache.put(key, plan); + if (key.useCache()) { + queryPlanCache.put(key, plan); + } } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index 5a7c47351..0830aa566 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -612,9 +612,9 @@ public class DefaultExpressionList implements SpiExpressionList { } for (SpiExpression expr : list) { expr.queryPlanHash(builder); - builder.append(","); + builder.append(','); } - builder.append("]"); + builder.append(']'); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InTuplesExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InTuplesExpression.java index b281ef7a2..90be80a46 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InTuplesExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InTuplesExpression.java @@ -66,7 +66,7 @@ final class InTuplesExpression extends AbstractExpression { if (maxInBinding == 0) { return 5000 / propertyCount; } - return Math.min((maxInBinding / propertyCount) - 200, 5000); + return (maxInBinding / propertyCount) - 200; } @Override @@ -159,19 +159,19 @@ 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"); } builder.append("InTuple["); - if (literalMode) { - builder.append(UUID.randomUUID()); - } else { - for (String property : properties) { - builder.append(property).append("-"); - } - builder.append(entries.size()); + for (String property : properties) { + builder.append(property).append("-"); } - builder.append("]"); + builder.append(entries.size()).append(']'); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/NativeSqlQueryPlanKey.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/NativeSqlQueryPlanKey.java index 2c5c7c4b4..86f8d8d4e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/NativeSqlQueryPlanKey.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/NativeSqlQueryPlanKey.java @@ -18,6 +18,11 @@ public class NativeSqlQueryPlanKey implements CQueryPlanKey { return partialKey(); } + @Override + public boolean useCache() { + return true; + } + @Override public CQueryPlanKey withDeleteByIds() { throw new IllegalStateException("Not allowed"); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/RawSqlQueryPlanKey.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/RawSqlQueryPlanKey.java index 77546dee6..ca38cdad0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/RawSqlQueryPlanKey.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/RawSqlQueryPlanKey.java @@ -24,6 +24,11 @@ final class RawSqlQueryPlanKey implements CQueryPlanKey { return partialKey() + ":r"; } + @Override + public boolean useCache() { + return true; + } + @Override public CQueryPlanKey withDeleteByIds() { throw new IllegalStateException("Not allowed"); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index a3bf1024a..ad8328b5c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -1047,13 +1047,13 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { if (temporalMode != SpiQuery.TemporalMode.CURRENT) { sb.append("/tm").append(temporalMode.ordinal()); if (versionsStart != null) { - sb.append("v"); + sb.append('v'); } } if (forUpdate != null) { sb.append("/fu").append(forUpdate.ordinal()); if (lockType != null) { - sb.append("t").append(lockType.ordinal()); + sb.append('t').append(lockType.ordinal()); } } if (id != null) { @@ -1092,27 +1092,27 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { if (detail != null) { sb.append("/d["); detail.queryPlanHash(sb); - sb.append("]"); + sb.append(']'); } if (bindParams != null) { sb.append("/b["); bindParams.buildQueryPlanHash(sb); - sb.append("]"); + sb.append(']'); } if (whereExpressions != null) { sb.append("/w["); whereExpressions.queryPlanHash(sb); - sb.append("]"); + sb.append(']'); } if (havingExpressions != null) { sb.append("/h["); havingExpressions.queryPlanHash(sb); - sb.append("]"); + sb.append(']'); } if (updateProperties != null) { sb.append("/u["); updateProperties.buildQueryPlanHash(sb); - sb.append("]"); + sb.append(']'); } return sb.toString(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java index f58ca42f0..72f79837c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java @@ -32,6 +32,11 @@ final class OrmQueryPlanKey implements CQueryPlanKey { return new OrmQueryPlanKey(description + "/deleteByIds", 0, 0, null); } + @Override + public boolean useCache() { + return !description.startsWith("$NoCache"); + } + @Override public String partialKey() { return description; diff --git a/ebean-test/src/test/java/org/tests/model/aggregation/TestInTuplesWithLocalDate.java b/ebean-test/src/test/java/org/tests/model/aggregation/TestInTuplesWithLocalDate.java index 19775f1fa..b1e9a2fca 100644 --- a/ebean-test/src/test/java/org/tests/model/aggregation/TestInTuplesWithLocalDate.java +++ b/ebean-test/src/test/java/org/tests/model/aggregation/TestInTuplesWithLocalDate.java @@ -81,9 +81,11 @@ class TestInTuplesWithLocalDate extends BaseTestCase { assertThat(sql).hasSize(2); 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)"); + 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)"); + 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); diff --git a/ebean-test/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey3.java b/ebean-test/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey3.java index 2c893c784..01d12b476 100644 --- a/ebean-test/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey3.java +++ b/ebean-test/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey3.java @@ -459,11 +459,11 @@ public class TestCacheViaComplexNaturalKey3 extends BaseTestCase { @Test void inTuples_literalMode() { InTuples tuples = InTuples.of("sku", "code"); - // add more entries than threshold triggers literal mode 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(UUID.randomUUID(), i); + tuples.add("x", i); } LoggedSql.start(); @@ -478,6 +478,11 @@ public class TestCacheViaComplexNaturalKey3 extends BaseTestCase { List sql = LoggedSql.stop(); - assertSql(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and (t0.sku,t0.code) in (('hi',123),('bye',121),('"); + 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),('"); + } } } diff --git a/platforms/postgres/src/main/java/io/ebean/platform/postgres/PostgresPlatform.java b/platforms/postgres/src/main/java/io/ebean/platform/postgres/PostgresPlatform.java index fbad23e3b..dd4a9c863 100644 --- a/platforms/postgres/src/main/java/io/ebean/platform/postgres/PostgresPlatform.java +++ b/platforms/postgres/src/main/java/io/ebean/platform/postgres/PostgresPlatform.java @@ -28,6 +28,7 @@ public class PostgresPlatform extends DatabasePlatform { public PostgresPlatform() { super(); this.platform = Platform.POSTGRES; + this.maxInBinding = 32_000; // technically 32_767 this.supportsNativeIlike = true; this.supportsDeleteTableAlias = true; this.selectCountWithAlias = true; diff --git a/platforms/sqlite/src/main/java/io/ebean/platform/sqlite/SQLitePlatform.java b/platforms/sqlite/src/main/java/io/ebean/platform/sqlite/SQLitePlatform.java index cfb2b2877..ba6f288de 100644 --- a/platforms/sqlite/src/main/java/io/ebean/platform/sqlite/SQLitePlatform.java +++ b/platforms/sqlite/src/main/java/io/ebean/platform/sqlite/SQLitePlatform.java @@ -13,6 +13,7 @@ public class SQLitePlatform extends DatabasePlatform { public SQLitePlatform() { super(); this.platform = Platform.SQLITE; + this.maxInBinding = 800; // technically 999 this.dbIdentity.setIdType(IdType.IDENTITY); this.dbIdentity.setSupportsGetGeneratedKeys(false); this.dbIdentity.setSupportsSequence(false);