Compare commits

...
Author SHA1 Message Date
Rob Bygrave 9ccaa824e0 Version 13.21.0 2023-08-17 22:51:00 +12:00
Rob BygraveandGitHub d6e46b15a3 Merge pull request #3167 from ebean-orm/feature/InTuples-Literal-skipCache
Change Lists.partition() argument order to match Guava's
2023-08-17 15:36:35 +12:00
robin.bygrave bab9be0228 Change Lists.partition() argument order to match Guava's
- May as well match what they have in terms of arg order
- Change from interface to class might be better
2023-08-17 15:36:03 +12:00
Rob BygraveandGitHub 9331050a43 Merge pull request #3166 from ebean-orm/feature/InTuples-Literal-skipCache
InTuples literal mode - don't cache ebean compiled query plan
2023-08-17 15:34:17 +12:00
robin.bygrave 9bcd189730 Test TestInTuplesWithLocalDate does not support SQLServer and DB2 yet 2023-08-17 15:24:28 +12:00
Rob BygraveandGitHub 428365ceb9 Merge pull request #3164 from FOCONIS/bugfix/drop_foreign_key_without_index
BUG: Include drop for foreign keys without index when dropping table
2023-08-17 15:12:19 +12:00
robin.bygrave 76c889c6d9 InTuples literal mode - don't cache ebean compiled query plan
- Don't cache ebean compiled query plan when InTuples goes into literal mode
- Postgres maxInBinding = 32_000;
- SQLite maxInBinding = 800;
- Note that not caching the compiled query plan means that metrics visitor isn't
  going to see it, we lose collection of that query execution metric. We might
  need a plan to fix this later.
2023-08-17 15:08:16 +12:00
Rob BygraveandGitHub 9e4007d25a Merge pull request #3165 from ebean-orm/feature/Lists-partitions
Add support for literal mode for InTuples with lots of entries
2023-08-17 10:31:03 +12:00
Rob Bygrave 9d7754b92d Add support for literal mode for InTuples with lots of entries
So as to not exceed the limit on bind values, the InTuples expression can
go into literal mode where it uses literal values for supported types -
Numbers, Strings, UUID, and LocalDate.
2023-08-17 00:33:56 +12:00
Rob BygraveandGitHub 3c80032063 Merge pull request #3163 from ebean-orm/feature/Lists-partitions
Add Lists.partition() helper method and use in DefaultPersister batch deletion
2023-08-17 00:32:04 +12:00
Jonas Pöhler 7dae937491 Generate working migration 2023-08-16 12:00:23 +02:00
Jonas Pöhler 6ce5e865ca FIX: also include foreign keys without index in drop statement 2023-08-16 11:59:50 +02:00
Jonas Pöhler b0d8fa0286 Generate failing migration 2023-08-16 11:58:57 +02:00
Jonas Pöhler 83c85adbde ADD: testcase 2023-08-16 11:57:34 +02:00
Rob Bygrave a1b51fd8ef For DefaultPersister batch deletion, use more explicit maxDeleteBatch 2023-08-16 17:55:41 +12:00
Rob Bygrave bcc4034371 Add Lists.partition() helper method and use in DefaultPersister batch deletion 2023-08-16 17:39:10 +12:00
Rob BygraveandGitHub 3dde07fc9f Merge pull request #3162 from ebean-orm/feature/3133-postgres
Tidy IdBinderEmbedded - merge use of getIdProperty() and idSelect()
2023-08-16 00:17:23 +12:00
Rob Bygrave 87ada9e999 IdBinder - refactor rename methods 2023-08-16 00:11:46 +12:00
Rob Bygrave ea55f25d44 Tidy IdBinderEmbedded - merge use of getIdProperty() and idSelect() to use idSelect()
Means we can remove getIdProperty()
2023-08-15 23:36:59 +12:00
Rob BygraveandGitHub 2231f723f8 Merge pull request #3161 from ebean-orm/feature/3133-postgres
Fix to #3133 changes for Postgres adding back support for MultiValue binding
2023-08-15 23:18:53 +12:00
Rob Bygrave 7c16694d4b Fix to #3133 changes for Postgres adding back support for MultiValue binding
The MultiValue binding that we use for Postgres, Yugabyte etc was lost with
the changes in #3133 and this puts that back.

We desire to use Postgres ANY Array binding for these cases.
2023-08-15 23:17:31 +12:00
Rob BygraveandGitHub ea60ff47cf Merge pull request #3160 from ebean-orm/feature/alsoIf
Add Query alsoIf() to conditionally apply changes to a query
2023-08-15 22:25:28 +12:00
Rob BygraveandGitHub 1f7912dab6 Merge pull request #3159 from ebean-orm/feature/idClass
Change IdClass property name to _$IdClass$
2023-08-15 21:59:56 +12:00
Rob Bygrave ec0c00eb72 Add Query alsoIf() to conditionally apply changes to a query 2023-08-15 21:56:50 +12:00
Rob Bygrave dd99e32cc6 Change IdClass property name to _$IdClass$ 2023-08-15 21:55:09 +12:00
Rob BygraveandGitHub 1decbe0152 Merge pull request #3147 from FOCONIS/rethrow-error-in-commit
FIX: Rethrow error in commit path of callbacks
2023-08-14 23:25:04 +12:00
Rob BygraveandGitHub d58e3d023f Merge pull request #3158 from ebean-orm/feature/tidy-idBinderEmbedded
Followup for #3133 - "Embedded id expansion on parentIds" for SQLServer and DB2
2023-08-14 23:06:28 +12:00
Rob Bygrave 1598b40c36 Followup for #3133 - "Embedded id expansion on parentIds" for SQLServer and DB2 2023-08-14 23:00:41 +12:00
Rob BygraveandGitHub 66ef667a0a Merge pull request #3157 from ebean-orm/feature/tidy-idBinderEmbedded
Tidy IdBinderEmbedded final fields, StringBuilder sizes and reuse code
2023-08-14 22:59:15 +12:00
Rob Bygrave e821d66268 Tidy IdBinderEmbedded final fields, StringBuilder sizes and reuse code 2023-08-14 15:10:00 +12:00
Rob BygraveandGitHub b8e164e04c Merge pull request #3148 from FOCONIS/fix-recursive-batch-flush
FIX: Recursive batch flush can save beans in wrong order
2023-08-14 13:55:50 +12:00
Rob BygraveandGitHub 9097466a7b Merge pull request #3145 from FOCONIS/m2m-where-formula
FIX: Query on formula m2o-property uses wrong alias:
2023-08-14 13:55:21 +12:00
Rob BygraveandGitHub 99604d8ec3 Merge pull request #3155 from ebean-orm/feature/3133-IdClass-match-on-name
#3133 - For IdClass match on property name
2023-08-14 13:53:58 +12:00
Rob Bygrave 95d37fc3dc Temporary ignore test TestCompositeForeignKey for DB2 and SQLServer 2023-08-14 13:41:34 +12:00
Rob Bygrave 76d85adc98 Fix tests for #3133 MySql and SQLServer which require column ordering of PK and FK to match 2023-08-14 11:14:43 +12:00
Rob Bygrave 2f233db465 #3133 - For IdClass match on property name 2023-08-10 23:22:57 +12:00
Rob Bygrave 1ca73ca6aa Test only - Update test IdClass CEPProductCategoryId to match expectations 2023-08-10 23:17:47 +12:00
Rob BygraveandGitHub d5e4c13fa8 Merge pull request #3154 from ebean-orm/feature/3134-idClass-delete
#3134 - String cannot be cast to class io.ebean.bean.EntityBean" when deleting entities that use cascade = [CascadeType.ALL] with composite keys
2023-08-10 21:15:09 +12:00
Rob Bygrave b5ac31838c #3134 Fix for - String cannot be cast to class io.ebean.bean.EntityBean" when deleting entities that use cascade = [CascadeType.ALL] with composite keys 2023-08-10 21:11:58 +12:00
Rob Bygrave e3e9412eea #3134 Failing test - String cannot be cast to class io.ebean.bean.EntityBean" when deleting entities that use cascade = [CascadeType.ALL] with composite keys 2023-08-10 20:50:40 +12:00
Roland PramlandRoland Praml d3427876b7 FIX: Recursive batch flush can save dependant beans in wrong order 2023-08-08 16:59:46 +02:00
Roland Praml 4b2976e31b Rethrow error in commit path of callbacks 2023-08-08 16:35:25 +02:00
Roland Praml 5720ee65b3 Ignored Postgres Platform 2023-08-08 14:02:49 +02:00
Roland Praml 7f441ce72e FIX: M2M Where Formula joins 2023-08-08 14:02:34 +02:00
Rob BygraveandGitHub 83e87b0c67 Merge pull request #3140 from ebean-orm/feature/bump-datasource
Bump ebean-datasource to 8.6, increases validation timeout default to 30 secs
2023-07-23 14:35:57 +12:00
Rob Bygrave 578feac87f Bump ebean-datasource to 8.6, increases validation timeout default to 30 secs 2023-07-21 17:02:08 +12:00
Rob BygraveandGitHub 42a64e715d Merge pull request #3138 from ebean-orm/feature/postgres-queryplan-capture-buffers
[Postgres] Include buffers in Postgres explain - explain (analyze, buffers)
2023-07-18 14:35:24 +12:00
robin.bygrave 12a3a180f4 [Postgres] Include buffers in Postgres explain - explain (analyze, buffers) 2023-07-18 13:52:21 +12:00
Rob BygraveandGitHub 8acd5bcb39 Merge pull request #3131 from ebean-orm/feature/h2-nonKeyWords
Modify ebean-test H2Database setup to default KEY,VALUE as non-keywords
2023-07-11 12:49:15 +12:00
Rob BygraveandGitHub eb71756c65 Merge pull request #3136 from ebean-orm/dependabot/maven/com.h2database-h2-2.2.220
Bump h2 from 2.1.214 to 2.2.220
2023-07-11 12:48:04 +12:00
dependabot[bot]andGitHub 8c8cadd471 Bump h2 from 2.1.214 to 2.2.220
Bumps [h2](https://github.com/h2database/h2database) from 2.1.214 to 2.2.220.
- [Release notes](https://github.com/h2database/h2database/releases)
- [Commits](https://github.com/h2database/h2database/compare/version-2.1.214...version-2.2.220)

---
updated-dependencies:
- dependency-name: com.h2database:h2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-07 21:57:51 +00:00
Rob BygraveandGitHub 222c49c59b Merge pull request #3130 from ebean-orm/feature/3123-root-error-suppressed
#3123 Root cause error can be suppressed on initialisation
2023-07-05 23:47:11 +12:00
Rob Bygrave 1e860112e6 Modify ebean-test H2Database setup to default KEY,VALUE as non-keywords
H2Database 2.x changed KEY and VALUE to be deemed keywords for SQL compliance. This is actually a bit of a pain as all the other databases we test allow KEY and VALUE as column names.

This changes the setup for H2Database to treat KEY and VALUE as non keywords as before and be more consistent with the other databases.
2023-07-05 23:46:06 +12:00
Rob Bygrave ffc21daaba Bump to next snapshot 2023-07-05 23:28:49 +12:00
Rob Bygrave f9adcb2c5a #3123 Root cause error can be suppressed on initialisation 2023-07-05 23:16:00 +12:00
Rob Bygrave d051bba71f Version 13.20.1 2023-07-04 12:05:30 +12:00
Rob Bygrave f414f04033 Add note on lack of support for InTuples with SQLServer and DB2 2023-07-04 12:05:10 +12:00
Rob Bygrave ac466ecd79 #3125 Add query.usingMaster() for query beans 2023-07-03 23:30:02 +12:00
Rob Bygrave 262a7f9bab Bump ebean-agent version 2023-07-03 22:57:55 +12:00
Rob BygraveandGitHub bb02d74005 Merge pull request #3125 from ebean-orm/feature/query-usingMaster
Add query.usingMaster() to explicitly choose master data source
2023-07-03 22:48:47 +12:00
Rob BygraveandGitHub e9b2b8d266 Merge pull request #3017 from ebean-orm/feature/db2luw9
Initial DB2Luw9Platform using row_number() for row limiting sql
2023-07-03 22:43:55 +12:00
Rob Bygrave fc16bde227 Modify ImplicitReadOnlyTransaction, call deactivate in finally 2023-07-03 22:43:13 +12:00
Rob Bygrave d1775dfcc4 #3017 Change DB2LegacyPlatform to also use the row_number limiters 2023-07-03 22:35:46 +12:00
3d5937f6da Update platforms/db2/src/main/java/io/ebean/platform/db2/DB2RowNumberBasicLimiter.java
Bump StringBuilder to use 120

Co-authored-by: Roland Praml <roland.praml@foconis.de>
2023-07-03 22:31:01 +12:00
Rob BygraveandGitHub adca8cd8fa Merge branch 'master' into feature/db2luw9 2023-07-03 22:28:43 +12:00
Rob BygraveandGitHub 6d30e6310b Merge pull request #3127 from ebean-orm/feature/3126
#3126 - Incorrect complex indexes generation
2023-07-03 22:22:32 +12:00
Rob Bygrave 79ed0a5f8e #3126 - Incorrect complex indexes generation 2023-07-03 22:19:38 +12:00
Rob Bygrave 94b3a020d5 Modify ImplicitReadOnlyTransaction useCommit to check connection.getAutoCommit() 2023-07-03 21:40:21 +12:00
Rob Bygrave 3e241907e6 Modify internal ImplicitReadOnlyTransaction to also support calling commit()
Originally, ImplicitReadOnlyTransaction was built to support read-only DataSource where the connections are using autoCommit true. This change modified ImplicitReadOnlyTransaction to also support use with the main DataSource where the connection are not using autoCommit true.

This is also now needed for the query.usingMaster() case where we have an implicit read only transaction but use the main DataSource with underlying connection requiring explicit commit() at the end of the transaction.
2023-07-03 21:27:58 +12:00
Rob Bygrave c4af1d9b23 Add query.usingMaster() to explicitly choose master data source
When we have a read replica with potential for replication lag, and config a read only DataSource to use that read replica, by default a query executed without a current transaction will use the read replica.

We specify usingMaster() when we explicitly want to execute a query and use the master data source.
2023-07-03 16:41:22 +12:00
Rob BygraveandGitHub 7702169762 Merge pull request #3124 from ebean-orm/feature/InTuples
Add InTuples expression for multi-column IN expression
2023-07-03 12:48:21 +12:00
Rob Bygrave 8209d9e229 Add SpiInTuples to hide more details from InTuples 2023-07-03 12:41:31 +12:00
Rob Bygrave 664120f369 Add InTuples expression for multi-column IN expression 2023-07-03 11:54:38 +12:00
Rob Bygrave e952b555e0 Add InTuples expression for multi-column IN expression 2023-07-03 11:49:53 +12:00
Rob BygraveandGitHub d99c50da75 Merge pull request #3122 from ebean-orm/feature/3109-jta-txn-fix
#3109 Fix for JtaTransactionManager when no active transaction
2023-06-26 21:30:55 +12:00
Rob Bygrave d4bec166f7 #3109 Fix for JtaTransactionManager when no active transaction
The call to registry() can throw an exception so
pulling that inside the try catch block and handle
that as no active transaction returning null.
2023-06-26 21:30:01 +12:00
Rob Bygrave 63a23904ac Update for DB2Luw9 with rn column as last column 2023-04-13 21:52:09 +12:00
Rob Bygrave 1807fc201e Initial DB2Luw9Platform using row_number() for row limiting sql 2023-03-30 22:56:55 +13:00
270 changed files with 2884 additions and 703 deletions
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-clickhouse</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-cockroach</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-db2</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-h2</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-hana</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-mariadb</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-mysql</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-nuodb</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-oracle</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-postgres</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-sqlite</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-sqlserver</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean-yugabyte</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -41,13 +41,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>composites</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean (all platforms)</name>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -40,7 +40,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -59,13 +59,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<artifactId>composites</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean api</name>
@@ -0,0 +1,49 @@
package io.ebean;
import io.ebean.service.SpiInTuples;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Default implementation of SpiInTuples.
*/
final class DInTuples implements SpiInTuples {
private final String[] properties;
private final int propertyCount;
private final List<Object[]> entries = new ArrayList<>();
DInTuples(String[] properties) {
this.properties = properties;
this.propertyCount = properties.length;
}
@Override
public InTuples add(Object... values) {
if (values.length != propertyCount) {
throw new IllegalArgumentException("Require " + propertyCount + " values but got " + values.length);
}
entries.add(values);
return this;
}
/**
* Return the first property name.
*/
@Override
public String[] properties() {
return properties;
}
/**
* Return all the value pairs.
*/
@Override
public List<Object[]> entries() {
return Collections.unmodifiableList(entries);
}
}
@@ -38,12 +38,15 @@ final class DbContext {
}
}
} catch (BeanNotEnhancedException e) {
String msg = "Bean is not enhanced? See https://ebean.io/docs/trouble-shooting#not-enhanced";
log.log(ERROR, msg, e);
throw e;
} catch (DataSourceConfigurationException e) {
String msg = "Configuration error creating DataSource for the default Database." +
" This typically means a missing application-test.yaml or missing ebean-test dependency." +
" See https://ebean.io/docs/trouble-shooting#datasource";
log.log(ERROR, msg, e);
throw new DataSourceConfigurationException(msg, e);
} catch (Throwable e) {
@@ -205,4 +205,16 @@ public interface DtoQuery<T> extends CancelableQuery {
* Use the explicit transaction to execute the query.
*/
DtoQuery<T> usingTransaction(Transaction transaction);
/**
* Ensure that the master DataSource is used if there is a read only data source
* being used (that is using a read replica database potentially with replication lag).
* <p>
* When the database is configured with a read-only DataSource via
* say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then
* by default when a query is run without an active transaction, it uses the read-only data
* source. We we use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
DtoQuery<T> usingMaster();
}
@@ -369,6 +369,11 @@ public interface ExpressionFactory {
*/
Expression inPairs(Pairs pairs);
/**
* In expression using multiple columns.
*/
Expression inTuples(InTuples pairs);
/**
* In - property has a value in the array of values.
*/
@@ -1131,6 +1131,11 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> inPairs(Pairs pairs);
/**
* In expression using multiple columns.
*/
ExpressionList<T> inTuples(InTuples pairs);
/**
* EXISTS a raw SQL SubQuery.
*
@@ -0,0 +1,36 @@
package io.ebean;
/**
* IN expression using multiple columns.
* <p>
* Currently this is not supported with SQLServer or DB2.
* <p>
* Produces SQL expression in the form of (A,B,C) IN ((a0,b0,c0), (a1,b1,c1), ... )
* where A,B,C are the properties in the tuples.
*/
public interface InTuples {
/**
* Create given the properties in the tuples.
*/
static InTuples of(String... properties) {
return new DInTuples(properties);
}
/**
* Create given the properties in the tuples.
*/
static InTuples of(Query.Property<?>... properties) {
String[] props = new String[properties.length];
for (int i = 0; i < properties.length; i++) {
props[i] = properties[i].toString();
}
return new DInTuples(props);
}
/**
* Add a tuple entry. All values must be non-null.
*/
InTuples add(Object... values);
}
@@ -0,0 +1,39 @@
package io.ebean;
import java.util.ArrayList;
import java.util.List;
/**
* Helper methods for Lists.
*/
public final class Lists {
private Lists() {
}
/**
* Partition the source List into sub-lists with a maximum size.
* <p>
* The sub-lists will all be the max size except for the last sub-list
* which can potentially be smaller.
*
* @param source The source list
* @param max The max size of each partition
* @param <T> The list element type
* @return List of sub-list partitions
*/
public static <T> List<List<T>> partition(List<T> source, int max) {
final int totalCount = source.size();
if (totalCount <= max) {
return List.of(source);
}
final int numOfPartitions = (totalCount + max - 1) / max; // round up
final var dest = new ArrayList<List<T>>(numOfPartitions);
for (int i = 0; i < numOfPartitions; i++) {
final int from = i * max;
final int to = Math.min(from + max, totalCount);
dest.add(source.subList(from, to));
}
return dest;
}
}
@@ -4,12 +4,14 @@ import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import javax.persistence.NonUniqueResultException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -670,6 +672,16 @@ public interface Query<T> extends CancelableQuery {
*/
Query<T> apply(FetchPath fetchPath);
/**
* Apply changes to the query conditional on the supplied predicate.
* <p>
* Typically, the changes are extra predicates etc.
*
* @param predicate The predicate which when true the changes are applied
* @param apply The changes to apply to the query
*/
Query<T> alsoIf(BooleanSupplier predicate, Consumer<Query<T>> apply);
/**
* Execute the query using the given transaction.
*/
@@ -685,6 +697,18 @@ public interface Query<T> extends CancelableQuery {
*/
Query<T> usingDatabase(Database database);
/**
* Ensure that the master DataSource is used if there is a read only data source
* being used (that is using a read replica database potentially with replication lag).
* <p>
* When the database is configured with a read-only DataSource via
* say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then
* by default when a query is run without an active transaction, it uses the read-only data
* source. We we use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
Query<T> usingMaster();
/**
* Execute the query returning the list of Id's.
* <p>
@@ -45,6 +45,18 @@ public interface SqlQuery extends Serializable, CancelableQuery {
*/
SqlQuery usingTransaction(Transaction transaction);
/**
* Ensure that the master DataSource is used if there is a read only data source
* being used (that is using a read replica database potentially with replication lag).
* <p>
* When the database is configured with a read-only DataSource via
* say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then
* by default when a query is run without an active transaction, it uses the read-only data
* source. We we use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
SqlQuery usingMaster();
/**
* Execute the query returning a list.
*/
@@ -135,7 +135,7 @@ public class DbConstraintNaming {
*/
protected String joinColumnNames(String[] columns) {
if (columns.length == 1) {
return normaliseColumn(columns[0]);
return normaliseColumn(columns[0]).replace(" ", "_");
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < columns.length; i++) {
@@ -0,0 +1,22 @@
package io.ebean.service;
import io.ebean.InTuples;
import java.util.List;
/**
* Internal extension to InTuples.
*/
public interface SpiInTuples extends InTuples {
/**
* Return the properties of the tuples.
*/
String[] properties();
/**
* Return all the tuple entries.
*/
List<Object[]> entries();
}
@@ -0,0 +1,83 @@
package io.ebean;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class ListsTest {
@Test
void partition_empty() {
List<List<Integer>> partitions = Lists.partition(List.of(), 5);
assertThat(partitions).hasSize(1);
assertThat(partitions.get(0)).hasSize(0);
}
@Test
void partition_lt() {
List<List<Integer>> partitions = Lists.partition(List.of(1, 2, 3, 4), 5);
assertThat(partitions).hasSize(1);
assertThat(partitions.get(0)).hasSize(4);
}
@Test
void partition_eq() {
List<List<Integer>> partitions = Lists.partition(List.of(1, 2, 3, 4, 5), 5);
assertThat(partitions).hasSize(1);
assertThat(partitions.get(0)).hasSize(5);
}
@Test
void partition_gt() {
List<List<Integer>> partitions = Lists.partition(List.of(1, 2, 3, 4, 5, 6), 5);
assertThat(partitions).hasSize(2);
assertThat(partitions.get(0)).hasSize(5);
assertThat(partitions.get(1)).hasSize(1);
assertThat(partitions.get(0)).containsExactly(1, 2, 3, 4, 5);
assertThat(partitions.get(1)).containsExactly(6);
}
@Test
void partition_gt1_letters() {
var partitions = Lists.partition(List.of("a", "b", "c", "d"), 3);
assertThat(partitions).hasSize(2);
assertThat(partitions.get(0)).hasSize(3);
assertThat(partitions.get(1)).hasSize(1);
assertThat(partitions.get(0)).containsExactly("a", "b", "c");
assertThat(partitions.get(1)).containsExactly("d");
}
@Test
void partition_gt1a_letters() {
var partitions = Lists.partition(List.of("a", "b", "c", "d", "e"), 3);
assertThat(partitions).hasSize(2);
assertThat(partitions.get(0)).hasSize(3);
assertThat(partitions.get(1)).hasSize(2);
assertThat(partitions.get(0)).containsExactly("a", "b", "c");
assertThat(partitions.get(1)).containsExactly("d", "e");
}
@Test
void partition_eq2_letters() {
var partitions = Lists.partition(List.of("a", "b", "c", "d", "e", "f"), 3);
assertThat(partitions).hasSize(2);
assertThat(partitions.get(0)).hasSize(3);
assertThat(partitions.get(1)).hasSize(3);
assertThat(partitions.get(0)).containsExactly("a", "b", "c");
assertThat(partitions.get(1)).containsExactly("d", "e", "f");
}
@Test
void partition_gt2_letters() {
var partitions = Lists.partition(List.of("a", "b", "c", "d", "e", "f", "g"), 3);
assertThat(partitions).hasSize(3);
assertThat(partitions.get(0)).hasSize(3);
assertThat(partitions.get(1)).hasSize(3);
assertThat(partitions.get(2)).hasSize(1);
assertThat(partitions.get(0)).containsExactly("a", "b", "c");
assertThat(partitions.get(1)).containsExactly("d", "e", "f");
assertThat(partitions.get(2)).containsExactly("g");
}
}
+25 -25
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -155,46 +155,46 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<!-- platforms -->
@@ -202,67 +202,67 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
+6 -6
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,7 +22,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -46,7 +46,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
</dependency>
<dependency>
@@ -159,21 +159,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>13.20.1-SNAPSHOT</version>
<version>13.21.0</version>
<scope>test</scope>
</dependency>
@@ -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).
@@ -135,8 +135,9 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectio
* Create a ServerTransaction for query purposes.
*
* @param tenantId For multi-tenant lazy loading provide the tenantId to use.
* @param useMaster Set to true when the query should use the master data source.
*/
SpiTransaction createReadOnlyTransaction(Object tenantId);
SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster);
/**
* An event from another server in the cluster used to notify local
@@ -31,6 +31,11 @@ public interface SpiExpressionRequest {
*/
SpiOrmQueryRequest<?> queryRequest();
/**
* Return the underling buffer.
*/
StringBuilder buffer();
/**
* Append to the expression sql without any parsing.
*/
@@ -235,6 +235,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
String planLabel();
/**
* Return true if this query should not use the read only data source.
*/
boolean isUseMaster();
/**
* Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id.
*/
@@ -5,6 +5,11 @@ package io.ebeaninternal.api;
*/
public interface SpiSqlBinding extends SpiCancelableQuery {
/**
* Return true if this query should not use the read only data source.
*/
boolean isUseMaster();
/**
* Return the named or positioned parameters.
*/
@@ -48,7 +48,7 @@ public abstract class AbstractSqlQueryRequest implements CancelableQuery {
transaction = server.currentServerTransaction();
if (transaction == null || !transaction.isActive()) {
// create a local readOnly transaction
transaction = server.createReadOnlyTransaction(null);
transaction = server.createReadOnlyTransaction(null, query.isUseMaster());
createdTransaction = true;
}
}
@@ -87,7 +87,7 @@ final class DefaultBeanLoader {
query.setLoadDescription("+lazy", null);
}
query.select(parentDesc.idBinder().getIdProperty());
query.select(parentDesc.idBinder().idSelect());
if (onlyIds) {
query.fetch(many.name(), many.targetIdProperty());
} else {
@@ -137,7 +137,7 @@ final class DefaultBeanLoader {
private List<?> executeQuery(LoadRequest loadRequest, SpiQuery<?> query) {
if (onIterateUseExtraTxn && loadRequest.isParentFindIterate()) {
// MySql - we need a different transaction to execute the secondary query
SpiTransaction extraTxn = server.createReadOnlyTransaction(query.tenantId());
SpiTransaction extraTxn = server.createReadOnlyTransaction(query.tenantId(), query.isUseMaster());
try {
return server.findList(query, extraTxn);
} finally {
@@ -2123,8 +2123,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
@Override
public SpiTransaction createReadOnlyTransaction(Object tenantId) {
return transactionManager.createReadOnlyTransaction(tenantId);
public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) {
return transactionManager.createReadOnlyTransaction(tenantId, useMaster);
}
/**
@@ -56,7 +56,10 @@ final class MultiTenantDbCatalogSupplier implements DataSourceSupplier {
}
@Override
public Connection readOnlyConnection(Object tenantId) throws SQLException {
public Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException {
if (readOnly == null || useMaster) {
return catalogDataSource.getConnectionForTenant(tenantId);
}
return readOnly.getConnectionForTenant(tenantId);
}
@@ -103,7 +106,6 @@ final class MultiTenantDbCatalogSupplier implements DataSourceSupplier {
*/
@Override
public Connection getConnection() throws SQLException {
Connection connection = dataSource.getConnection();
connection.setCatalog(tenantCatalog());
return connection;
@@ -56,8 +56,12 @@ final class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
}
@Override
public Connection readOnlyConnection(Object tenantId) throws SQLException {
return readOnly.getConnectionForTenant(tenantId);
public Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException {
if (readOnly == null || useMaster) {
return schemaDataSource.getConnectionForTenant(tenantId);
} else {
return readOnly.getConnectionForTenant(tenantId);
}
}
@Override
@@ -106,7 +110,6 @@ final class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
*/
@Override
public Connection getConnection() throws SQLException {
Connection connection = dataSource.getConnection();
connection.setSchema(tenantSchema());
return connection;
@@ -43,8 +43,8 @@ final class MultiTenantDbSupplier implements DataSourceSupplier {
}
@Override
public Connection readOnlyConnection(Object tenantId) throws SQLException {
throw new SQLException("Not currently supported");
public Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException {
return dataSourceProvider.dataSource(tenantId).getConnection();
}
@Override
@@ -220,7 +220,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
} else {
// create an implicit transaction to execute this query
// potentially using read-only DataSource with autoCommit
transaction = server.createReadOnlyTransaction(query.tenantId());
transaction = server.createReadOnlyTransaction(query.tenantId(), query.isUseMaster());
}
createdTransaction = true;
}
@@ -44,7 +44,7 @@ public final class PersistDeferredRelationship {
return; // could not bind: TODO: should we log/throw an error?
}
// bind the where clause for the bean
Object[] idValues = beanDescriptor.idBinder().getIdValues(bean);
Object[] idValues = beanDescriptor.idBinder().values(bean);
for (int j = 0; j < idValues.length; j++) {
sqlUpdate.setParameter(pos + j, idValues[j]);
}
@@ -41,8 +41,12 @@ final class SimpleDataSourceProvider implements DataSourceSupplier {
}
@Override
public Connection readOnlyConnection(Object tenantId) throws SQLException {
return readOnlyDataSource.getConnection();
public Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException {
if (readOnlyDataSource == null || useMaster) {
return dataSource.getConnection();
} else {
return readOnlyDataSource.getConnection();
}
}
@Override
@@ -212,7 +212,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private final EntityBean prototypeEntityBean;
private final IdBinder idBinder;
private final String idSelect;
private String idBinderInLHSSql;
private String idBinderIdSql;
private String deleteByIdSql;
@@ -355,23 +354,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
propertiesIndex[i] = propMap.get(ebi.property(i));
}
}
idSelect = initIdSelect();
}
String initIdSelect() {
if (idProperty != null && !idProperty.name().equals("_idClass")) {
return idProperty.name();
} else if (entityType == EntityType.EMBEDDED) {
return null;
} else {
StringJoiner sj = new StringJoiner(",");
for (BeanProperty prop : propertiesNonMany) {
if (prop.isImportedPrimaryKey()) {
sj.add(prop.name());
}
}
return sj.toString().intern();
}
public String idSelect() {
if (idBinder == null) throw new UnsupportedOperationException();
return idBinder.idSelect();
}
public boolean isJacksonCorePresent() {
@@ -543,10 +530,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
unidirectional.initialise(initContext);
}
idBinder.initialise();
idBinderInLHSSql = idBinder.getBindIdInSql(baseTableAlias);
idBinderIdSql = idBinder.getBindIdSql(baseTableAlias);
String idBinderInLHSSqlNoAlias = idBinder.getBindIdInSql(null);
String idEqualsSql = idBinder.getBindIdSql(null);
idBinderInLHSSql = idBinder.bindInSql(baseTableAlias);
idBinderIdSql = idBinder.bindEqSql(baseTableAlias);
String idBinderInLHSSqlNoAlias = idBinder.bindInSql(null);
String idEqualsSql = idBinder.bindEqSql(null);
deleteByIdSql = "delete from " + baseTable + " where " + idEqualsSql;
whereIdInSql = " where " + idBinderInLHSSqlNoAlias + " ";
deleteByIdInSql = "delete from " + baseTable + whereIdInSql;
@@ -868,10 +855,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private SqlUpdate deleteByIdList(List<Object> idList, DeleteMode mode) {
String baseSql = mode.isHard() ? deleteByIdInSql : softDeleteByIdInSql;
StringBuilder sb = new StringBuilder(baseSql);
String inClause = idBinder.getIdInValueExprDelete(idList.size());
String inClause = idBinder.idInValueExprDelete(idList.size());
sb.append(inClause);
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
idBinder.addIdInBindValues(delete, idList);
idBinder.addBindValues(delete, idList);
return delete;
}
@@ -882,7 +869,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private SqlUpdate deleteById(Object id, DeleteMode mode) {
String baseSql = mode.isHard() ? deleteByIdSql : softDeleteByIdSql;
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(baseSql);
Object[] bindValues = idBinder.getBindValues(id);
Object[] bindValues = idBinder.bindValues(id);
for (Object bindValue : bindValues) {
sqlDelete.setParameter(bindValue);
}
@@ -1516,7 +1503,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
public void queryPlan(CQueryPlanKey key, CQueryPlan plan) {
queryPlanCache.put(key, plan);
if (key.useCache()) {
queryPlanCache.put(key, plan);
}
}
/**
@@ -1537,7 +1526,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* Return a Sql update statement to set the importedId value (deferred execution).
*/
public String updateImportedIdSql(ImportedId prop) {
return "update " + baseTable + " set " + prop.importedIdClause() + " where " + idBinder.getBindIdSql(null);
return "update " + baseTable + " set " + prop.importedIdClause() + " where " + idBinder.bindEqSql(null);
}
/**
@@ -1631,7 +1620,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* Return a raw expression for 'where parent id in ...' clause.
*/
String parentIdInExpr(int parentIdSize, String rawWhere) {
String inClause = idBinder.getIdInValueExpr(false, parentIdSize);
String inClause = idBinder.idInValueExpr(false, parentIdSize);
return idBinder.isIdInExpandedForm() ? inClause : rawWhere + inClause;
}
@@ -1684,7 +1673,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
if (alias == null) {
return idBinderIdSql;
} else {
return idBinder.getBindIdSql(alias);
return idBinder.bindEqSql(alias);
}
}
@@ -1710,7 +1699,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* This 'flattens' any EmbeddedId or multiple Id property cases.
*/
public Object[] bindIdValues(Object idValue) {
return idBinder.getBindValues(idValue);
return idBinder.bindValues(idValue);
}
@Override
@@ -2118,7 +2107,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* The usage is to provide simple id types for JSON processing (for embeddedId's).
*/
public Object idForJson(Object bean) {
return idBinder.getIdForJson((EntityBean) bean);
return idBinder.convertForJson((EntityBean) bean);
}
/**
@@ -2127,7 +2116,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* The usage is to provide simple id types for JSON processing (for embeddedId's).
*/
Object convertIdFromJson(Object idValue) {
return idBinder.convertIdFromJson(idValue);
return idBinder.convertFromJson(idValue);
}
/**
@@ -2135,7 +2124,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* included in the query.
*/
public String defaultOrderBy() {
return idBinder.getDefaultOrderBy();
return idBinder.orderBy();
}
/**
@@ -2450,9 +2439,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
BeanProperty _findBeanProperty(String propName) {
BeanProperty prop = propMap.get(propName);
if (prop == null && inheritInfo != null) {
// search in sub types...
return inheritInfo.findSubTypeProperty(propName);
if (prop == null) {
if ("_$IdClass$".equals(propName)) {
return idProperty;
} else if (inheritInfo != null) {
// search in sub types...
return inheritInfo.findSubTypeProperty(propName);
}
}
return prop;
}
@@ -3057,10 +3050,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return idProperty;
}
public String idSelect() {
return idSelect;
}
/**
* Return true if this bean should be inserted rather than updated.
*
@@ -23,11 +23,6 @@ abstract class BeanDescriptorElement<T> extends BeanDescriptor<T> {
this.elementHelp = elementHelp;
}
@Override
String initIdSelect() {
return null;
}
private String shortName(String name) {
int pos = name.lastIndexOf('.');
if (pos > 1) {
@@ -518,7 +518,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
IdBinder idBinder = d.idBinder();
if (idBinder instanceof IdBinderEmbedded) {
IdBinderEmbedded embId = (IdBinderEmbedded) idBinder;
BeanDescriptor<?> idBeanDescriptor = embId.getIdBeanDescriptor();
BeanDescriptor<?> idBeanDescriptor = embId.descriptor();
Class<?> idType = idBeanDescriptor.type();
try {
idType.getDeclaredMethod("hashCode");
@@ -456,7 +456,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType, String manyWhere) {
if (formula && sqlFormulaJoin != null) {
ctx.appendFormulaJoin(sqlFormulaJoin, joinType, manyWhere);
String alias = ctx.tableAliasManyWhere(manyWhere);
ctx.appendFormulaJoin(sqlFormulaJoin, joinType, alias);
} else if (secondaryTableJoin != null) {
String relativePrefix = ctx.relativePrefix(secondaryTableJoinPrefix);
secondaryTableJoin.addJoin(joinType, relativePrefix, ctx);
@@ -122,7 +122,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
targetInheritInfo = targetDescriptor.inheritInfo();
saveRecurseSkippable = targetDescriptor.isSaveRecurseSkippable();
if (!targetIdBinder.isComplexId()) {
targetIdProperty = targetIdBinder.getIdProperty();
targetIdProperty = targetIdBinder.idSelect();
}
}
}
@@ -458,7 +458,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
*/
@Override
public Object[] assocIdValues(EntityBean bean) {
return targetDescriptor.idBinder().getIdValues(bean);
return targetDescriptor.idBinder().values(bean);
}
/**
@@ -466,7 +466,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
*/
@Override
public String assocIdExpression(String prefix, String operator) {
return targetDescriptor.idBinder().getAssocOneIdExpr(prefix, operator);
return targetDescriptor.idBinder().assocExpr(prefix, operator);
}
/**
@@ -474,7 +474,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
*/
@Override
public String assocIdInValueExpr(boolean not, int size) {
return targetDescriptor.idBinder().getIdInValueExpr(not, size);
return targetDescriptor.idBinder().idInValueExpr(not, size);
}
/**
@@ -482,7 +482,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
*/
@Override
public String assocIdInExpr(String prefix) {
return targetDescriptor.idBinder().getAssocIdInExpr(prefix);
return targetDescriptor.idBinder().assocInExpr(prefix);
}
@Override
@@ -21,13 +21,15 @@ class BeanPropertyAssocManySqlHelp<T> {
private final String deleteByParentIdSql;
private final String deleteByParentIdInSql;
private final String elementCollectionInsertSql;
private final boolean idInExpandedForm;
BeanPropertyAssocManySqlHelp(BeanPropertyAssocMany<T> many, ExportedProperty[] exportedProperties) {
this.many = many;
this.exportedProperties = exportedProperties;
this.hasJoinTable = many.hasJoinTable();
this.descriptor = many.descriptor();
this.exportedPropertyBindProto = deriveExportedPropertyBindProto();
this.exportedPropertyBindProto = initExportedBindProto();
this.idInExpandedForm = descriptor.idBinder().isIdInExpandedForm();
String delStmt;
if (hasJoinTable) {
@@ -35,8 +37,8 @@ class BeanPropertyAssocManySqlHelp<T> {
} else {
delStmt = "delete from " + many.targetTable() + " where ";
}
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false, "");
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true, "");
deleteByParentIdSql = delStmt + rawParentIdEQ("");
deleteByParentIdInSql = delStmt;
if (many.isElementCollection()) {
elementCollectionInsertSql = elementCollectionInsert();
} else {
@@ -45,7 +47,7 @@ class BeanPropertyAssocManySqlHelp<T> {
}
private String elementCollectionInsert() {
StringBuilder sb = new StringBuilder(200);
final StringBuilder sb = new StringBuilder(200);
sb.append("insert into ").append(many.targetTable()).append(" (");
append(sb);
@@ -54,7 +56,7 @@ class BeanPropertyAssocManySqlHelp<T> {
sb.append(") values (");
appendBind(sb, exportedProperties.length, true);
appendBind(sb, cols.colCount, false);
sb.append(")");
sb.append(')');
return sb.toString();
}
@@ -73,7 +75,7 @@ class BeanPropertyAssocManySqlHelp<T> {
@Override
public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded) {
sb.append(",").append(p.dbColumn());
sb.append(',').append(p.dbColumn());
colCount++;
}
@@ -96,14 +98,14 @@ class BeanPropertyAssocManySqlHelp<T> {
String lazyFetchOrderBy(String fetchOrderBy) {
// derive lazyFetchOrderBy
StringBuilder sb = new StringBuilder(50);
final String fkTableAlias = hasJoinTable ? "int_" : "t0";
final var sb = new StringBuilder(50);
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(", ");
}
// these fk columns are either on the intersection (int_) or base table (t0)
String fkTableAlias = hasJoinTable ? "int_" : "t0";
sb.append(fkTableAlias).append(".").append(exportedProperties[i].getForeignDbColumn());
sb.append(fkTableAlias).append('.').append(exportedProperties[i].getForeignDbColumn());
}
sb.append(", ").append(fetchOrderBy);
return sb.toString().trim();
@@ -113,56 +115,50 @@ class BeanPropertyAssocManySqlHelp<T> {
* Add a where clause to the query for a given list of parent Id's.
*/
void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
String tableAlias = hasJoinTable ? "int_." : "t0.";
final String tableAlias = hasJoinTable ? "int_." : "t0.";
if (hasJoinTable) {
query.setM2MIncludeJoin(many.inverseJoin);
}
String rawWhere = deriveWhereParentIdSql(true, tableAlias);
String expr = descriptor.parentIdInExpr(parentIds.size(), rawWhere);
many.bindParentIdsIn(expr, parentIds, query);
final String rawWhere = rawParentIdIN(tableAlias, parentIds.size());
many.bindParentIdsIn(rawWhere, parentIds, query);
}
List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds, boolean hard) {
String rawWhere = deriveWhereParentIdSql(false, "");
SpiEbeanServer server = descriptor.ebeanServer();
SpiQuery<?> q = many.newQuery(server);
many.bindParentIdEq(rawWhere, parentId, q);
final SpiEbeanServer server = descriptor.ebeanServer();
final SpiQuery<?> query = many.newQuery(server);
many.bindParentIdEq(rawParentIdEQ(""), parentId, query);
if (hard) {
q.setIncludeSoftDeletes();
query.setIncludeSoftDeletes();
}
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
query.where().not(query.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
return server.findIds(query, t);
}
List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds, boolean hard) {
String rawWhere = deriveWhereParentIdSql(true, "");
String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto);
String expr = rawWhere + inClause;
SpiEbeanServer server = descriptor.ebeanServer();
SpiQuery<?> q = many.newQuery(server);
//Query<?> q = server.find(propertyType);
many.bindParentIdsIn(expr, parentIds, q);
final SpiEbeanServer server = descriptor.ebeanServer();
final SpiQuery<?> query = many.newQuery(server);
many.bindParentIdsIn(rawParentIdIN("", parentIds.size()), parentIds, query);
if (hard) {
q.setIncludeSoftDeletes();
query.setIncludeSoftDeletes();
}
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
query.where().not(query.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
return server.findIds(query, t);
}
SpiSqlUpdate deleteByParentId(Object parentId) {
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql);
final var sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql);
many.bindParentId(sqlDelete, parentId);
return sqlDelete;
}
SpiSqlUpdate deleteByParentIdList(List<Object> parentIds) {
String sql = Str.add(deleteByParentIdInSql, buildInClauseBinding(parentIds.size(), exportedPropertyBindProto));
DefaultSqlUpdate delete = new DefaultSqlUpdate(sql);
final String rawWhere = rawParentIdIN("", parentIds.size());
final String sql = Str.add(deleteByParentIdInSql, rawWhere);
final var delete = new DefaultSqlUpdate(sql);
many.bindParentIds(delete, parentIds);
return delete;
}
@@ -172,71 +168,107 @@ class BeanPropertyAssocManySqlHelp<T> {
if (!skipComma || i > 0) {
sb.append(",");
}
sb.append("?");
sb.append('?');
}
}
private void append(StringBuilder sb) {
for (int i = 0; i < exportedProperties.length; i++) {
String fkColumn = exportedProperties[i].getForeignDbColumn();
if (i > 0) {
sb.append(",");
sb.append(',');
}
sb.append(fkColumn);
sb.append(exportedProperties[i].getForeignDbColumn());
}
}
private String deriveWhereParentIdSql(boolean inClause, String tableAlias) {
StringBuilder sb = new StringBuilder();
if (inClause) {
sb.append("(");
private String rawParentIdIN(String tableAlias, int size) {
if (idInExpandedForm) {
return rawParentIdExpanded(tableAlias, size);
} else {
return rawParentIdStandard(tableAlias, size);
}
}
private String rawParentIdStandard(String tableAlias, int size) {
if (descriptor.isSimpleId()) {
return rawParentIdMultiBinder(tableAlias, size);
}
final var sb = new StringBuilder(100 + size * exportedPropertyBindProto.length());
sb.append('(');
for (int i = 0; i < exportedProperties.length; i++) {
String fkColumn = exportedProperties[i].getForeignDbColumn();
if (i > 0) {
sb.append(inClause ? "," : " and ");
sb.append(',');
}
sb.append(tableAlias).append(fkColumn);
if (!inClause) {
sb.append("=? ");
}
}
if (inClause) {
sb.append(")");
}
return sb.toString();
}
private String buildInClauseBinding(int size, String bindProto) {
if (descriptor.isSimpleId()) {
return descriptor.idBinder().getIdInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder(10 + (size * (bindProto.length() + 1)));
sb.append(" in");
sb.append(" (");
sb.append(") in (");
for (int i = 0; i < size; i++) {
if (i > 0) {
sb.append(",");
sb.append(',');
}
sb.append(bindProto);
sb.append(exportedPropertyBindProto);
}
sb.append(") ");
sb.append(')');
return sb.toString();
}
private String deriveExportedPropertyBindProto() {
/**
* Potentially a MultiValue binder like Postgres ANY Array binding.
*/
private String rawParentIdMultiBinder(String tableAlias, int size) {
final String property = '(' + tableAlias + exportedProperties[0].getForeignDbColumn() + ')';
final String inValueExpr = descriptor.idBinder().idInValueExpr(false, size);
return property + inValueExpr;
}
private String rawParentIdExpanded(String tableAlias, int size) {
final String proto = parentIdExpandedProto(tableAlias);
final var result = new StringBuilder(size * (proto.length() + 2) + 10).append('(');
for (int i = 0; i < size; i++) {
if (i > 0) {
result.append(" or ");
}
result.append(proto);
}
return result.append(')').toString();
}
private String parentIdExpandedProto(String tableAlias) {
final var sb = new StringBuilder(60).append('(');
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(" and ");
}
sb.append(tableAlias).append(exportedProperties[i].getForeignDbColumn()).append("=?");
}
return sb.append(')').toString();
}
private String rawParentIdEQ(String tableAlias) {
final var sb = new StringBuilder(80);
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(" and ");
}
sb.append(tableAlias).append(exportedProperties[i].getForeignDbColumn()).append("=?");
}
return sb.toString();
}
private String initExportedBindProto() {
if (exportedProperties.length == 1) {
return "?";
}
StringBuilder sb = new StringBuilder();
final var sb = new StringBuilder(exportedProperties.length * 2 + 2);
sb.append("(");
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(",");
sb.append(',');
}
sb.append("?");
sb.append('?');
}
sb.append(")");
sb.append(')');
return sb.toString();
}
@@ -246,7 +246,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
private SqlUpdate deleteByParentIdList(List<Object> parentIds) {
String sql = deleteByParentIdInSql + targetIdBinder.getIdInValueExpr(false, parentIds.size());
String sql = deleteByParentIdInSql + targetIdBinder.idInValueExpr(false, parentIds.size());
DefaultSqlUpdate delete = new DefaultSqlUpdate(sql);
bindParentIds(delete, parentIds);
return delete;
@@ -276,7 +276,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
private List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t) {
String rawWhere = deriveWhereParentIdSql(true);
String inClause = idBinder().getIdInValueExpr(false, parentIds.size());
String inClause = idBinder().idInValueExpr(false, parentIds.size());
String expr = rawWhere + inClause;
SpiEbeanServer server = server();
Query<?> q = server.find(type());
@@ -487,7 +487,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
*/
@Override
public Object[] assocIdValues(EntityBean bean) {
return targetDescriptor.idBinder().getIdValues(bean);
return targetDescriptor.idBinder().values(bean);
}
/**
@@ -495,7 +495,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
*/
@Override
public String assocIdExpression(String prefix, String operator) {
return targetDescriptor.idBinder().getAssocOneIdExpr(prefix, operator);
return targetDescriptor.idBinder().assocExpr(prefix, operator);
}
/**
@@ -503,7 +503,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
*/
@Override
public String assocIdInValueExpr(boolean not, int size) {
return targetDescriptor.idBinder().getIdInValueExpr(not, size);
return targetDescriptor.idBinder().idInValueExpr(not, size);
}
/**
@@ -511,7 +511,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
*/
@Override
public String assocIdInExpr(String prefix) {
return targetDescriptor.idBinder().getAssocIdInExpr(prefix);
return targetDescriptor.idBinder().assocInExpr(prefix);
}
@Override
@@ -613,12 +613,24 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
/**
* Add table join with explicit table alias.
*/
@Override
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
if (sqlFormulaJoin != null) {
ctx.appendFormulaJoin(sqlFormulaJoin, joinType, a1);
}
return super.addJoin(joinType, a1, a2, ctx);
}
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType, String manyWhere) {
if (!isTransient && !primaryKeyExport) {
localHelp.appendFrom(ctx, joinType);
if (sqlFormulaJoin != null) {
ctx.appendFormulaJoin(sqlFormulaJoin, joinType, manyWhere);
String alias = ctx.tableAliasManyWhere(manyWhere);
ctx.appendFormulaJoin(sqlFormulaJoin, joinType, alias);
}
}
}
@@ -73,7 +73,7 @@ public interface DbSqlContext {
* Append a Sql Formula join. This converts the "${ta}" keyword to the current
* table alias.
*/
void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType, String manyWhere);
void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType, String tableAlias);
/**
* Return the current content length.
@@ -26,6 +26,8 @@ public interface IdBinder {
*/
void initialise();
String idSelect();
/**
* Return true if this is a compound key and must use expanded and or form.
*/
@@ -41,19 +43,10 @@ public interface IdBinder {
*/
Object readData(DataInput dataInput) throws IOException;
/**
* Return the name(s) of the Id property(s). Comma delimited if there is more
* than one.
* <p>
* This can be used to include in a query.
* </p>
*/
String getIdProperty();
/**
* Return the Id BeanProperty.
*/
STreeProperty getBeanProperty();
STreeProperty beanProperty();
/**
* Find a BeanProperty that is mapped to the database column.
@@ -70,9 +63,14 @@ public interface IdBinder {
* Return the default order by that may need to be used if the query includes
* a many property.
*/
String getDefaultOrderBy();
String orderBy();
String getOrderBy(String pathPrefix, boolean ascending);
String orderBy(String pathPrefix, boolean ascending);
/**
* Return the id values for a given bean.
*/
Object[] values(EntityBean bean);
/**
* Return the values as an array of scalar bindable values.
@@ -84,8 +82,7 @@ public interface IdBinder {
* Added primarily for Query.addWhere().add(Expr.idEq()) support.
* </p>
*/
Object[] getBindValues(Object idValue);
Object[] bindValues(Object idValue);
/**
* For EmbeddedId convert the idValue into a simple map.
@@ -94,7 +91,7 @@ public interface IdBinder {
* This is used to provide a simple JSON serializable version of the id value.
* </p>
*/
Object getIdForJson(EntityBean idValue);
Object convertForJson(EntityBean idValue);
/**
* For EmbeddedId the value is assumed to be a Map and this is
@@ -106,12 +103,7 @@ public interface IdBinder {
* This is used to provide a simple JSON serializable version of the id value.
* </p>
*/
Object convertIdFromJson(Object value);
/**
* Return the id values for a given bean.
*/
Object[] getIdValues(EntityBean bean);
Object convertFromJson(Object value);
/**
* Build a string of the logical expressions.
@@ -119,12 +111,12 @@ public interface IdBinder {
* Typically used to build a id = ? string.
* </p>
*/
String getAssocOneIdExpr(String prefix, String operator);
String assocExpr(String prefix, String operator);
/**
* Return the logical id in expression taking into account embedded id's.
*/
String getAssocIdInExpr(String prefix);
String assocInExpr(String prefix);
/**
* Binds an id value to a prepared statement.
@@ -139,27 +131,27 @@ public interface IdBinder {
/**
* Binds multiple id value to an update.
*/
void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids);
void addBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids);
/**
* Binds multiple id value to a request.
*/
void addIdInBindValues(SpiExpressionRequest request, Collection<?> ids);
void addBindValues(SpiExpressionRequest request, Collection<?> ids);
/**
* Return the sql for binding the id using an IN clause.
*/
String getBindIdInSql(String baseTableAlias);
String bindInSql(String baseTableAlias);
/**
* Return the binding expression (like "?" or "(?,?)")for the Id.
*/
String getIdInValueExpr(boolean not, int size);
String idInValueExpr(boolean not, int size);
/**
* Same as getIdInValueExpr but for delete by id.
*/
String getIdInValueExprDelete(int size);
String idInValueExprDelete(int size);
void buildRawSqlSelectChain(String prefix, List<String> selectChain);
@@ -188,7 +180,7 @@ public interface IdBinder {
* Return the sql for binding the id to. This includes table alias and columns
* that make up the id.
*/
String getBindIdSql(String baseTableAlias);
String bindEqSql(String baseTableAlias);
/**
* Cast or convert the Id value if necessary and optionally set it.
@@ -35,7 +35,7 @@ public final class IdBinderEmbedded implements IdBinder {
public IdBinderEmbedded(boolean idInExpandedForm, BeanPropertyAssocOne<?> embIdProperty) {
this.idInExpandedForm = idInExpandedForm;
this.embIdProperty = embIdProperty;
this.idClass = "_idClass".equals(embIdProperty.name());
this.idClass = "_$IdClass$".equals(embIdProperty.name());
}
@Override
@@ -45,13 +45,18 @@ public final class IdBinderEmbedded implements IdBinder {
this.idInValueSql = idInExpandedForm ? idInExpanded() : idInCompressed();
}
@Override
public String idSelect() {
return embIdProperty.name();
}
@Override
public boolean isIdInExpandedForm() {
return idInExpandedForm;
}
private String idInExpanded() {
StringBuilder sb = new StringBuilder(30);
final StringBuilder sb = new StringBuilder(30);
sb.append("(");
for (int i = 0; i < props.length; i++) {
if (i > 0) {
@@ -64,7 +69,7 @@ public final class IdBinderEmbedded implements IdBinder {
}
private String idInCompressed() {
StringBuilder sb = new StringBuilder(20).append("(");
final StringBuilder sb = new StringBuilder(props.length * 2 + 2).append("(");
for (int i = 0; i < props.length; i++) {
if (i > 0) {
sb.append(",");
@@ -76,40 +81,14 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public BeanProperty getBeanProperty() {
public BeanProperty beanProperty() {
return embIdProperty;
}
@Override
public String getOrderBy(String pathPrefix, boolean ascending) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < props.length; i++) {
if (i > 0) {
sb.append(", ");
}
if (pathPrefix != null) {
sb.append(pathPrefix).append(".");
}
if (!idClass) {
sb.append(embIdProperty.name()).append(".");
}
sb.append(props[i].name());
if (!ascending) {
sb.append(" desc");
}
}
return sb.toString();
}
public BeanDescriptor<?> getIdBeanDescriptor() {
public BeanDescriptor<?> descriptor() {
return idDesc;
}
@Override
public String getIdProperty() {
return embIdProperty.name();
}
@Override
public void buildRawSqlSelectChain(String prefix, List<String> selectChain) {
if (!idClass) {
@@ -136,33 +115,44 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public String getDefaultOrderBy() {
StringBuilder sb = new StringBuilder();
public String orderBy() {
return orderBy(null, true);
}
@Override
public String orderBy(String pathPrefix, boolean ascending) {
final StringBuilder sb = new StringBuilder(100);
for (int i = 0; i < props.length; i++) {
if (i > 0) {
sb.append(", ");
}
if (pathPrefix != null) {
sb.append(pathPrefix).append(".");
}
if (!idClass) {
sb.append(embIdProperty.name()).append(".");
}
sb.append(props[i].name());
if (!ascending) {
sb.append(" desc");
}
}
return sb.toString();
}
public BeanProperty[] getProperties() {
public BeanProperty[] properties() {
return props;
}
@Override
public String getIdInValueExprDelete(int size) {
public String idInValueExprDelete(int size) {
if (size <= 0) {
throw new IndexOutOfBoundsException("The size must be at least 1");
}
if (!idInExpandedForm) {
return getIdInValueExpr(false, size);
return idInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder();
final StringBuilder sb = new StringBuilder(80);
sb.append("(");
for (int j = 0; j < size; j++) {
if (j > 0) {
@@ -182,11 +172,11 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public String getIdInValueExpr(boolean not, int size) {
public String idInValueExpr(boolean not, int size) {
if (size <= 0) {
throw new IndexOutOfBoundsException("The size must be at least 1");
}
StringBuilder sb = new StringBuilder();
final StringBuilder sb = new StringBuilder(80);
if (not) {
sb.append(" not");
}
@@ -209,20 +199,16 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public Object[] getIdValues(EntityBean bean) {
Object val = embIdProperty.getValue(bean);
Object[] bindValues = new Object[props.length];
for (int i = 0; i < props.length; i++) {
bindValues[i] = props[i].getValue((EntityBean) val);
}
return bindValues;
public Object[] values(EntityBean bean) {
return bindValues(embIdProperty.getValue(bean));
}
@Override
public Object[] getBindValues(Object value) {
Object[] bindValues = new Object[props.length];
public Object[] bindValues(Object value) {
final EntityBean bean = (EntityBean) value;
final Object[] bindValues = new Object[props.length];
for (int i = 0; i < props.length; i++) {
bindValues[i] = props[i].getValue((EntityBean) value);
bindValues[i] = props[i].getValue(bean);
}
return bindValues;
}
@@ -231,9 +217,9 @@ public final class IdBinderEmbedded implements IdBinder {
* Convert from embedded bean to Map.
*/
@Override
public Object getIdForJson(EntityBean bean) {
EntityBean ebValue = (EntityBean) embIdProperty.getValue(bean);
Map<String, Object> map = new LinkedHashMap<>();
public Object convertForJson(EntityBean bean) {
final EntityBean ebValue = (EntityBean) embIdProperty.getValue(bean);
final Map<String, Object> map = new LinkedHashMap<>();
for (BeanProperty prop : props) {
map.put(prop.name(), prop.getValue(ebValue));
}
@@ -245,9 +231,9 @@ public final class IdBinderEmbedded implements IdBinder {
*/
@Override
@SuppressWarnings("unchecked")
public Object convertIdFromJson(Object value) {
Map<String, Object> map = (Map<String, Object>) value;
EntityBean idValue = idDesc.createEntityBean();
public Object convertFromJson(Object value) {
final Map<String, Object> map = (Map<String, Object>) value;
final EntityBean idValue = idDesc.createEntityBean();
for (BeanProperty prop : props) {
prop.setValue(idValue, map.get(prop.name()));
}
@@ -256,42 +242,43 @@ public final class IdBinderEmbedded implements IdBinder {
@Override
public void bindId(DefaultSqlUpdate sqlUpdate, Object value) {
final EntityBean bean = (EntityBean) value;
for (BeanProperty prop : props) {
Object embFieldValue = prop.getValue((EntityBean) value);
sqlUpdate.setParameter(embFieldValue);
sqlUpdate.setParameter(prop.getValue(bean));
}
}
@Override
public void bindId(DataBind dataBind, Object value) throws SQLException {
final EntityBean bean = (EntityBean) value;
for (BeanProperty prop : props) {
Object embFieldValue = prop.getValue((EntityBean) value);
prop.bind(dataBind, embFieldValue);
prop.bind(dataBind, prop.getValue(bean));
}
}
@Override
public void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> values) {
public void addBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> values) {
for (Object value : values) {
bindId(sqlUpdate, value);
}
}
@Override
public void addIdInBindValues(SpiExpressionRequest request, Collection<?> values) {
public void addBindValues(SpiExpressionRequest request, Collection<?> values) {
for (Object value : values) {
final EntityBean bean = (EntityBean) value;
for (BeanProperty prop : props) {
request.addBindValue(prop.getValue((EntityBean) value));
request.addBindValue(prop.getValue(bean));
}
}
}
@Override
public Object readData(DataInput dataInput) throws IOException {
EntityBean embId = idDesc.createEntityBean();
final EntityBean embId = idDesc.createEntityBean();
boolean notNull = true;
for (BeanProperty prop : props) {
Object value = prop.readData(dataInput);
final Object value = prop.readData(dataInput);
prop.setValue(embId, value);
if (value == null) {
notNull = false;
@@ -302,9 +289,9 @@ public final class IdBinderEmbedded implements IdBinder {
@Override
public void writeData(DataOutput dataOutput, Object idValue) throws IOException {
final EntityBean bean = (EntityBean) idValue;
for (BeanProperty prop : props) {
Object embFieldValue = prop.getValue((EntityBean) idValue);
prop.writeData(dataOutput, embFieldValue);
prop.writeData(dataOutput, prop.getValue(bean));
}
}
@@ -317,10 +304,10 @@ public final class IdBinderEmbedded implements IdBinder {
@Override
public Object read(DbReadContext ctx) throws SQLException {
EntityBean embId = idDesc.createEntityBean();
final EntityBean embId = idDesc.createEntityBean();
boolean nullValue = true;
for (BeanProperty prop : props) {
Object value = prop.read(ctx);
final Object value = prop.read(ctx);
if (value != null) {
prop.setValue(embId, value);
nullValue = false;
@@ -348,8 +335,8 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public String getAssocIdInExpr(String prefix) {
StringBuilder sb = new StringBuilder();
public String assocInExpr(String prefix) {
final StringBuilder sb = new StringBuilder(80);
sb.append("(");
for (int i = 0; i < props.length; i++) {
if (i > 0) {
@@ -365,8 +352,8 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public String getAssocOneIdExpr(String prefix, String operator) {
StringBuilder sb = new StringBuilder();
public String assocExpr(String prefix, String operator) {
final StringBuilder sb = new StringBuilder(100);
for (int i = 0; i < props.length; i++) {
if (i > 0) {
sb.append(" and ");
@@ -383,8 +370,8 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public String getBindIdSql(String baseTableAlias) {
StringBuilder sb = new StringBuilder();
public String bindEqSql(String baseTableAlias) {
final StringBuilder sb = new StringBuilder(80);
for (int i = 0; i < props.length; i++) {
if (i > 0) {
sb.append(" and ");
@@ -398,11 +385,11 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public String getBindIdInSql(String baseTableAlias) {
public String bindInSql(String baseTableAlias) {
if (idInExpandedForm) {
return "";
}
StringBuilder sb = new StringBuilder();
final StringBuilder sb = new StringBuilder(80);
sb.append("(");
for (int i = 0; i < props.length; i++) {
if (i > 0) {
@@ -442,8 +429,8 @@ public final class IdBinderEmbedded implements IdBinder {
@Override
public String cacheKey(Object value) {
EntityBean bean = (EntityBean)value;
StringBuilder sb = new StringBuilder();
final EntityBean bean = (EntityBean)value;
final StringBuilder sb = new StringBuilder(80);
for (BeanProperty prop : props) {
Object val = prop.getValue(bean);
if (val != null) {
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.deploy.id;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.bind.DataBind;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.bind.DataBind;
import java.io.DataInput;
import java.io.DataOutput;
@@ -27,13 +27,18 @@ final class IdBinderEmpty implements IdBinder {
public void initialise() {
}
@Override
public String idSelect() {
return "";
}
@Override
public boolean isIdInExpandedForm() {
return false;
}
@Override
public String getOrderBy(String pathPrefix, boolean ascending) {
public String orderBy(String pathPrefix, boolean ascending) {
return pathPrefix;
}
@@ -42,12 +47,7 @@ final class IdBinderEmpty implements IdBinder {
}
@Override
public BeanProperty getBeanProperty() {
return null;
}
@Override
public String getIdProperty() {
public BeanProperty beanProperty() {
return null;
}
@@ -62,58 +62,58 @@ final class IdBinderEmpty implements IdBinder {
}
@Override
public String getDefaultOrderBy() {
public String orderBy() {
// this should never happen?
return "";
}
@Override
public String getBindIdSql(String baseTableAlias) {
public String bindEqSql(String baseTableAlias) {
return bindIdSql;
}
@Override
public String getAssocOneIdExpr(String prefix, String operator) {
public String assocExpr(String prefix, String operator) {
return null;
}
@Override
public String getAssocIdInExpr(String prefix) {
public String assocInExpr(String prefix) {
return null;
}
@Override
public String getIdInValueExprDelete(int size) {
return getIdInValueExpr(false, size);
public String idInValueExprDelete(int size) {
return idInValueExpr(false, size);
}
@Override
public String getIdInValueExpr(boolean not, int size) {
public String idInValueExpr(boolean not, int size) {
return "";
}
@Override
public String getBindIdInSql(String baseTableAlias) {
public String bindInSql(String baseTableAlias) {
return null;
}
@Override
public Object[] getIdValues(EntityBean bean) {
public Object[] values(EntityBean bean) {
return null;
}
@Override
public Object[] getBindValues(Object idValue) {
public Object[] bindValues(Object idValue) {
return new Object[]{idValue};
}
@Override
public Object getIdForJson(EntityBean bean) {
public Object convertForJson(EntityBean bean) {
return null;
}
@Override
public Object convertIdFromJson(Object value) {
public Object convertFromJson(Object value) {
return value;
}
@@ -126,11 +126,11 @@ final class IdBinderEmpty implements IdBinder {
}
@Override
public void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids) {
public void addBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids) {
}
@Override
public void addIdInBindValues(SpiExpressionRequest request, Collection<?> ids) {
public void addBindValues(SpiExpressionRequest request, Collection<?> ids) {
}
@Override
@@ -45,13 +45,18 @@ public final class IdBinderSimple implements IdBinder {
// do nothing
}
@Override
public String idSelect() {
return idProperty.name();
}
@Override
public boolean isIdInExpandedForm() {
return false;
}
@Override
public String getOrderBy(String pathPrefix, boolean ascending) {
public String orderBy(String pathPrefix, boolean ascending) {
StringBuilder sb = new StringBuilder();
if (pathPrefix != null) {
sb.append(pathPrefix).append(".");
@@ -69,15 +74,10 @@ public final class IdBinderSimple implements IdBinder {
}
@Override
public BeanProperty getBeanProperty() {
public BeanProperty beanProperty() {
return idProperty;
}
@Override
public String getIdProperty() {
return idProperty.name();
}
@Override
public BeanProperty findBeanProperty(String dbColumnName) {
if (dbColumnName.equalsIgnoreCase(idProperty.dbColumn())) {
@@ -92,12 +92,12 @@ public final class IdBinderSimple implements IdBinder {
}
@Override
public String getDefaultOrderBy() {
public String orderBy() {
return idProperty.name();
}
@Override
public String getBindIdInSql(String baseTableAlias) {
public String bindInSql(String baseTableAlias) {
if (baseTableAlias == null) {
return idProperty.dbColumn();
} else {
@@ -106,7 +106,7 @@ public final class IdBinderSimple implements IdBinder {
}
@Override
public String getBindIdSql(String baseTableAlias) {
public String bindEqSql(String baseTableAlias) {
if (baseTableAlias == null) {
return bindIdSql;
} else {
@@ -115,22 +115,22 @@ public final class IdBinderSimple implements IdBinder {
}
@Override
public Object[] getIdValues(EntityBean bean) {
public Object[] values(EntityBean bean) {
return new Object[]{idProperty.getValue(bean)};
}
@Override
public Object[] getBindValues(Object idValue) {
public Object[] bindValues(Object idValue) {
return new Object[]{idValue};
}
@Override
public String getIdInValueExprDelete(int size) {
return getIdInValueExpr(false, size);
public String idInValueExprDelete(int size) {
return idInValueExpr(false, size);
}
@Override
public String getIdInValueExpr(boolean not, int size) {
public String idInValueExpr(boolean not, int size) {
if (size <= 0) {
throw new IndexOutOfBoundsException("The size must be at least 1");
}
@@ -138,24 +138,24 @@ public final class IdBinderSimple implements IdBinder {
}
@Override
public void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids) {
public void addBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids) {
sqlUpdate.setParameter(new MultiValueWrapper(ids));
}
@Override
public void addIdInBindValues(SpiExpressionRequest request, Collection<?> values) {
public void addBindValues(SpiExpressionRequest request, Collection<?> values) {
List<Object> copy = new ArrayList<>(values);
copy.replaceAll(idValue -> convertSetId(idValue, null));
request.addBindValue(new MultiValueWrapper(copy));
}
@Override
public Object getIdForJson(EntityBean bean) {
public Object convertForJson(EntityBean bean) {
return idProperty.getValue(bean);
}
@Override
public Object convertIdFromJson(Object value) {
public Object convertFromJson(Object value) {
// handle simple type conversion if required
return convertId(value);
}
@@ -208,11 +208,11 @@ public final class IdBinderSimple implements IdBinder {
}
@Override
public String getAssocOneIdExpr(String prefix, String operator) {
StringBuilder sb = new StringBuilder();
public String assocExpr(String prefix, String operator) {
StringBuilder sb = new StringBuilder(25);
if (prefix != null) {
sb.append(prefix);
sb.append(".");
sb.append('.');
}
sb.append(idProperty.name());
sb.append(operator);
@@ -220,7 +220,7 @@ public final class IdBinderSimple implements IdBinder {
}
@Override
public String getAssocIdInExpr(String prefix) {
public String assocInExpr(String prefix) {
StringBuilder sb = new StringBuilder();
if (prefix != null) {
sb.append(prefix);
@@ -654,7 +654,7 @@ public class DeployBeanDescriptor<T> {
public void postAnnotations() {
if (idClass != null) {
idClassProperty = new DeployBeanPropertyAssocOne<>(this, idClass);
idClassProperty.setName("_idClass");
idClassProperty.setName("_$IdClass$");
idClassProperty.setEmbedded();
idClassProperty.setNullable(false);
}
@@ -22,7 +22,7 @@ public class VisitProperties {
protected void visitProperties(BeanDescriptor<?> desc, BeanPropertyVisitor propertyVisitor) {
BeanProperty idProp = desc.idProperty();
if (idProp != null) {
if (idProp != null && !idProp.name().equals("_$IdClass$")) {
visit(propertyVisitor, idProp);
}
BeanPropertyAssocOne<?> unidirectional = desc.unidirectional();
@@ -471,6 +471,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
return new InPairsExpression(pairs, false);
}
@Override
public Expression inTuples(InTuples pairs) {
return new InTuplesExpression(pairs, false);
}
/**
* In - property has a value in the array of values.
*/
@@ -612,9 +612,9 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
for (SpiExpression expr : list) {
expr.queryPlanHash(builder);
builder.append(",");
builder.append(',');
}
builder.append("]");
builder.append(']');
}
@Override
@@ -902,6 +902,10 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return add(expr.inPairs(pairs));
}
@Override
public ExpressionList<T> inTuples(InTuples pairs) {
return add(expr.inTuples(pairs));
}
@Override
public ExpressionList<T> exists(String sqlSubQuery, Object... bindValues) {
@@ -110,6 +110,11 @@ public final class DefaultExpressionRequest implements SpiExpressionRequest {
return queryRequest;
}
@Override
public StringBuilder buffer() {
return sql;
}
/**
* Append text the underlying sql expression.
*/
@@ -66,7 +66,7 @@ public final class IdInExpression extends NonPrepareExpression implements IdInCo
return;
}
// Bind the ID values including EmbeddedId and multiple ID
request.descriptor().idBinder().addIdInBindValues(request, idCollection);
request.descriptor().idBinder().addBindValues(request, idCollection);
}
/**
@@ -77,8 +77,8 @@ public final class IdInExpression extends NonPrepareExpression implements IdInCo
request.append(SQL_FALSE); // append false for this stage
} else {
final BeanDescriptor<?> descriptor = request.descriptor();
request.property(descriptor.idBinder().getBindIdInSql(null));
request.append(descriptor.idBinder().getIdInValueExpr(false, idCollection.size()));
request.property(descriptor.idBinder().bindInSql(null));
request.append(descriptor.idBinder().idInValueExpr(false, idCollection.size()));
}
}
@@ -91,9 +91,9 @@ public final class IdInExpression extends NonPrepareExpression implements IdInCo
final IdBinder idBinder = descriptor.idBinder();
if (idBinder.isComplexId()) {
request.parse(descriptor.idBinderInLHSSql());
request.append(idBinder.getIdInValueExpr(false, idCollection.size()));
request.append(idBinder.idInValueExpr(false, idCollection.size()));
} else {
request.property(idBinder.getBeanProperty().name());
request.property(idBinder.beanProperty().name());
request.appendInExpression(false, idCollection);
}
}
@@ -0,0 +1,73 @@
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("'}");
}
}
}
@@ -0,0 +1,192 @@
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;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import java.util.List;
import java.util.UUID;
import static java.util.Objects.requireNonNull;
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("");
SpiInTuples inTuples = (SpiInTuples) pairs;
this.properties = inTuples.properties();
// the entries might be modified on cache hit.
this.entries = inTuples.entries();
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;
}
@Override
public void writeDocQuery(DocQueryContext context) {
throw new RuntimeException("Not supported with document query");
}
@Override
public void addBindValues(SpiExpressionRequest request) {
if (literalMode) {
return;
}
for (Object[] entry : entries) {
for (Object value : entry) {
requireNonNull(value);
request.addBindValue(value);
}
}
}
@Override
public void addSql(SpiExpressionRequest request) {
if (entries.isEmpty()) {
request.append(not ? SQL_TRUE : SQL_FALSE);
return;
}
request.append("(");
for (int i = 0; i < properties.length; i++) {
if (i > 0) {
request.append(",");
}
request.property(properties[i]);
}
request.append(") in (");
if (literalMode) {
addSqlLiterals(request);
} else {
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++) {
if (i > 0) {
request.append(",");
}
request.append(eb);
}
}
private String entryBinding() {
StringBuilder sb = new StringBuilder();
sb.append('(');
for (int i = 0; i < properties.length; i++) {
if (i > 0) {
sb.append(',');
}
sb.append('?');
}
return sb.append(')').toString();
}
/**
* Based on the number of values in the in clause.
*/
@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[");
for (String property : properties) {
builder.append(property).append("-");
}
builder.append(entries.size()).append(']');
}
@Override
public void queryBindKey(BindValuesKey key) {
key.add(entries.size());
for (Object[] entry : entries) {
for (Object value : entry) {
key.add(value);
}
}
}
@Override
public boolean isSameByBind(SpiExpression other) {
InTuplesExpression that = (InTuplesExpression) other;
return this.entries.size() == that.entries.size() && entries.equals(that.entries);
}
}
@@ -710,6 +710,11 @@ final class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expr
return exprList.inPairs(pairs);
}
@Override
public ExpressionList<T> inTuples(InTuples pairs) {
return exprList.inTuples(pairs);
}
@Override
public ExpressionList<T> in(String propertyName, Collection<?> values) {
return exprList.in(propertyName, values);
@@ -224,14 +224,21 @@ public final class BatchControl {
* Execute all the requests contained in the list.
*/
void executeNow(ArrayList<PersistRequest> list) throws BatchedSqlException {
for (int i = 0; i < list.size(); i++) {
if (i % batchSize == 0) {
// hit the batch size so flush
flushPstmtHolder();
boolean old = transaction.isFlushOnQuery();
transaction.setFlushOnQuery(false);
// disable flush on query due transsaction callbacks
try {
for (int i = 0; i < list.size(); i++) {
if (i % batchSize == 0) {
// hit the batch size so flush
flushPstmtHolder();
}
list.get(i).executeNow();
}
list.get(i).executeNow();
flushPstmtHolder();
} finally {
transaction.setFlushOnQuery(old);
}
flushPstmtHolder();
}
public void flushOnCommit() throws BatchedSqlException {
@@ -44,13 +44,21 @@ public final class DefaultPersister implements Persister {
private final PersistExecute persistExecute;
private final SpiEbeanServer server;
private final BeanDescriptorManager beanDescriptorManager;
private final int maxInBinding;
private final int maxDeleteBatch;
public DefaultPersister(SpiEbeanServer server, Binder binder, BeanDescriptorManager descMgr) {
this.server = server;
this.beanDescriptorManager = descMgr;
this.persistExecute = new DefaultPersistExecute(binder, server.config().getPersistBatchSize());
this.maxInBinding = server.databasePlatform().maxInBinding();
this.maxDeleteBatch = initMaxDeleteBatch(server.databasePlatform().maxInBinding());
}
/**
* When delete batching is required limit the size.
* e.g. SqlServer has a 2100 parameter limit, so delete max 2000 for it.
*/
private int initMaxDeleteBatch(int maxInBinding) {
return maxInBinding == 0 ? 1000 : maxInBinding;
}
@Override
@@ -647,16 +655,13 @@ public final class DefaultPersister implements Persister {
* Delete by Id or a List of Id's.
*/
private int delete(BeanDescriptor<?> descriptor, Object id, List<Object> idList, Transaction transaction, DeleteMode deleteMode) {
if (idList == null || idList.size() <= maxDeleteBatch) {
return deleteBatch(descriptor, id, idList, transaction, deleteMode);
}
int rows = 0;
if (maxInBinding > 0) {
// SqlServer has a 2100 parameter limit, so delete max 2000 ids at once
// this gives space up to 100 more query parameters.
while (idList != null && idList.size() > maxInBinding) {
rows += deleteBatch(descriptor, id, idList.subList(0, maxInBinding), transaction, deleteMode);
idList = idList.subList(maxInBinding, idList.size());
}
for (List<Object> batchOfIds : Lists.partition(idList, maxDeleteBatch)) {
rows += deleteBatch(descriptor, id, batchOfIds, transaction, deleteMode);
}
rows += deleteBatch(descriptor, id, idList, transaction, deleteMode);
return rows;
}
@@ -64,7 +64,6 @@ final class BindableIdEmbedded implements BindableId {
@Override
public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException {
EntityBean idValue = (EntityBean) embId.getValue(bean);
for (BeanProperty prop : props) {
Object value = prop.getValue(idValue);
@@ -75,14 +74,20 @@ final class BindableIdEmbedded implements BindableId {
@Override
public void dmlAppend(GenerateDmlRequest request) {
for (BeanProperty prop : props) {
request.appendColumn(prop.dbColumn());
if (matches != null) {
// prefer the match dbColumns over the embedded property ones
for (MatchedImportedProperty match : matches) {
request.appendColumn(match.dbColumn());
}
} else {
for (BeanProperty prop : props) {
request.appendColumn(prop.dbColumn());
}
}
}
@Override
public boolean deriveConcatenatedId(PersistRequestBean<?> persist) {
if (matches == null) {
String m = "No matches for " + embId.fullName() + " the concatenated key columns where not found?"
+ " I expect that the concatenated key was null, and this bean does"
@@ -30,4 +30,8 @@ final class MatchedImportedEmbedded implements MatchedImportedProperty {
localProp.setValue(embeddedId, value);
}
@Override
public String dbColumn() {
return localProp.dbColumn();
}
}
@@ -39,6 +39,12 @@ final class MatchedImportedFactory {
return new MatchedImportedScalar(prop, beanProperty);
}
}
// match on property name
for (BeanProperty beanProperty : desc.propertiesBaseScalar()) {
if (prop.name().equals(beanProperty.name())) {
return new MatchedImportedScalar(prop, beanProperty);
}
}
// there was no matching assoc one property.
// example UserRole bean missing assoc one to User?
return null;
@@ -8,4 +8,9 @@ interface MatchedImportedProperty {
* Populate the embeddedId bean from the source entity.
*/
void populate(EntityBean sourceBean, EntityBean embeddedId);
/**
* Return the DB column to use.
*/
String dbColumn();
}
@@ -22,4 +22,9 @@ final class MatchedImportedScalar implements MatchedImportedProperty {
localProp.setValue(embeddedId, value);
}
@Override
public String dbColumn() {
return foreignProp.dbColumn();
}
}
@@ -59,7 +59,7 @@ final class CQueryOrderBy {
if (beanProperty instanceof BeanPropertyAssoc<?>) {
BeanPropertyAssoc<?> ap = (BeanPropertyAssoc<?>) beanProperty;
IdBinder idBinder = ap.targetDescriptor().idBinder();
return idBinder.getOrderBy(el.elName(), p.isAscending());
return idBinder.orderBy(el.elName(), p.isAscending());
}
return p.toStringFormat();
}
@@ -197,7 +197,7 @@ final class DefaultDbSqlContext implements DbSqlContext {
@Override
public String tableAliasManyWhere(String prefix) {
return alias.tableAliasManyWhere(prefix);
return prefix == null ? tableAliasStack.peek() : alias.tableAliasManyWhere(prefix);
}
@Override
@@ -225,9 +225,8 @@ final class DefaultDbSqlContext implements DbSqlContext {
}
@Override
public void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType, String manyWhere) {
public void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType, String tableAlias) {
// replace ${ta} placeholder with the real table alias...
String tableAlias = manyWhere == null ? tableAliasStack.peek() : tableAliasManyWhere(manyWhere);
String converted = sqlFormulaJoin.replace(tableAliasPlaceHolder, tableAlias);
if (formulaJoins == null) {
formulaJoins = new HashSet<>();
@@ -240,11 +239,17 @@ final class DefaultDbSqlContext implements DbSqlContext {
formulaJoins.add(converted);
sb.append(" ");
if (joinType == SqlJoinType.OUTER) {
if ("join".equalsIgnoreCase(sqlFormulaJoin.substring(0, 4))) {
if ("join".equalsIgnoreCase(converted.substring(0, 4))) {
// prepend left as we are in the 'many' part
sb.append("left ");
}
}
if (joinType == SqlJoinType.INNER) {
if ("left join".equalsIgnoreCase(converted.substring(0, 9))) {
// remove left as we do not need it
converted = converted.substring(5);
}
}
sb.append(converted);
}
@@ -37,6 +37,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -219,6 +220,11 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public Query<T> alsoIf(BooleanSupplier predicate, Consumer<Query<T>> apply) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public Query<T> usingTransaction(Transaction transaction) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
@@ -234,6 +240,11 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public Query<T> usingMaster() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public <A> List<A> findIds() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
@@ -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");
@@ -19,7 +19,7 @@ public final class QueryPlanLoggerPostgres extends QueryPlanLogger {
@Override
public SpiDbQueryPlan collectPlan(Connection conn, SpiQueryPlan plan, BindCapture bind) {
String explain = "explain analyze " + plan.sql();
String explain = "explain (analyze, buffers) " + plan.sql();
try (PreparedStatement explainStmt = conn.prepareStatement(explain)) {
bind.prepare(explainStmt, conn);
try (ResultSet rset = explainStmt.executeQuery()) {
@@ -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");
@@ -81,7 +81,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
// if we have no property ask first children (in a distinct select with join)
if (children.length == 0) {
// expected to be a findIds query
return desc.idBinder().getBeanProperty();
return desc.idBinder().beanProperty();
}
return children[0].singleAttributeReader();
}
@@ -159,7 +159,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
lazyLoadParent.addSelectExported(ctx, prefix);
}
if (readId) {
appendSelectId(ctx, idBinder.getBeanProperty());
appendSelectId(ctx, idBinder.beanProperty());
}
for (STreeProperty property : properties) {
if (!property.isAggregation()) {
@@ -201,7 +201,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (!subQuery && inheritInfo != null) {
ctx.appendColumn(inheritInfo.getDiscriminatorColumn());
}
appendSelectId(ctx, idBinder.getBeanProperty());
appendSelectId(ctx, idBinder.beanProperty());
}
appendSelect(ctx, subQuery, properties);
for (SqlTreeNode child : children) {
@@ -30,7 +30,7 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
@Override
public void appendDistinctOn(DbSqlContext ctx, boolean subQuery) {
ctx.pushTableAlias(prefix);
appendSelectId(ctx, idBinder.getBeanProperty());
appendSelectId(ctx, idBinder.beanProperty());
ctx.popTableAlias();
}
@@ -45,7 +45,7 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean {
public void appendDistinctOn(DbSqlContext ctx, boolean subQuery) {
if (readId) {
ctx.pushTableAlias(prefix);
appendSelectId(ctx, idBinder.getBeanProperty());
appendSelectId(ctx, idBinder.beanProperty());
ctx.popTableAlias();
super.appendDistinctOn(ctx, subQuery);
}
@@ -18,6 +18,7 @@ public class AbstractQuery implements SpiCancelableQuery {
private final ReentrantLock lock = new ReentrantLock();
private boolean cancelled;
private CancelableQuery cancelableQuery;
protected boolean useMaster;
@Override
public final void cancel() {
@@ -48,6 +48,7 @@ public final class DefaultDtoQuery<T> extends AbstractQuery implements SpiDtoQue
this.server = server;
this.descriptor = descriptor;
this.ormQuery = ormQuery;
this.useMaster = ormQuery.isUseMaster();
this.label = ormQuery.label();
this.profileLocation = ormQuery.profileLocation();
}
@@ -88,6 +89,17 @@ public final class DefaultDtoQuery<T> extends AbstractQuery implements SpiDtoQue
return this;
}
@Override
public DtoQuery<T> usingMaster() {
this.useMaster = true;
return this;
}
@Override
public boolean isUseMaster() {
return useMaster;
}
@Override
public void findEach(Consumer<T> consumer) {
server.findDtoEach(this, consumer);
@@ -26,6 +26,7 @@ import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.Timestamp;
import java.util.*;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -282,6 +283,14 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
return this;
}
@Override
public Query<T> alsoIf(BooleanSupplier predicate, Consumer<Query<T>> consumer) {
if (predicate.getAsBoolean()) {
consumer.accept(this);
}
return this;
}
@Override
public final void addSoftDeletePredicate(String softDeletePredicate) {
if (softDeletePredicates == null) {
@@ -715,6 +724,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
// forUpdate is NOT copied - see #2762
DefaultOrmQuery<T> copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory);
copy.transaction = transaction;
copy.useMaster = useMaster;
copy.m2mIncludeJoin = m2mIncludeJoin;
copy.profilingListener = profilingListener;
copy.profileLocation = profileLocation;
@@ -1037,13 +1047,13 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
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) {
@@ -1082,27 +1092,27 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
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();
}
@@ -1396,6 +1406,17 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
return this;
}
@Override
public Query<T> usingMaster() {
this.useMaster = true;
return this;
}
@Override
public boolean isUseMaster() {
return useMaster;
}
@Override
public final int delete() {
return server.delete(this, transaction);
@@ -49,6 +49,17 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq
return this;
}
@Override
public SqlQuery usingMaster() {
this.useMaster = true;
return this;
}
@Override
public boolean isUseMaster() {
return useMaster;
}
private void transaction(Transaction transaction) {
this.transaction = transaction;
}
@@ -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;
@@ -43,10 +43,11 @@ public interface DataSourceSupplier {
/**
* Return a connection from the read only DataSource taking into account a tenantId for multi-tenant lazy loading.
*
* @param tenantId Most often null but well supplied indicates a multi-tenant lazy loading query
* @param tenantId Most often null but well supplied indicates a multi-tenant lazy loading query
* @param useMaster When true use the master data source
* @return the connection to use
*/
Connection readOnlyConnection(Object tenantId) throws SQLException;
Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException;
/**
* Shutdown the datasource de-registering the JDBC driver if requested.
@@ -24,7 +24,7 @@ public final class DocStoreTransactionManager extends TransactionManager {
}
@Override
public SpiTransaction createReadOnlyTransaction(Object tenantId) {
public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) {
return new DocStoreOnlyTransaction(false, this);
}
@@ -32,6 +32,10 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
private static final String illegalStateMessage = "Transaction is Inactive";
private static final String notExpectedMessage = "Not expected on read only transaction";
/**
* Set false when using autoCommit (as a performance optimisation for the read-only case).
*/
private final boolean useCommit;
private final TransactionManager manager;
private final SpiTxnLogger logger;
private final boolean logSql;
@@ -60,7 +64,7 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
/**
* Create without a tenantId.
*/
ImplicitReadOnlyTransaction(TransactionManager manager, Connection connection) {
ImplicitReadOnlyTransaction(boolean useCommit, TransactionManager manager, Connection connection) {
this.manager = manager;
this.logger = manager.loggerReadOnly();
this.logSql = logger.isLogSql();
@@ -69,13 +73,18 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
this.connection = connection;
this.persistenceContext = new DefaultPersistenceContext();
this.startNanos = System.nanoTime();
try {
this.useCommit = useCommit && !connection.getAutoCommit();
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
/**
* Create with a tenantId.
*/
ImplicitReadOnlyTransaction(TransactionManager manager, Connection connection, Object tenantId) {
this(manager, connection);
this(true, manager, connection);
this.tenantId = tenantId;
}
@@ -505,16 +514,22 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
// do nothing, expect AutoCommit
}
/**
* Commit the transaction.
*/
@Override
public void commit() {
if (!active) {
throw new IllegalStateException(illegalStateMessage);
}
// expect AutoCommit so just deactivate / put back into pool
deactivate();
try {
if (useCommit) {
try {
connection.commit();
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
} finally {
deactivate();
}
}
/**
@@ -556,8 +571,17 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
if (!active) {
throw new IllegalStateException(illegalStateMessage);
}
// expect AutoCommit so it really has already committed
deactivate();
try {
if (useCommit) {
try {
connection.rollback();
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
} finally {
deactivate();
}
}
/**
@@ -565,7 +589,7 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
*/
@Override
public void end() throws PersistenceException {
if (isActive()) {
if (active) {
rollback();
}
}
@@ -190,7 +190,6 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
}
@Override
public final void setAutoPersistUpdates(boolean autoPersistUpdates) {
this.autoPersistUpdates = autoPersistUpdates;
@@ -251,7 +250,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
callbackList.add(callback);
}
private void withEachCallback(Consumer<TransactionCallback> consumer) {
private void withEachCallbackFailSilent(Consumer<TransactionCallback> consumer) {
if (callbackList != null) {
// using old style loop to cater for case when new callbacks are added recursively (as otherwise iterator fails fast)
for (int i = 0; i < callbackList.size(); i++) {
@@ -259,17 +258,27 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
consumer.accept(callbackList.get(i));
} catch (Exception e) {
log.log(ERROR, "Error executing transaction callback", e);
throw wrapIfNeeded(e);
}
}
}
}
private void withEachCallback(Consumer<TransactionCallback> consumer) {
if (callbackList != null) {
// using old style loop to cater for case when new callbacks are added recursively (as otherwise iterator fails fast)
for (int i = 0; i < callbackList.size(); i++) {
consumer.accept(callbackList.get(i));
}
}
}
private void firePreRollback() {
withEachCallback(TransactionCallback::preRollback);
withEachCallbackFailSilent(TransactionCallback::preRollback);
}
private void firePostRollback() {
withEachCallback(TransactionCallback::postRollback);
withEachCallbackFailSilent(TransactionCallback::postRollback);
if (changeLogHolder != null) {
changeLogHolder.postRollback();
}
@@ -78,8 +78,9 @@ public final class JtaTransactionManager implements ExternalTransactionManager {
*/
@Override
public Object getCurrentTransaction() {
TransactionSynchronizationRegistry syncRegistry = registry();
TransactionSynchronizationRegistry syncRegistry;
try {
syncRegistry = registry();
SpiTransaction t = (SpiTransaction) syncRegistry.getResource(EBEAN_TXN_RESOURCE);
if (t != null) {
// we have already seen this transaction
@@ -21,9 +21,10 @@ abstract class TransactionFactory {
/**
* Return a new query only transaction.
*
* @param tenantId The tenantId for lazy loading queries.
* @param tenantId The tenantId for lazy loading queries.
* @param useMaster Explicitly use the master data source rather than read only data source
*/
abstract SpiTransaction createReadOnlyTransaction(Object tenantId);
abstract SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster);
/**
* Return a new transaction.
@@ -13,7 +13,7 @@ import java.sql.SQLException;
*/
class TransactionFactoryBasic extends TransactionFactory {
private final DataSource dataSource;
protected final DataSource dataSource;
TransactionFactoryBasic(TransactionManager manager, DataSourceSupplier dataSourceSupplier) {
super(manager);
@@ -21,12 +21,11 @@ class TransactionFactoryBasic extends TransactionFactory {
}
@Override
public SpiTransaction createReadOnlyTransaction(Object tenantId) {
public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) {
Connection connection = null;
try {
connection = dataSource.getConnection();
return create(false, connection);
return new ImplicitReadOnlyTransaction(true, manager, connection);
} catch (PersistenceException ex) {
JdbcClose.close(connection);
throw ex;
@@ -26,11 +26,11 @@ final class TransactionFactoryBasicWithRead extends TransactionFactoryBasic {
}
@Override
public SpiTransaction createReadOnlyTransaction(Object tenantId) {
public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) {
Connection connection = null;
try {
connection = readOnlyDataSource.getConnection();
return new ImplicitReadOnlyTransaction(manager, connection);
connection = useMaster ? dataSource.getConnection() : readOnlyDataSource.getConnection();
return new ImplicitReadOnlyTransaction(useMaster, manager, connection);
} catch (PersistenceException ex) {
JdbcClose.close(connection);
throw ex;
@@ -11,7 +11,6 @@ final class TransactionFactoryBuilder {
* Build and return based on multi-tenancy and read only DataSource.
*/
static TransactionFactory build(TransactionManager manager, DataSourceSupplier dataSourceSupplier, CurrentTenantProvider tenantProvider) {
boolean hasReadOnlyDataSource = dataSourceSupplier.readOnlyDataSource() != null;
if (tenantProvider == null) {
if (hasReadOnlyDataSource) {
@@ -24,7 +24,7 @@ class TransactionFactoryTenant extends TransactionFactory {
}
@Override
public SpiTransaction createReadOnlyTransaction(Object tenantId) {
public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) {
return create(false, tenantId);
}
@@ -23,14 +23,14 @@ final class TransactionFactoryTenantWithRead extends TransactionFactoryTenant {
}
@Override
public SpiTransaction createReadOnlyTransaction(Object tenantId) {
public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) {
Connection connection = null;
try {
if (tenantId == null) {
// obtain the tenantId if the DataSource requires it
tenantId = dataSourceSupplier.currentTenantId();
}
connection = dataSourceSupplier.readOnlyConnection(tenantId);
connection = dataSourceSupplier.readOnlyConnection(tenantId, useMaster);
return new ImplicitReadOnlyTransaction(manager, connection, tenantId);
} catch (PersistenceException ex) {
JdbcClose.close(connection);

Some files were not shown because too many files have changed in this diff Show More