Compare commits

..
Author SHA1 Message Date
Rob Bygrave b6b666013e ClickHouse: Support optimised inserts for ClickHouse 2024-04-17 22:50:28 +12:00
Rob Bygrave e51d9ba2ab Fix test test-java16 using maven.compiler.release 2024-04-14 22:26:20 +12:00
Rob BygraveandGitHub 1b1dce90ed Merge pull request #3385 from ebean-orm/feature/rename-tqrootbean
Rename TQRootBean to QueryBean
2024-04-12 22:47:07 +12:00
Rob BygraveandGitHub 405834fa45 Merge branch 'master' into feature/rename-tqrootbean 2024-04-12 22:46:55 +12:00
Rob BygraveandGitHub 7720229af9 Merge pull request #3389 from ebean-orm/feature/bump-datasource-2
Bump ebean-datasource, ebean-test-containers, ebean-agent
2024-04-12 21:51:51 +12:00
Rob Bygrave 89a294414e Bump ebean-agent 14.2.1 2024-04-12 21:49:56 +12:00
Rob Bygrave 7bbfc00b86 Bump ebean-datasource and ebean-test-containers
ebean-datasource 8.14 has experimental lambda mode
ebean-test-containers has helper method to create S3Client for Localstack
2024-04-11 23:20:37 +12:00
Rob BygraveandGitHub d6c76de0a2 Merge pull request #3387 from ebean-orm/feature/bump-parent-4_1-byteBuddy
Bump parent to 4.1 with Byte-Buddy net.bytebuddy.experimental=true
2024-04-09 21:32:32 +12:00
Rob Bygrave 36f3986689 Bump avaje junit composite and bytebuddy to 1.14.13 2024-04-09 21:06:52 +12:00
Rob Bygrave f0f4f13ec6 Bump parent to 4.1 with Byte-Buddy net.bytebuddy.experimental=true
Uses maven-surefire-plugin with:
<argLine>-XX:+EnableDynamicAgentLoading -Dnet.bytebuddy.experimental=true</argLine>
2024-04-09 21:03:59 +12:00
Rob Bygrave 08466d42e9 Fix test TestLazyLoadMany with orderBy on query 2024-04-05 22:56:05 +13:00
Rob Bygrave 1f841982a5 Rename TQRootBean to QueryBean 2024-04-05 22:08:26 +13:00
Rob Bygrave b34704a1fa Fix kotlin test to use latest kotlin-querybean-generator 2024-04-05 00:29:17 +13:00
Rob Bygrave 0128d64841 Version 14.1.0 2024-04-04 22:54:23 +13:00
Rob Bygrave 68b313f778 Update ebean-agent to 14.1.0
This version allows for removal of query bean setRoot() which is no longer needed.
2024-04-04 22:42:06 +13:00
Rob BygraveandGitHub fad32c58d8 Merge pull request #3384 from ebean-orm/feature/rename-iquerybean
Rename QueryBean interface to IQueryBean (because we want to rename TQRootBean)
2024-04-04 22:36:59 +13:00
Rob Bygrave 8f1ebdb89a Rename QueryBean interface to IQueryBean (because we want to rename TQRootBean)
We want to rename TQRootBean to QueryBean. I can't do that right now because we need to make ebean-agent aware of that first. So yes, perhaps not ideal with IQueryBean but I think it will make sense once TQRootBean is renamed to QueryBean.
2024-04-04 22:35:55 +13:00
Rob BygraveandGitHub f7703efde2 Merge pull request #3383 from ebean-orm/feature/orderById
#3379 Followup, Move orderById() to common QueryBuilder interface
2024-04-04 22:09:34 +13:00
Rob BygraveandGitHub c5ffccb649 Merge pull request #3382 from ebean-orm/fix/3381-pc-clear
Fix for #3381 - Empty OneToMany when overflowing LazyLoadBatchSize an…
2024-04-04 22:08:57 +13:00
Rob Bygrave 3e65705bf9 #3379 Followup, Move orderById() to common QueryBuilder interface
Effectively this adds it to QueryBeans (where it was missing)
2024-04-04 22:05:17 +13:00
Rob Bygrave 7469a38bbc #3375 Followup, Temporarily add back setRoot() method to allow time for IntelliJ plugin to be released
Adding this means that it will work with an old ebean-agent. It takes a few days to get a new IntelliJ plugin released and we can't wait due to wanting to release the bug fix for #3381
2024-04-04 21:53:18 +13:00
Rob Bygrave 7ea4311662 Fix for #3381 - Empty OneToMany when overflowing LazyLoadBatchSize and mixed queries
14.0.0 via #3295 introduced this bug.

 #3295 introduced a behaviour where bulk updates clear the persistence context. Now the lazy loading of a BeanCollection works with an assumption that the "parent bean" is in the persistence context (and this assumption is broken with that change for this test case). That is, the bulk update is clearing the persistence context - removing the parent bean BEFORE the lazy loading is invoked ... and that doesn't then work because the parent bean isn't in the persistence context.

 This fix means that when lazy loading many's, the parent beans are putIfAbsent into the persistence context.
2024-04-04 20:13:20 +13:00
Rob BygraveandGitHub f556e04f58 Merge pull request #3380 from ebean-orm/feature/deprecate-update-transaction
Deprecate update(transaction) and delete(transaction) - migrate to usingTransaction(transaction)
2024-04-01 20:57:58 +13:00
Rob Bygrave d9c75a60a5 Deprecate update(transaction) and delete(transaction) - migrate to usingTransaction(transaction)
Migrate to usingTransaction(transaction) to set the transaction explicitly on a delete or update query.
2024-04-01 20:35:59 +13:00
Rob BygraveandGitHub 225bec7b28 Merge pull request #3379 from ebean-orm/feature/query-builder-interface
Add QueryBuilder interface as common API for Query and QueryBean
2024-04-01 19:42:34 +13:00
Rob Bygrave da44f7e604 Add QueryBuilder interface as common API for Query and QueryBean 2024-04-01 18:58:57 +13:00
e0eda4f8be #3370 LimitOffsetPagedList add getTotalCount cache (#3373)
* #3370 LimitOffsetPagedList add getTotalCount cache

* Move totalRowCount check to inside the lock

* No change, format only on LimitOffsetPagedList

---------

Co-authored-by: Rob Bygrave <robin.bygrave@gmail.com>
2024-04-01 11:08:57 +13:00
Rob BygraveandGitHub e4e780588c Merge pull request #3378 from ebean-orm/feature/QueryBean-interface
Add QueryBean interface for query beans to implement
2024-04-01 10:59:38 +13:00
Rob Bygrave 7c8f235550 Add QueryBean interface for query beans to implement 2024-04-01 10:56:25 +13:00
Rob BygraveandGitHub 9a77ec1567 Merge pull request #3377 from ebean-orm/feature/3374
querybeans: #3374 - Add copy() method to TQRootBean
2024-04-01 09:09:24 +13:00
Rob Bygrave ff426319f5 Fix deprecated annotation for kotlin-querybean-generator 2024-04-01 09:02:13 +13:00
Rob Bygrave 7f56aff9dd Bump kotlin-querybean-generator 2024-03-31 11:49:58 +13:00
Rob Bygrave cdbdd98d36 querybeans: #3374 - Add copy() method to TQRootBean 2024-03-31 11:44:32 +13:00
Rob BygraveandGitHub 7bf030f59c Merge pull request #3376 from ebean-orm/feature/querybean-txn
Deprecate Query bean constructor that takes transaction - migrate to query.usingTransaction() instead
2024-03-31 11:38:26 +13:00
Rob Bygrave 16dca28831 Deprecate Query bean constructor that takes transaction - migrate to usingTransaction()
Migrate to query.usingTransaction() instead of the constructor that takes a transaction.
2024-03-31 11:37:32 +13:00
Rob BygraveandGitHub 678b43693d Merge pull request #3372 from ebean-orm/feature/1857-SlowQueryBindCapture
#1857 Add bind values capture for SlowQueryEvent / SlowQueryListener
2024-03-31 11:34:18 +13:00
Rob BygraveandGitHub 7ab93a0cdf Merge pull request #3375 from ebean-orm/feature/tidy-tqrootbean
Update TQRootBean with final methods, remove setRoot()
2024-03-31 11:32:54 +13:00
Rob Bygrave 951f942c73 Tidy TQRootBean constructor for alias.
Note that this requires the updated ebean-agent, where it will not longer use the setRoot() method which has now been removed.
2024-03-31 11:19:39 +13:00
Rob Bygrave cf2ec4b81d Modify TQRootBean making root final
Remove the unused setRoot() method
2024-03-31 10:02:41 +13:00
Rob Bygrave 7d1745ba53 Add final modifier to TQRootBean methods
As these are never expected to be overwritten
2024-03-31 09:57:42 +13:00
Rob BygraveandGitHub 7adbdbf532 feat: For SpringJdbcTransactionManager support using CurrentTenantProvider (#3368)
Note that this only works for Multi-Tenant PARTITION mode where the TenantId is a column on the tables (rather than schema or database multi-tenancy mode).
2024-03-26 22:45:39 +13:00
Rob Bygrave 40d9c88e34 #1857 Add bind values capture for SlowQueryEvent / SlowQueryListener
- changes SqlQueryEvent into an interface.
- captures the bind values the slow query used and makes those available via SlowQueryEvent
- also makes the query label and profileLocation available via SlowQueryEvent
2024-03-25 22:52:04 +13:00
Rob Bygrave ab45ad34e0 Version 14.0.2 2024-03-21 22:07:12 +13:00
Rob Bygrave 24b514f851 Bump ebean-agent, no effective change 2024-03-21 21:55:02 +13:00
Rob BygraveandGitHub 582fec7ce4 For #3363 disable flushOnQuery before BeanPersistAdapter post-processors (#3367) 2024-03-21 21:51:10 +13:00
Rob BygraveandGitHub 0b11c541c8 fix: #3142 Cascading a OneToMany, set the relationship when the child isn't new or dirty (#3366)
That is, previously the relationship back from the child to the parent was being updated when the child bean was new or dirty BUT NOT in the case when the child bean was loaded and unchanged.

This fix includes that case updating the ManyToOne side of the relationship on the child when the save is cascaded.
2024-03-21 00:14:54 +13:00
Rob BygraveandGitHub 540ceab96f feat: Add query.setHint() to support sql hint as inline comment in select queries (#3365)
* feat: Add query.setHint() to support sql hint as inline comment in select queries

This is for ORM queries only.

An ORM query like:

    new QCustomer()
      .setHint("FirstRows")
      .select(QCustomer.Alias.id, QCustomer.Alias.name)
      .findList();

Produces SQL that includes the hint as an inline comment like:

  select /*+ FirstRows */ t0.id, t0.name
  from customer t0

* Fix test QOrderTest
2024-03-20 21:48:43 +13:00
Rob BygraveandGitHub 8b858a073a feat: Add includeLabelInSql configuration option to include query label as inline comment in generated sql select (#3362)
* feat: Add includeLabelInSql configuration option to include query label as inline comment in generated sql select

The generated select queries can look like:

select /* MyInnerTest.insert_and_find */ t0.id, ...

Using either query.setLabel() or profile location (which all query beans get by default). This means that tooling looking at sql in the database can more easily relate that sql back to application code.

* More tests for labels with includeLabelInSql=true

* Remove trim when using profileLocation label
2024-03-18 07:38:30 +13:00
Rob BygraveandGitHub 02dafc9838 Merge pull request #3360 from ebean-orm/feature/refactor-DbExpressionRequest
Refactor extract DbExpressionRequest interface for db platform specific expressions
2024-03-08 00:53:56 +13:00
Rob Bygrave 0816810d3b Refactor extract DbExpressionRequest interface for db platform specific expressions
This provides a simpler DbExpressionRequest interface for db platform specific expression adapters (rather than the SpiExpressionRequest which has more features that we don't wish to expose to those expression adapters.
2024-03-07 22:45:21 +13:00
Rob Bygrave 1f6e2ee844 No effective change - update test only ClusterTest, increase wait to 200ms 2024-03-07 20:59:41 +13:00
Rob Bygrave dc00765a98 Merge branch 'fix-for-db2' 2024-03-07 20:45:57 +13:00
Rob Bygrave a48b2f4ebe #3354 Use 4000 for DB Lob detection with distinct query
- Use 4000 to match the DB2 logic for considering a column a lob (for distinct etc)
- Rename distinctNoLobs -> platformDistinctNoLobs
- Rename  isDbLob() -> isLobForPlatform()
- Rename unselectLobs() -> unselectLobsForPlatform()
2024-03-07 20:45:40 +13:00
Rob BygraveandGitHub 837563544f Merge pull request #3357 from ebean-orm/feature/avaje-config-dependency-ContainerConfig
Modify ContainerConfig to remove the avaje-config dependency
2024-03-07 20:23:36 +13:00
Rob Bygrave fd199f406b Modify ContainerConfig to remove the avaje-config dependency
This actually reverts a change that was made in commit:
https://github.com/ebean-orm/ebean/commit/803f1d86428ce3b653276d72207e5a0009b57d16
2024-03-05 21:18:27 +13:00
Roland Praml a48e96c7e9 FIX broken test for DB2 2024-03-04 10:02:05 +01:00
Rob Bygrave 7ba6e5f67d Add howto-deploy-to-central.md 2024-03-03 22:08:47 +13:00
552 changed files with 29849 additions and 4528 deletions
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
</parent>
<artifactId>composites</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
</parent>
<name>ebean api</name>
@@ -155,4 +155,10 @@ public abstract class BeanFinder<I,T> {
return db().findNative(type, nativeSql);
}
/**
* Creates a query using the ORM query language.
*/
protected Query<T> query(String ormQuery) {
return db().createQuery(type, ormQuery);
}
}
+52
View File
@@ -741,6 +741,21 @@ public final class DB {
return getDefault().createUpdate(beanType, ormUpdate);
}
/**
* Create a named query.
* <p>
* For RawSql the named query is expected to be in ebean.xml.
*
* @param beanType The type of entity bean
* @param namedQuery The name of the query
* @param <T> The type of entity bean
* @return The query
*/
public static <T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery) {
return getDefault().createNamedQuery(beanType, namedQuery);
}
/**
* Create a query for a type of entity bean.
* <p>
@@ -760,6 +775,43 @@ public final class DB {
return getDefault().createQuery(beanType);
}
/**
* Parse the Ebean query language statement returning the query which can then
* be modified (add expressions, change order by clause, change maxRows, change
* fetch and select paths etc).
* <p>
* <h3>Example</h3>
* <pre>{@code
*
* // Find order additionally fetching the customer, details and details.product name.
*
* String eql = "fetch customer fetch details fetch details.product (name) where id = :orderId ";
*
* Query<Order> query = DB.createQuery(Order.class, eql);
* query.setParameter("orderId", 2);
*
* Order order = query.findOne();
*
* // This is the same as:
*
* Order order = DB.find(Order.class)
* .fetch("customer")
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findOne();
*
* }</pre>
*
* @param beanType The type of bean to fetch
* @param eql The Ebean query
* @param <T> The type of the entity bean
* @return The query with expressions defined as per the parsed query statement
*/
public static <T> Query<T> createQuery(Class<T> beanType, String eql) {
return getDefault().createQuery(beanType, eql);
}
/**
* Create a query for a type of entity bean.
* <p>
@@ -242,6 +242,18 @@ public interface Database {
*/
<T> UpdateQuery<T> update(Class<T> beanType);
/**
* Create a named query.
* <p>
* For RawSql the named query is expected to be in ebean.xml.
*
* @param beanType The type of entity bean
* @param namedQuery The name of the query
* @param <T> The type of entity bean
* @return The query
*/
<T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery);
/**
* Create a query for an entity bean and synonym for {@link #find(Class)}.
*
@@ -249,6 +261,41 @@ public interface Database {
*/
<T> Query<T> createQuery(Class<T> beanType);
/**
* Parse the Ebean query language statement returning the query which can then
* be modified (add expressions, change order by clause, change maxRows, change
* fetch and select paths etc).
* <p>
* <h3>Example</h3>
* <pre>{@code
*
* // Find order additionally fetching the customer, details and details.product name.
*
* String ormQuery = "fetch customer fetch details fetch details.product (name) where id = :orderId ";
*
* Query<Order> query = DB.createQuery(Order.class, ormQuery);
* query.setParameter("orderId", 2);
*
* Order order = query.findOne();
*
* // This is the same as:
*
* Order order = DB.find(Order.class)
* .fetch("customer")
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findOne();
*
* }</pre>
*
* @param beanType The type of bean to fetch
* @param ormQuery The Ebean ORM query
* @param <T> The type of the entity bean
* @return The query with expressions defined as per the parsed query statement
*/
<T> Query<T> createQuery(Class<T> beanType, String ormQuery);
/**
* Create a query for a type of entity bean.
* <p>
@@ -412,6 +459,18 @@ public interface Database {
*/
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
/**
* Create a named Query for DTO beans.
* <p>
* DTO beans are just normal bean like classes with public constructor(s) and setters.
* They do not need to be registered with DB before use.
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param namedQuery The name of the query
* @param <T> The type of the DTO bean.
*/
<T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery);
/**
* Look to execute a native sql query that does not return beans but instead
* returns SqlRow or direct access to ResultSet.
@@ -1326,6 +1385,109 @@ public interface Database {
*/
ScriptRunner script();
/**
* Return the Document store.
*/
DocumentStore docStore();
/**
* Publish a single bean given its type and id returning the resulting live bean.
* <p>
* The values are published from the draft to the live bean.
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean
* @param transaction the transaction the publish process should use (can be null)
*/
@Nullable
<T> T publish(Class<T> beanType, Object id, Transaction transaction);
/**
* Publish a single bean given its type and id returning the resulting live bean.
* This will use the current transaction or create one if required.
* <p>
* The values are published from the draft to the live bean.
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean
*/
@Nullable
<T> T publish(Class<T> beanType, Object id);
/**
* Publish the beans that match the query returning the resulting published beans.
* <p>
* The values are published from the draft beans to the live beans.
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
* @param transaction the transaction the publish process should use (can be null)
*/
<T> List<T> publish(Query<T> query, Transaction transaction);
/**
* Publish the beans that match the query returning the resulting published beans.
* This will use the current transaction or create one if required.
* <p>
* The values are published from the draft beans to the live beans.
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
*/
<T> List<T> publish(Query<T> query);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean to restore
* @param transaction the transaction the restore process should use (can be null)
*/
@Nullable
<T> T draftRestore(Class<T> beanType, Object id, Transaction transaction);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean to restore
*/
@Nullable
<T> T draftRestore(Class<T> beanType, Object id);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
* @param transaction the transaction the restore process should use (can be null)
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
*/
<T> List<T> draftRestore(Query<T> query);
/**
* Returns the set of properties/paths that are unknown (do not map to known properties or paths).
* <p>
@@ -13,6 +13,8 @@ import io.ebean.event.*;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import jakarta.persistence.EnumType;
import javax.sql.DataSource;
@@ -689,6 +691,38 @@ public interface DatabaseBuilder {
@Deprecated
DatabaseBuilder setChangeLogAsync(boolean changeLogAsync);
/**
* Set the ReadAuditLogger to use. If not set the default implementation is used
* which logs the read events in JSON format to a standard named SLF4J logger
* (which can be configured in say logback to log to a separate log file).
*/
default DatabaseBuilder readAuditLogger(ReadAuditLogger readAuditLogger) {
return setReadAuditLogger(readAuditLogger);
}
/**
* @deprecated migrate to {@link #readAuditLogger(ReadAuditLogger)}.
*/
@Deprecated
DatabaseBuilder setReadAuditLogger(ReadAuditLogger readAuditLogger);
/**
* Set the ReadAuditPrepare to use.
* <p>
* It is expected that an implementation is used that read user context information
* (user id, user ip address etc) and sets it on the ReadEvent bean before it is sent
* to the ReadAuditLogger.
*/
default DatabaseBuilder readAuditPrepare(ReadAuditPrepare readAuditPrepare) {
return setReadAuditPrepare(readAuditPrepare);
}
/**
* @deprecated migrate to {@link #readAuditPrepare(ReadAuditPrepare)}.
*/
@Deprecated
DatabaseBuilder setReadAuditPrepare(ReadAuditPrepare readAuditPrepare);
/**
* Set the configuration for profiling.
*/
@@ -971,6 +1005,16 @@ public interface DatabaseBuilder {
@Deprecated
DatabaseBuilder setAllQuotedIdentifiers(boolean allQuotedIdentifiers);
/**
* Set to true if this Database is Document store only instance (has no JDBC DB).
*/
DatabaseBuilder setDocStoreOnly(boolean docStoreOnly);
/**
* Set the configuration for the ElasticSearch integration.
*/
DatabaseBuilder setDocStoreConfig(DocStoreConfig docStoreConfig);
/**
* Set the constraint naming convention used in DDL generation.
*/
@@ -2165,6 +2209,15 @@ public interface DatabaseBuilder {
@Deprecated
DatabaseBuilder setLoadModuleInfo(boolean loadModuleInfo);
/**
* Set if generated SQL SELECT should include the query label as an
* inline SQL comment (to help reference back from the SQL to the code
* that executed the query.
*
* @param includeLabelInSql When true include a SQL inline comment in generated SELECT queries.
*/
DatabaseConfig includeLabelInSql(boolean includeLabelInSql);
/**
* Set the naming convention to apply to metrics names.
*/
@@ -2404,6 +2457,16 @@ public interface DatabaseBuilder {
*/
boolean isChangeLogAsync();
/**
* Return the ReadAuditLogger to use.
*/
ReadAuditLogger getReadAuditLogger();
/**
* Return the ReadAuditPrepare to use.
*/
ReadAuditPrepare getReadAuditPrepare();
/**
* Return the tenancy catalog provider.
*/
@@ -2542,6 +2605,16 @@ public interface DatabaseBuilder {
*/
boolean isAllQuotedIdentifiers();
/**
* Return true if this Database is a Document store only instance (has no JDBC DB).
*/
boolean isDocStoreOnly();
/**
* Return the configuration for the ElasticSearch integration.
*/
DocStoreConfig getDocStoreConfig();
/**
* Return the constraint naming convention used in DDL generation.
*/
@@ -3040,6 +3113,12 @@ public interface DatabaseBuilder {
*/
boolean isLoadModuleInfo();
/**
* Return true if generated sql select query should include an inline sql comment with the
* query label or profile location label.
*/
boolean isIncludeLabelInSql();
/**
* Return the naming convention to apply to metrics names.
*/
@@ -0,0 +1,94 @@
package io.ebean;
/**
* Bean holding the details to update the document store.
*/
public final class DocStoreQueueEntry {
/**
* Action to either update or delete a document from the index.
*/
public enum Action {
/**
* Action is to update a document in the doc store.
*/
INDEX(1),
/**
* Action is to delete a document from the doc store..
*/
DELETE(2),
/**
* An update is required based on a change to a nested/embedded object at a given path.
*/
NESTED(3);
int value;
Action(int value) {
this.value = value;
}
/**
* Return the value associated with this action type.
*/
public int getValue() {
return value;
}
}
private final Action type;
private final String queueId;
private final String path;
private final Object beanId;
/**
* Construct for an INDEX or DELETE action.
*/
public DocStoreQueueEntry(Action type, String queueId, Object beanId) {
this(type, queueId, null, beanId);
}
/**
* Construct for an NESTED/embedded path invalidation action.
*/
public DocStoreQueueEntry(Action type, String queueId, String path, Object beanId) {
this.type = type;
this.queueId = queueId;
this.path = path;
this.beanId = beanId;
}
/**
* Return the event type.
*/
public Action getType() {
return type;
}
/**
* Return the associate queueId.
*/
public String getQueueId() {
return queueId;
}
/**
* Return the path if this is a nested update.
*/
public String getPath() {
return path;
}
/**
* Return the bean id (which matches the document id).
*/
public Object getBeanId() {
return beanId;
}
}
@@ -0,0 +1,312 @@
package io.ebean;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import io.ebean.docstore.DocQueryContext;
import io.ebean.docstore.RawDoc;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Document storage operations.
*/
@NonNullApi
public interface DocumentStore {
/**
* Update the associated document store using the result of the query.
* <p>
* This will execute the query against the database creating a document for each
* bean graph and sending this to the document store.
* </p>
* <p>
* Note that the select and fetch paths of the query is set for you to match the
* document structure needed based on <code>@DocStore</code> and <code>@DocStoreEmbedded</code>
* so what this query requires is the predicates only.
* </p>
* <p>
* This query will be executed using findEach so it is safe to use a query
* that will fetch a lot of beans. The default bulkBatchSize is used.
* </p>
*
* @param query The query that selects object to send to the document store.
*/
<T> void indexByQuery(Query<T> query);
/**
* Update the associated document store index using the result of the query additionally specifying a
* bulkBatchSize to use for sending the messages to ElasticSearch.
*
* @param query The query that selects object to send to the document store.
* @param bulkBatchSize The batch size to use when bulk sending to the document store.
*/
<T> void indexByQuery(Query<T> query, int bulkBatchSize);
/**
* Update the document store for all beans of this type.
* <p>
* This is the same as indexByQuery where the query has no predicates and so fetches all rows.
* </p>
*/
void indexAll(Class<?> beanType);
/**
* Return the bean by fetching it's content from the document store.
* If the document is not found null is returned.
* <p>
* Typically this is called indirectly by findOne() on the query.
* </p>
* <pre>{@code
*
* Customer customer =
* database.find(Customer.class)
* .setUseDocStore(true)
* .setId(42)
* .findOne();
*
* }</pre>
*/
@Nullable
<T> T find(DocQueryContext<T> request);
/**
* Execute the find list query. This request is prepared to execute secondary queries.
* <p>
* Typically this is called indirectly by findList() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* List<Customer> newCustomers =
* database.find(Customer.class)
* .setUseDocStore(true)
* .where().eq("status, Customer.Status.NEW)
* .findList();
*
* }</pre>
*/
<T> List<T> findList(DocQueryContext<T> request);
/**
* Execute the query against the document store returning the paged list.
* <p>
* The query should have <code>firstRow</code> or <code>maxRows</code> set prior to calling this method.
* </p>
* <p>
* Typically this is called indirectly by findPagedList() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* PagedList<Customer> newCustomers =
* database.find(Customer.class)
* .setUseDocStore(true)
* .where().eq("status, Customer.Status.NEW)
* .setMaxRows(50)
* .findPagedList();
*
* }</pre>
*/
<T> PagedList<T> findPagedList(DocQueryContext<T> request);
/**
* Execute the query against the document store with the expectation of a large set of results
* that are processed in a scrolling resultSet fashion.
* <p>
* For example, with the ElasticSearch doc store this uses SCROLL.
* </p>
* <p>
* Typically this is called indirectly by findEach() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* database.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEach((Order order) -> {
* // process the bean ...
* });
*
* }</pre>
*/
<T> void findEach(DocQueryContext<T> query, Consumer<T> consumer);
/**
* Execute the query against the document store with the expectation of a large set of results
* that are processed in a scrolling resultSet fashion.
* <p>
* Unlike findEach() this provides the opportunity to stop iterating through the large query.
* </p>
* <p>
* For example, with the ElasticSearch doc store this uses SCROLL.
* </p>
* <p>
* Typically this is called indirectly by findEachWhile() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* database.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEachWhile(new Predicate<Order>() {
* @Override
* public void accept(Order bean) {
* // process the bean
*
* // return true to continue, false to stop
* // boolean shouldContinue = ...
* return shouldContinue;
* }
* });
*
* }</pre>
*/
<T> void findEachWhile(DocQueryContext<T> query, Predicate<T> consumer);
/**
* Find each processing raw documents.
*
* @param indexNameType The full index name and type
* @param rawQuery The query to execute
* @param consumer Consumer to process each document
*/
void findEach(String indexNameType, String rawQuery, Consumer<RawDoc> consumer);
/**
* Find each processing raw documents stopping when the predicate returns false.
*
* @param indexNameType The full index name and type
* @param rawQuery The query to execute
* @param consumer Consumer to process each document until false is returned
*/
void findEachWhile(String indexNameType, String rawQuery, Predicate<RawDoc> consumer);
/**
* Process the queue entries sending updates to the document store or queuing them for later processing.
*/
long process(List<DocStoreQueueEntry> queueEntries) throws IOException;
/**
* Drop the index from the document store (similar to DDL drop table).
* <pre>{@code
*
* DocumentStore documentStore = database.docStore();
*
* documentStore.dropIndex("product_copy");
*
* }</pre>
*/
void dropIndex(String indexName);
/**
* Create an index given a mapping file as a resource in the classPath (similar to DDL create table).
* <pre>{@code
*
* DocumentStore documentStore = database.docStore();
*
* // uses product_copy.mapping.json resource
* // ... to define mappings for the index
*
* documentStore.createIndex("product_copy", null);
*
* }</pre>
*
* @param indexName the name of the new index
* @param alias the alias of the index
*/
void createIndex(String indexName, String alias);
/**
* Modify the settings on an index.
* <p>
* For example, this can be used be used to set elasticSearch refresh_interval
* on an index before a bulk update.
* </p>
* <pre>{@code
*
* // refresh_interval -1 ... disable refresh while bulk loading
*
* Map<String,Object> settings = new LinkedHashMap<>();
* settings.put("refresh_interval", "-1");
*
* documentStore.indexSettings("product", settings);
*
* }</pre>
* <pre>{@code
*
* // refresh_interval 1s ... restore after bulk loading
*
* Map<String,Object> settings = new LinkedHashMap<>();
* settings.put("refresh_interval", "1s");
*
* documentStore.indexSettings("product", settings);
*
* }</pre>
*
* @param indexName the name of the index to update settings on
* @param settings the settings to set on the index
*/
void indexSettings(String indexName, Map<String, Object> settings);
/**
* Copy the index to a new index.
* <p>
* This copy process does not use the database but instead will copy from the source index to a destination index.
* </p>
* <pre>{@code
*
* long copyCount = documentStore.copyIndex(Product.class, "product_copy");
*
* }</pre>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex);
/**
* Copy entries from an index to a new index but limiting to documents that have been
* modified since the sinceEpochMillis time.
* <p>
* To support this the document needs to have a <code>@WhenModified</code> property.
* </p>
* <pre>{@code
*
* long copyCount = documentStore.copyIndex(Product.class, "product_copy", sinceMillis);
*
* }</pre>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex, long sinceEpochMillis);
/**
* Copy from a source index to a new index taking only the documents
* matching the given query.
* <pre>{@code
*
* // predicates to select the source documents to copy
* Query<Product> query = database.find(Product.class)
* .where()
* .ge("whenModified", new Timestamp(since))
* .ge("name", "A")
* .lt("name", "D")
* .query();
*
* // copy from the source index to "product_copy" index
* long copyCount = documentStore.copyIndex(query, "product_copy", 1000);
*
* }</pre>
*
* @param query The query to select the source documents to copy
* @param newIndex The target index to copy the documents to
* @param bulkBatchSize The ElasticSearch bulk batch size, if 0 uses the default.
* @return The number of documents copied to the new index.
*/
long copyIndex(Query<?> query, String newIndex, int bulkBatchSize);
}
@@ -1,5 +1,7 @@
package io.ebean;
import io.ebean.search.*;
import java.util.Collection;
import java.util.Map;
@@ -623,6 +625,31 @@ public interface ExpressionFactory {
*/
Expression raw(String raw);
/**
* Create a Text Match expression (currently doc store/Elastic only).
*/
Expression textMatch(String propertyName, String search, Match options);
/**
* Create a Text Multi match expression (currently doc store/Elastic only).
*/
Expression textMultiMatch(String query, MultiMatch options);
/**
* Create a text simple query expression (currently doc store/Elastic only).
*/
Expression textSimple(String search, TextSimple options);
/**
* Create a text query string expression (currently doc store/Elastic only).
*/
Expression textQueryString(String search, TextQueryString options);
/**
* Create a text common terms expression (currently doc store/Elastic only).
*/
Expression textCommonTerms(String search, TextCommonTerms options);
/**
* And - join two expressions with a logical and.
*/
@@ -666,4 +693,12 @@ public interface ExpressionFactory {
*/
<T> Junction<T> junction(Junction.Type type, Query<T> query, ExpressionList<T> parent);
/**
* Add the expressions to the given expression list.
*
* @param where The expression list to add the expressions to
* @param expressions The expressions that are parsed
* @param params Bind parameters to match ? or ?1 bind positions.
*/
<T> void where(ExpressionList<T> where, String expressions, Object[] params);
}
@@ -2,6 +2,7 @@ package io.ebean;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import io.ebean.search.*;
import jakarta.persistence.NonUniqueResultException;
import java.sql.Connection;
@@ -105,6 +106,11 @@ public interface ExpressionList<T> {
*/
Query<T> asOf(Timestamp asOf);
/**
* Execute the query against the draft set of tables.
*/
Query<T> asDraft();
/**
* Convert the query to a DTO bean query.
* <p>
@@ -216,6 +222,8 @@ public interface ExpressionList<T> {
int delete();
/**
* @deprecated migrate to {@link #usingTransaction(Transaction)} then delete().
* <p>
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
@@ -225,6 +233,7 @@ public interface ExpressionList<T> {
*
* @return the number of rows that were deleted.
*/
@Deprecated(forRemoval = true, since = "13.1.0")
int delete(Transaction transaction);
/**
@@ -236,11 +245,14 @@ public interface ExpressionList<T> {
int update();
/**
* @deprecated migrate to {@link #usingTransaction(Transaction)} then update().
* <p>
* Execute as a update query with the given transaction.
*
* @return the number of rows that were updated.
* @see UpdateQuery
*/
@Deprecated(forRemoval = true, since = "13.1.0")
int update(Transaction transaction);
/**
@@ -501,6 +513,28 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> filterMany(String manyProperty);
/**
* @deprecated for removal - migrate to {@link #filterManyRaw(String, String, Object...)}.
* <p>
* Add filter expressions to the many property.
*
* <pre>{@code
*
* DB.find(Customer.class)
* .where()
* .eq("name", "Rob")
* .filterMany("orders", "status = ?", Status.NEW)
* .findList();
*
* }</pre>
*
* @param manyProperty The many property
* @param expressions Filter expressions with and, or and ? or ?1 type bind parameters
* @param params Bind parameters used in the expressions
*/
@Deprecated(forRemoval = true)
ExpressionList<T> filterMany(String manyProperty, String expressions, Object... params);
/**
* Add filter expressions for the many path. The expressions can include SQL functions if
* desired and the property names are translated to column names.
@@ -552,6 +586,19 @@ public interface ExpressionList<T> {
*/
Query<T> setDistinct(boolean distinct);
/**
* Set the index(es) to search for a document store which uses partitions.
* <p>
* For example, when executing a query against ElasticSearch with daily indexes we can
* explicitly specify the indexes to search against.
* </p>
*
* @param indexName The index or indexes to search against
* @return This query
* @see Query#setDocIndexName(String)
*/
Query<T> setDocIndexName(String indexName);
/**
* Set the first row to fetch.
*
@@ -635,6 +682,14 @@ public interface ExpressionList<T> {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set to true if this query should execute against the doc store.
* <p>
* When setting this you may also consider disabling lazy loading.
* </p>
*/
Query<T> setUseDocStore(boolean useDocsStore);
/**
* Set true if you want to disable lazy loading.
* <p>
@@ -643,6 +698,16 @@ public interface ExpressionList<T> {
*/
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
/**
* Disable read auditing for this query.
* <p>
* This is intended to be used when the query is not a user initiated query and instead
* part of the internal processing in an application to load a cache or document store etc.
* In these cases we don't want the query to be part of read auditing.
* </p>
*/
Query<T> setDisableReadAuditing();
/**
* Set a label on the query (to help identify query execution statistics).
*/
@@ -662,6 +727,14 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> where();
/**
* Add the expressions to this expression list.
*
* @param expressions The expressions that are parsed and added to this expression list
* @param params Bind parameters to match ? or ?1 bind positions.
*/
ExpressionList<T> where(String expressions, Object... params);
/**
* Path exists - for the given path in a JSON document.
* <pre>{@code
@@ -1556,6 +1629,47 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> rawOrEmpty(String raw, Collection<?> values);
/**
* Add a match expression.
*
* @param propertyName The property name for the match
* @param search The search value
*/
ExpressionList<T> match(String propertyName, String search);
/**
* Add a match expression with options.
*
* @param propertyName The property name for the match
* @param search The search value
*/
ExpressionList<T> match(String propertyName, String search, Match options);
/**
* Add a multi-match expression.
*/
ExpressionList<T> multiMatch(String search, String... properties);
/**
* Add a multi-match expression using options.
*/
ExpressionList<T> multiMatch(String search, MultiMatch options);
/**
* Add a simple query string expression.
*/
ExpressionList<T> textSimple(String search, TextSimple options);
/**
* Add a query string expression.
*/
ExpressionList<T> textQueryString(String search, TextQueryString options);
/**
* Add common terms expression.
*/
ExpressionList<T> textCommonTerms(String search, TextCommonTerms options);
/**
* And - join two expressions with a logical and.
*/
@@ -1698,6 +1812,42 @@ public interface ExpressionList<T> {
*/
Junction<T> disjunction();
/**
* Start a list of expressions that will be joined by MUST.
* <p>
* This automatically makes the query a useDocStore(true) query that
* will execute against the document store (ElasticSearch etc).
* </p>
* <p>
* This is logically similar to and().
* </p>
*/
Junction<T> must();
/**
* Start a list of expressions that will be joined by SHOULD.
* <p>
* This automatically makes the query a useDocStore(true) query that
* will execute against the document store (ElasticSearch etc).
* </p>
* <p>
* This is logically similar to or().
* </p>
*/
Junction<T> should();
/**
* Start a list of expressions that will be joined by MUST NOT.
* <p>
* This automatically makes the query a useDocStore(true) query that
* will execute against the document store (ElasticSearch etc).
* </p>
* <p>
* This is logically similar to not().
* </p>
*/
Junction<T> mustNot();
/**
* End a junction returning the parent expression list.
* <p>
@@ -213,4 +213,11 @@ public class Finder<I, T> {
return db().findNative(type, nativeSql);
}
/**
* Creates a query using the ORM query language.
*/
public Query<T> query(String ormQuery) {
return db().createQuery(type, ormQuery);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,975 @@
package io.ebean;
import io.avaje.lang.Nullable;
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;
/**
* Build and execute an ORM query.
*
* @param <SELF> The type of the builder
* @param <T> The entity bean type
*/
public interface QueryBuilder<SELF, T> extends QueryBuilderProjection<SELF, T> {
/**
* Set root table alias.
*/
SELF alias(String alias);
/**
* 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
*/
SELF alsoIf(BooleanSupplier predicate, Consumer<SELF> apply);
/**
* Perform an 'As of' query using history tables to return the object graph
* as of a time in the past.
* <p>
* To perform this query the DB must have underlying history tables.
*
* @param asOf the date time in the past at which you want to view the data
*/
SELF asOf(Timestamp asOf);
/**
* Execute the query against the draft set of tables.
*/
SELF asDraft();
/**
* Convert the query to a DTO bean query.
* <p>
* We effectively use the underlying ORM query to build the SQL and then execute
* and map it into DTO beans.
*/
<D> DtoQuery<D> asDto(Class<D> dtoClass);
/**
* Convert the query to a UpdateQuery.
* <p>
* Typically this is used with query beans to covert a query bean
* query into an UpdateQuery like the examples below.
* </p>
*
* <pre>{@code
*
* int rowsUpdated = new QCustomer()
* .name.startsWith("Rob")
* .asUpdate()
* .set("active", false)
* .update();;
*
* }</pre>
*
* <pre>{@code
*
* int rowsUpdated = new QContact()
* .notes.note.startsWith("Make Inactive")
* .email.endsWith("@foo.com")
* .customer.id.equalTo(42)
* .asUpdate()
* .set("inactive", true)
* .setRaw("email = lower(email)")
* .update();
*
* }</pre>
*/
UpdateQuery<T> asUpdate();
/**
* Return a copy of the query.
* <p>
* This is so that you can use a Query as a "prototype" for creating other
* query instances. You could create a Query with various where expressions
* and use that as a "prototype" - using this copy() method to create a new
* instance that you can then add other expressions then execute.
* </p>
*/
SELF copy();
/**
* Execute the query using the given transaction.
*/
SELF usingTransaction(Transaction transaction);
/**
* Execute the query using the given connection.
*/
SELF usingConnection(Connection connection);
/**
* Execute the query using the given database.
*/
SELF 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.
*/
SELF usingMaster();
/**
* Set the base table to use for this query.
* <p>
* Typically this is used when a table has partitioning and we wish to specify a specific
* partition/table to query against.
* </p>
* <pre>{@code
*
* QOrder()
* .setBaseTable("order_2019_05")
* .status.equalTo(Status.NEW)
* .findList();
*
* }</pre>
*/
SELF setBaseTable(String baseTable);
/**
* Specify the PersistenceContextScope to use for this query.
* <p>
* When this is not set the 'default' configured on {@link io.ebean.config.DatabaseConfig#setPersistenceContextScope(PersistenceContextScope)}
* is used - this value defaults to {@link PersistenceContextScope#TRANSACTION}.
* <p>
* Note that the same persistence Context is used for subsequent lazy loading and query join queries.
* <p>
* Note that #findEach uses a 'per object graph' PersistenceContext so this scope is ignored for
* queries executed as #findIterate, #findEach, #findEachWhile.
*
* @param scope The scope to use for this query and subsequent lazy loading.
*/
SELF setPersistenceContextScope(PersistenceContextScope scope);
/**
* Explicitly specify whether to use AutoTune for this query.
* <p>
* If you do not call this method on a query the "Implicit AutoTune mode" is
* used to determine if AutoTune should be used for a given query.
* <p>
* AutoTune can add additional fetch paths to the query and specify which
* properties are included for each path. If you have explicitly defined some
* fetch paths AutoTune will not remove them.
*/
SELF setAutoTune(boolean autoTune);
/**
* Execute the query allowing properties with invalid JSON to be collected and not fail the query.
* <pre>{@code
*
* // fetch a bean with JSON content
* EBasicJsonList bean= DB.find(EBasicJsonList.class)
* .setId(42)
* .setAllowLoadErrors() // collect errors into bean state if we have invalid JSON
* .findOne();
*
*
* // get the invalid JSON errors from the bean state
* Map<String, Exception> errors = server().getBeanState(bean).getLoadErrors();
*
* // If this map is not empty tell we have invalid JSON
* // and should try and fix the JSON content or inform the user
*
* }</pre>
*/
SELF setAllowLoadErrors();
/**
* Set the default lazy loading batch size to use.
* <p>
* When lazy loading is invoked on beans loaded by this query then this sets the
* batch size used to load those beans.
*
* @param lazyLoadBatchSize the number of beans to lazy load in a single batch
*/
SELF setLazyLoadBatchSize(int lazyLoadBatchSize);
/**
* Set a label on the query.
* <p>
* This label can be used to help identify query performance metrics but we can also use
* profile location enhancement on Finders so for some that would be a better option.
*/
SELF setLabel(String label);
/**
* Set a SQL query hint.
* <p>
* This results in an inline comment that immediately follows
* after the select keyword in the form: {@code /*+ hint *\/ }
*/
SELF setHint(String hint);
/**
* Set the index(es) to search for a document store which uses partitions.
* <p>
* For example, when executing a query against ElasticSearch with daily indexes we can
* explicitly specify the indexes to search against.
* </p>
* <pre>{@code
*
* // explicitly specify the indexes to search
* query.setDocIndexName("logstash-2016.11.5,logstash-2016.11.6")
*
* // search today's index
* query.setDocIndexName("$today")
*
* // search the last 3 days
* query.setDocIndexName("$last-3")
*
* }</pre>
* <p>
* If the indexName is specified with ${daily} e.g. "logstash-${daily}" ... then we can use
* $today and $last-x as the search docIndexName like the examples below.
* </p>
* <pre>{@code
*
* // search today's index
* query.setDocIndexName("$today")
*
* // search the last 3 days
* query.setDocIndexName("$last-3")
*
* }</pre>
*
* @param indexName The index or indexes to search against
* @return This query
*/
SELF setDocIndexName(String indexName);
/**
* Execute the query including soft deleted rows.
* <p>
* This means that Ebean will not add any predicates to the query for filtering out
* soft deleted rows. You can still add your own predicates for the deleted properties
* and effectively you have full control over the query to include or exclude soft deleted
* rows as needed for a given use case.
*/
SELF setIncludeSoftDeletes();
/**
* Disable read auditing for this query.
* <p>
* This is intended to be used when the query is not a user initiated query and instead
* part of the internal processing in an application to load a cache or document store etc.
* In these cases we don't want the query to be part of read auditing.
*/
SELF setDisableReadAuditing();
/**
* Set true if you want to disable lazy loading.
* <p>
* That is, once the object graph is returned further lazy loading is disabled.
*/
SELF setDisableLazyLoading(boolean disableLazyLoading);
/**
* Set whether this query uses DISTINCT.
*/
SELF setDistinct(boolean distinct);
/**
* Restrict the query to only return subtypes of the given inherit type.
* <pre>{@code
*
* List<Animal> animals =
* new QAnimal()
* .name.startsWith("Fluffy")
* .setInheritType(Cat.class)
* .findList();
*
* }</pre>
*/
SELF setInheritType(Class<? extends T> type);
/**
* Set the first row to return for this query.
*
* @param firstRow the first row to include in the query result.
*/
SELF setFirstRow(int firstRow);
/**
* Set the maximum number of rows to return in the query.
*
* @param maxRows the maximum number of rows to return in the query.
*/
SELF setMaxRows(int maxRows);
/**
* Set RawSql to use for this query.
*/
SELF setRawSql(RawSql rawSql);
/**
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
*
* <pre>{@code
*
* List<CountedValue<Order.Status>> orderStatusCount =
*
* DB.find(Order.class)
* .select("status")
* .where()
* .gt("orderDate", LocalDate.now().minusMonths(3))
*
* // fetch as single attribute with a COUNT
* .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
* .findSingleAttributeList();
*
* for (CountedValue<Order.Status> entry : orderStatusCount) {
* System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
* }
*
* // produces
*
* count:3 orderStatus:NEW
* count:1 orderStatus:SHIPPED
* count:1 orderStatus:COMPLETE
*
* }</pre>
*/
SELF setCountDistinct(CountDistinctOrder orderBy);
/**
* Set the property to use as keys for a map.
* <p>
* If no property is set then the id property is used.
* </p>
* <pre>{@code
*
* // Assuming sku is unique for products...
*
* Map<String,Product> productMap = DB.find(Product.class)
* .setMapKey("sku") // sku map keys...
* .findMap();
*
* }</pre>
*
* @param mapKey the property to use as keys for a map.
*/
SELF setMapKey(String mapKey);
/**
* Set to true if this query should execute against the doc store.
* <p>
* When setting this you may also consider disabling lazy loading.
*/
SELF setUseDocStore(boolean useDocStore);
/**
* When set to true when you want the returned beans to be read only.
*/
SELF setReadOnly(boolean readOnly);
/**
* Set a timeout on this query.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs the query timeout limit in seconds. Zero means there is no limit.
*/
SELF setTimeout(int secs);
/**
* A hint which for JDBC translates to the Statement.fetchSize().
* <p>
* Gives the JDBC driver a hint as to the number of rows that should be
* fetched from the database when more rows are needed for ResultSet.
* </p>
* <p>
* Note that internally findEach and findEachWhile will set the fetch size
* if it has not already as these queries expect to process a lot of rows.
* If we didn't then Postgres and MySql for example would eagerly pull back
* all the row data and potentially consume a lot of memory in the process.
* </p>
* <p>
* As findEach and findEachWhile automatically set the fetch size we don't have
* to do so generally but we might still wish to for tuning a specific use case.
* </p>
*/
SELF setBufferFetchSizeHint(int fetchSize);
/**
* Set the mode to use the bean cache when executing this query.
* <p>
* By default, "find by id" and "find by natural key" will use the bean cache
* when bean caching is enabled. Setting this to false means that the query
* will not use the bean cache and instead hit the database.
* <p>
* By default, findList() with natural keys will not use the bean cache. In that
* case we need to explicitly use the bean cache.
*/
SELF setBeanCacheMode(CacheMode beanCacheMode);
/**
* Set the {@link CacheMode} to use the query for executing this query.
*/
SELF setUseQueryCache(CacheMode cacheMode);
/**
* Set this to false to not use the bean cache.
* <p>
* This method is now superseded by {@link #setBeanCacheMode(CacheMode)}
* which provides more explicit options controlled bean cache use.
* <p>
* This method is likely to be deprecated in the future with migration
* over to setUseBeanCache().
*/
default SELF setUseCache(boolean useCache) {
return setBeanCacheMode(useCache ? CacheMode.ON : CacheMode.OFF);
}
/**
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
*/
default SELF setUseQueryCache(boolean enabled) {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set the order by clause replacing the existing order by clause if there is
* one.
* <p>
* This follows SQL syntax using commas between each property with the
* optional asc and desc keywords representing ascending and descending order
* respectively.
*/
SELF orderBy(String orderByClause);
/**
* Set an OrderBy object to replace any existing OrderBy clause.
*/
SELF setOrderBy(OrderBy<T> orderBy);
/**
* Controls, if paginated queries should always append an 'order by id' statement at the end to
* guarantee a deterministic sort result. This may affect performance.
* If this is not enabled, and an orderBy is set on the query, it's up to the programmer that
* this query provides a deterministic result.
*/
SELF orderById(boolean orderById);
/**
* Execute the query with the given lock type and WAIT.
* <p>
* Note that <code>forUpdate()</code> is the same as
* <code>withLock(LockType.UPDATE)</code>.
* <p>
* Provides us with the ability to explicitly use Postgres
* SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks.
*/
SELF withLock(Query.LockType lockType);
/**
* Execute the query with the given lock type and lock wait.
* <p>
* Note that <code>forUpdateNoWait()</code> is the same as
* <code>withLock(LockType.UPDATE, LockWait.NOWAIT)</code>.
* <p>
* Provides us with the ability to explicitly use Postgres
* SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks.
*/
SELF withLock(Query.LockType lockType, Query.LockWait lockWait);
/**
* Execute using "for update" clause which results in the DB locking the record.
* <p>
* The same as <code>withLock(LockType.UPDATE, LockWait.WAIT)</code>.
*/
SELF forUpdate();
/**
* Execute using "for update" clause with "no wait" option.
* <p>
* This is typically a Postgres and Oracle only option at this stage.
* <p>
* The same as <code>withLock(LockType.UPDATE, LockWait.NOWAIT)</code>.
*/
SELF forUpdateNoWait();
/**
* Execute using "for update" clause with "skip locked" option.
* <p>
* This is typically a Postgres and Oracle only option at this stage.
* <p>
* The same as <code>withLock(LockType.UPDATE, LockWait.SKIPLOCKED)</code>.
*/
SELF forUpdateSkipLocked();
/**
* Returns the set of properties or paths that are unknown (do not map to known properties or paths).
* <p>
* Validate the query checking the where and orderBy expression paths to confirm if
* they represent valid properties or paths for the given bean type.
*/
Set<String> validate();
/**
* Return the sql that was generated for executing this query.
* <p>
* This is only available after the query has been executed and provided only
* for informational purposes.
*/
String getGeneratedSql();
/**
* Return the type of beans being queried.
*/
Class<T> getBeanType();
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
*
* @return the number of beans/rows that were deleted.
*/
int delete();
/**
* Execute the query returning true if a row is found.
* <p>
* The query is executed using max rows of 1 and will only select the id property.
* This method is really just a convenient way to optimise a query to perform a
* 'does a row exist in the db' check.
*
* <h2>Example using a query bean:</h2>
* <pre>{@code
*
* boolean userExists =
* new QContact()
* .email.equalTo("rob@foo.com")
* .exists();
*
* }</pre>
*
* <h2>Example:</h2>
* <pre>{@code
*
* boolean userExists = query()
* .where().eq("email", "rob@foo.com")
* .exists();
*
* }</pre>
*
* @return True if the query finds a matching row in the database
*/
boolean exists();
/**
* Execute the query returning either a single bean or null (if no matching
* bean is found).
* <p>
* If more than 1 row is found for this query then a PersistenceException is
* thrown.
* <p>
* This is useful when your predicates dictate that your query should only
* return 0 or 1 results.
* <p>
* <pre>{@code
*
* // assuming the sku of products is unique...
* Product product =
* new QProduct()
* .sku.equalTo("aa113")
* .findOne();
* ...
* }</pre>
* <p>
* It is also useful with finding objects by their id when you want to specify
* further join information to optimise the query.
* <p>
* <pre>{@code
*
* // Fetch order 42 and additionally fetch join its order details...
* Order order =
* new QOrder()
* .fetch("details") // eagerly load the order details
* .id.equalTo(42)
* .findOne();
*
* // the order details were eagerly loaded
* List<OrderDetail> details = order.getDetails();
* ...
* }</pre>
*/
@Nullable
T findOne();
/**
* Execute the query returning an optional bean.
*/
Optional<T> findOneOrEmpty();
/**
* Execute the query returning the list of objects.
* <p>
* This query will execute against the EbeanServer that was used to create it.
* <p>
* <pre>{@code
*
* List<Customer> customers =
* new QCustomer()
* .name.ilike("rob%")
* .findList();
*
* }</pre>
*
* @see Query#findList()
*/
List<T> findList();
/**
* Execute the query returning the result as a Stream.
* <p>
* Note that this can support very large queries iterating
* any number of results. To do so internally it can use
* multiple persistence contexts.
* </p>
* <pre>{@code
*
* // use try with resources to ensure Stream is closed
*
* try (Stream<Customer> stream = query.findStream()) {
* stream
* .map(...)
* .collect(...);
* }
*
* }</pre>
*/
Stream<T> findStream();
/**
* Execute the query returning the set of objects.
* <p>
* This query will execute against the EbeanServer that was used to create it.
* <p>
* <pre>{@code
*
* Set<Customer> customers =
* new QCustomer()
* .name.ilike("rob%")
* .findSet();
*
* }</pre>
*
* @see Query#findSet()
*/
Set<T> findSet();
/**
* Execute the query returning the list of Id's.
* <p>
* This query will execute against the EbeanServer that was used to create it.
*
* @see Query#findIds()
*/
<A> List<A> findIds();
/**
* Execute the query returning a map of the objects.
* <p>
* This query will execute against the EbeanServer that was used to create it.
* <p>
* You can use setMapKey() or asMapKey() to specify the property to be used as keys
* on the map. If one is not specified then the id property is used.
* <p>
* <pre>{@code
*
* Map<String, Product> map =
* new QProduct()
* .sku.asMapKey()
* .findMap();
*
* }</pre>
*
* @see Query#findMap()
*/
<K> Map<K, T> findMap();
/**
* Execute the query iterating over the results.
* <p>
* Note that findIterate (and findEach and findEachWhile) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* <p>
* Remember that with {@link QueryIterator} you must call {@link QueryIterator#close()}
* when you have finished iterating the results (typically in a finally block).
* <p>
* findEach() and findEachWhile() are preferred to findIterate() as they ensure
* the jdbc statement and resultSet are closed at the end of the iteration.
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
* <pre>{@code
*
* Query<Customer> query =
* new QCustomer()
* .status.equalTo(Customer.Status.NEW)
* .orderBy()
* id.asc()
* .query();
*
* try (QueryIterator<Customer> it = query.findIterate()) {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer ...
* }
* }
*
* }</pre>
*/
QueryIterator<T> findIterate();
/**
* Execute the query returning a list of values for a single property.
* <p>
* <h3>Example</h3>
* <pre>{@code
*
* List<String> names =
* new QCustomer()
* .setDistinct(true)
* .select(name)
* .findSingleAttributeList();
*
* }</pre>
*
* @return the list of values for the selected property
*/
<A> List<A> findSingleAttributeList();
/**
* Execute the query returning a single value or null for a single property.
* <p>
* <h3>Example</h3>
* <pre>{@code
*
* LocalDate maxDate =
* new QCustomer()
* .select("max(startDate)")
* .findSingleAttribute();
*
* }</pre>
*
* @return a single value or null for the selected property
*/
@Nullable
<A> A findSingleAttribute();
/**
* Execute the query returning a single optional attribute value.
* <p>
* <h3>Example</h3>
* <pre>{@code
*
* Optional<String> maybeName =
* new QCustomer()
* .select(name)
* .id.eq(42)
* .status.eq(NEW)
* .findSingleAttributeOrEmpty();
*
* }</pre>
*
* @return an optional value for the selected property
*/
<A> Optional<A> findSingleAttributeOrEmpty();
/**
* Execute the query returning a hashset of values for a single property.
*/
<A> Set<A> findSingleAttributeSet();
/**
* Execute the query processing the beans one at a time.
* <p>
* This method is appropriate to process very large query results as the
* beans are consumed one at a time and do not need to be held in memory
* (unlike #findList #findSet etc)
* <p>
* Note that internally Ebean can inform the JDBC driver that it is expecting larger
* resultSet and specifically for MySQL this hint is required to stop it's JDBC driver
* from buffering the entire resultSet. As such, for smaller resultSets findList() is
* generally preferable.
* <p>
* Compared with #findEachWhile this will always process all the beans where as
* #findEachWhile provides a way to stop processing the query result early before
* all the beans have been read.
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the Consumer interface which is better suited to use with closures.
*
* <pre>{@code
*
* new QCustomer()
* .status.equalTo(Status.NEW)
* .orderBy().id.asc()
* .findEach((Customer customer) -> {
*
* // do something with customer
* System.out.println("-- visit " + customer);
* });
*
* }</pre>
*
* @param consumer the consumer used to process the queried beans.
*/
void findEach(Consumer<T> consumer);
/**
* Execute findEach streaming query batching the results for consuming.
* <p>
* This query execution will stream the results and is suited to consuming
* large numbers of results from the database.
* <p>
* Typically, we use this batch consumer when we want to do further processing on
* the beans and want to do that processing in batch form, for example - 100 at
* a time.
*
* @param batch The number of beans processed in the batch
* @param consumer Process the batch of beans
*/
void findEach(int batch, Consumer<List<T>> consumer);
/**
* Execute the query using callbacks to a visitor to process the resulting
* beans one at a time.
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the Predicate interface which is better suited to use with closures.
*
* <pre>{@code
*
* new QCustomer()
* .status.equalTo(Status.NEW)
* .orderBy().id.asc()
* .findEachWhile((Customer customer) -> {
*
* // do something with customer
* System.out.println("-- visit " + customer);
*
* // return true to continue processing or false to stop
* return (customer.getId() < 40);
* });
*
* }</pre>
*
* @param consumer the consumer used to process the queried beans.
*/
void findEachWhile(Predicate<T> consumer);
/**
* Return versions of a @History entity bean.
* <p>
* Generally this query is expected to be a find by id or unique predicates query.
* It will execute the query against the history returning the versions of the bean.
*/
List<Version<T>> findVersions();
/**
* Return versions of a @History entity bean between a start and end timestamp.
* <p>
* Generally this query is expected to be a find by id or unique predicates query.
* It will execute the query against the history returning the versions of the bean.
*/
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
/**
* Return the count of entities this query should return.
* <p>
* This is the number of 'top level' or 'root level' entities.
*/
int findCount();
/**
* Execute find row count query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
*
* @return a Future object for the row count query
*/
FutureRowCount<T> findFutureCount();
/**
* Execute find Id's query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
*
* @return a Future object for the list of Id's
*/
FutureIds<T> findFutureIds();
/**
* Execute find list query in a background thread.
* <p>
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
*
* @return a Future object for the list result of the query
*/
FutureList<T> findFutureList();
/**
* Return a PagedList for this query using firstRow and maxRows.
* <p>
* The benefit of using this over findList() is that it provides functionality to get the
* total row count etc.
* <p>
* If maxRows is not set on the query prior to calling findPagedList() then a
* PersistenceException is thrown.
* <p>
* <pre>{@code
*
* PagedList<Order> pagedList =
* new QOrder()
* .setFirstRow(50)
* .setMaxRows(20)
* .findPagedList();
*
* // fetch the total row count in the background
* pagedList.loadRowCount();
*
* List<Order> orders = pagedList.getList();
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* @return The PagedList
*/
PagedList<T> findPagedList();
}
@@ -0,0 +1,242 @@
package io.ebean;
/**
* Builder for ORM Query projection (the select and fetch part).
*
* @param <SELF> The builder type
* @param <T> The entity bean type
*/
public interface QueryBuilderProjection<SELF, T> {
/**
* Apply the path properties replacing the select and fetch clauses.
* <p>
* This is typically used when the FetchPath is applied to both the query and the JSON output.
*/
SELF apply(FetchPath fetchPath);
/**
* Specify the properties to fetch on the root level entity bean in comma delimited format.
* <p>
* The Id property is automatically included in the properties to fetch unless setDistinct(true)
* is set on the query.
* </p>
* <p>
* Use {@link #fetch(String, String)} to specify specific properties to fetch
* on other non-root level paths of the object graph.
* </p>
* <pre>{@code
*
* List<Customer> customers = DB.find(Customer.class)
* // Only fetch the customer id, name and status.
* // This is described as a "Partial Object"
* .select("name, status")
* .where.ilike("name", "rob%")
* .findList();
*
* }</pre>
*
* @param fetchProperties the properties to fetch for this bean (* = all properties).
*/
SELF select(String fetchProperties);
/**
* Apply the fetchGroup which defines what part of the object graph to load.
*/
SELF select(FetchGroup<T> fetchGroup);
/**
* Specify a path to fetch eagerly including specific properties.
* <p>
* Ebean will endeavour to fetch this path using a SQL join. If Ebean determines that it can
* not use a SQL join (due to maxRows or because it would result in a cartesian product) Ebean
* will automatically convert this fetch query into a "query join" - i.e. use fetchQuery().
* </p>
* <pre>{@code
*
* // query orders...
* List<Order> orders = DB.find(Order.class)
* // fetch the customer...
* // ... getting the customers name and phone number
* .fetch("customer", "name, phoneNumber")
*
* // ... also fetch the customers billing address (* = all properties)
* .fetch("customer.billingAddress", "*")
* .findList();
* }</pre>
* <p>
* If columns is null or "*" then all columns/properties for that path are fetched.
* </p>
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers = DB.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email")
* .findList();
*
* }</pre>
*
* @param path the property path we wish to fetch eagerly.
* @param fetchProperties properties of the associated bean that you want to include in the
* fetch (* means all properties, null also means all properties).
*/
SELF fetch(String path, String fetchProperties);
/**
* Fetch the path and properties using a "query join" (separate SQL query).
* <p>
* This is the same as:
* </p>
* <pre>{@code
*
* fetch(path, fetchProperties, FetchConfig.ofQuery())
*
* }</pre>
* <p>
* This would be used instead of a fetch() when we use a separate SQL query to fetch this
* part of the object graph rather than a SQL join.
* <p>
* We might typically get a performance benefit when the path to fetch is a OneToMany
* or ManyToMany, the 'width' of the 'root bean' is wide and the cardinality of the many
* is high.
*
* @param path the property path we wish to fetch eagerly.
* @param fetchProperties properties of the associated bean that you want to include in the
* fetch (* means all properties, null also means all properties).
*/
SELF fetchQuery(String path, String fetchProperties);
/**
* Fetch the path and properties using L2 bean cache.
*
* @param path The path of the beans we are fetching from L2 cache.
* @param fetchProperties The properties that should be loaded.
*/
SELF fetchCache(String path, String fetchProperties);
/**
* Fetch the path and properties lazily (via batch lazy loading).
* <p>
* This is the same as:
*
* <pre>{@code
*
* fetch(path, fetchProperties, FetchConfig.ofLazy())
*
* }</pre>
* <p>
* The reason for using fetchLazy() is to either:
* <ul>
* <li>Control/tune what is fetched as part of lazy loading</li>
* <li>Make use of the L2 cache, build this part of the graph from L2 cache</li>
* </ul>
*
* @param path the property path we wish to fetch lazily.
* @param fetchProperties properties of the associated bean that you want to include in the
* fetch (* means all properties, null also means all properties).
*/
SELF fetchLazy(String path, String fetchProperties);
/**
* Additionally specify a FetchConfig to use a separate query or lazy loading
* to load this path.
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers = DB.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email", FetchConfig.ofLazy(10))
* .findList();
*
* }</pre>
*
* @param path the property path we wish to fetch eagerly.
*/
SELF fetch(String path, String fetchProperties, FetchConfig fetchConfig);
/**
* Specify a path to fetch eagerly including all its properties.
* <p>
* Ebean will endeavour to fetch this path using a SQL join. If Ebean determines that it can
* not use a SQL join (due to maxRows or because it would result in a cartesian product) Ebean
* will automatically convert this fetch query into a "query join" - i.e. use fetchQuery().
* </p>
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers = DB.find(Customer.class)
* // eager fetch the contacts
* .fetch("contacts")
* .findList();
*
* }</pre>
*
* @param path the property path we wish to fetch eagerly.
*/
SELF fetch(String path);
/**
* Fetch the path eagerly using a "query join" (separate SQL query).
* <p>
* This is the same as:
* <pre>{@code
*
* fetch(path, FetchConfig.ofQuery())
*
* }</pre>
* <p>
* This would be used instead of a fetch() when we use a separate SQL query to fetch this
* part of the object graph rather than a SQL join.
* <p>
* We might typically get a performance benefit when the path to fetch is a OneToMany
* or ManyToMany, the 'width' of the 'root bean' is wide and the cardinality of the many
* is high.
*
* @param path the property path we wish to fetch eagerly
*/
SELF fetchQuery(String path);
/**
* Fetch the path eagerly using L2 cache.
*/
SELF fetchCache(String path);
/**
* Fetch the path lazily (via batch lazy loading).
* <p>
* This is the same as:
* </p>
* <pre>{@code
*
* fetch(path, FetchConfig.ofLazy())
*
* }</pre>
* <p>
* The reason for using fetchLazy() is to either:
* </p>
* <ul>
* <li>Control/tune what is fetched as part of lazy loading</li>
* <li>Make use of the L2 cache, build this part of the graph from L2 cache</li>
* </ul>
*
* @param path the property path we wish to fetch lazily.
*/
SELF fetchLazy(String path);
/**
* Additionally specify a JoinConfig to specify a "query join" and or define
* the lazy loading query.
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers = DB.find(Customer.class)
* // lazy fetch contacts with a batch size of 100
* .fetch("contacts", FetchConfig.ofLazy(100))
* .findList();
*
* }</pre>
*/
SELF fetch(String path, FetchConfig fetchConfig);
}
@@ -1,7 +1,9 @@
package io.ebean;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.DocStoreConfig;
import jakarta.persistence.PersistenceException;
import java.sql.Connection;
@@ -209,6 +211,29 @@ public interface Transaction extends AutoCloseable {
*/
boolean isActive();
/**
* Set the behavior for document store updates on this transaction.
* <p>
* For example, set the mode to DocStoreEvent.IGNORE for this transaction and
* then any changes via this transaction are not sent to the doc store. This
* would be used when doing large bulk inserts into the database and we want
* to control how that is sent to the document store.
* </p>
*/
void setDocStoreMode(DocStoreMode mode);
/**
* Set the batch size to use for sending messages to the document store.
* <p>
* You might set this if you know the changes in this transaction result in especially large or
* especially small payloads and want to adjust the batch size to match.
* </p>
* <p>
* Setting this overrides the default of {@link DocStoreConfig#getBulkBatchSize()}
* </p>
*/
void setDocStoreBatchSize(int batchSize);
/**
* Explicitly turn off or on the cascading nature of save() and delete(). This
* gives the developer exact control over what beans are saved and deleted
@@ -1,8 +1,5 @@
package io.ebean.config;
import io.avaje.config.Config;
import io.avaje.config.Configuration;
import java.util.Properties;
/**
@@ -19,16 +16,6 @@ public class ContainerConfig {
private String podName;
private int port;
private Properties properties;
private Configuration configuration;
public ContainerConfig() {
this.configuration = Config.asConfiguration();
this.active = configuration.getBool("ebean.cluster.active", active);
this.serviceName = configuration.getNullable("ebean.cluster.serviceName", serviceName);
this.namespace = configuration.getNullable("ebean.cluster.namespace", namespace);
this.podName = configuration.getNullable("ebean.cluster.podName", podName);
this.port = configuration.getInt("ebean.cluster.port", 0);
}
/**
* Return the service name.
@@ -104,7 +91,7 @@ public class ContainerConfig {
* Return the deployment properties.
*/
public Properties getProperties() {
return properties != null ? properties : configuration.asProperties();
return properties;
}
/**
@@ -114,4 +101,25 @@ public class ContainerConfig {
this.properties = properties;
}
/**
* Load the settings from properties.
*/
public void loadFromProperties(Properties properties) {
this.properties = properties;
this.active = getProperty(properties, "ebean.cluster.active", active);
this.serviceName = properties.getProperty("ebean.cluster.serviceName", serviceName);
this.namespace = properties.getProperty("ebean.cluster.namespace", namespace);
this.podName = properties.getProperty("ebean.cluster.podName", podName);
String portParam = properties.getProperty("ebean.cluster.port");
if (portParam != null) {
this.port = Integer.parseInt(portParam);
}
}
/**
* Return the boolean property setting.
*/
protected boolean getProperty(Properties properties, String key, boolean defaultValue) {
return "true".equalsIgnoreCase(properties.getProperty(key, Boolean.toString(defaultValue)));
}
}
@@ -16,6 +16,8 @@ import io.ebean.event.*;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetricNamingMatch;
import io.ebean.util.StringHelper;
import jakarta.persistence.EnumType;
@@ -123,6 +125,12 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
*/
private boolean loadModuleInfo = true;
/**
* When true then include a sql comment in generated SELECT queries with the query
* label or profile location label.
*/
private boolean includeLabelInSql;
/**
* Interesting classes such as entities, embedded, ScalarTypes,
* Listeners, Finders, Controllers, AttributeConverters etc.
@@ -135,6 +143,16 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
*/
private List<String> packages = new ArrayList<>();
/**
* Configuration for the ElasticSearch integration.
*/
private DocStoreConfig docStoreConfig = new DocStoreConfig();
/**
* Set to true when the Database only uses Document store.
*/
private boolean docStoreOnly;
/**
* This is used to populate @WhoCreated, @WhoModified and
* support other audit features (who executed a query etc).
@@ -409,6 +427,8 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
private ChangeLogListener changeLogListener;
private ChangeLogRegister changeLogRegister;
private boolean changeLogAsync = true;
private ReadAuditLogger readAuditLogger;
private ReadAuditPrepare readAuditPrepare;
private EncryptKeyManager encryptKeyManager;
private EncryptDeployManager encryptDeployManager;
private Encryptor encryptor;
@@ -982,6 +1002,28 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
return this;
}
@Override
public ReadAuditLogger getReadAuditLogger() {
return readAuditLogger;
}
@Override
public DatabaseConfig setReadAuditLogger(ReadAuditLogger readAuditLogger) {
this.readAuditLogger = readAuditLogger;
return this;
}
@Override
public ReadAuditPrepare getReadAuditPrepare() {
return readAuditPrepare;
}
@Override
public DatabaseConfig setReadAuditPrepare(ReadAuditPrepare readAuditPrepare) {
this.readAuditPrepare = readAuditPrepare;
return this;
}
@Override
public ProfilingConfig getProfilingConfig() {
return profilingConfig;
@@ -1256,6 +1298,28 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
}
}
@Override
public boolean isDocStoreOnly() {
return docStoreOnly;
}
@Override
public DatabaseConfig setDocStoreOnly(boolean docStoreOnly) {
this.docStoreOnly = docStoreOnly;
return this;
}
@Override
public DocStoreConfig getDocStoreConfig() {
return docStoreConfig;
}
@Override
public DatabaseConfig setDocStoreConfig(DocStoreConfig docStoreConfig) {
this.docStoreConfig = docStoreConfig;
return this;
}
@Override
public DbConstraintNaming getConstraintNaming() {
return platformConfig.getConstraintNaming();
@@ -2028,6 +2092,13 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
readOnlyDataSourceConfig.loadSettings(p.properties, name + "-ro");
}
/**
* This is broken out to allow overridden behaviour.
*/
protected void loadDocStoreSettings(PropertiesWrapper p) {
docStoreConfig.loadSettings(p);
}
/**
* This is broken out to allow overridden behaviour.
*/
@@ -2059,10 +2130,16 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
}
loadDataSourceSettings(p);
if (docStoreConfig == null) {
docStoreConfig = new DocStoreConfig();
}
loadDocStoreSettings(p);
defaultServer = p.getBoolean("defaultServer", defaultServer);
readOnlyDatabase = p.getBoolean("readOnlyDatabase", readOnlyDatabase);
autoPersistUpdates = p.getBoolean("autoPersistUpdates", autoPersistUpdates);
loadModuleInfo = p.getBoolean("loadModuleInfo", loadModuleInfo);
includeLabelInSql = p.getBoolean("includeLabelInSql", includeLabelInSql);
maxCallStack = p.getInt("maxCallStack", maxCallStack);
dumpMetricsOnShutdown = p.getBoolean("dumpMetricsOnShutdown", dumpMetricsOnShutdown);
dumpMetricsOptions = p.get("dumpMetricsOptions", dumpMetricsOptions);
@@ -2074,6 +2151,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
queryPlanCapturePeriodSecs = p.getLong("queryPlan.capturePeriodSecs", queryPlanCapturePeriodSecs);
queryPlanCaptureMaxTimeMillis = p.getLong("queryPlan.captureMaxTimeMillis", queryPlanCaptureMaxTimeMillis);
queryPlanCaptureMaxCount = p.getInt("queryPlan.captureMaxCount", queryPlanCaptureMaxCount);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
localOnlyL2Cache = p.getBoolean("localOnlyL2Cache", localOnlyL2Cache);
enabledL2Regions = p.get("enabledL2Regions", enabledL2Regions);
@@ -2476,6 +2554,11 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
return loadModuleInfo;
}
@Override
public boolean isIncludeLabelInSql() {
return includeLabelInSql;
}
/**
* @deprecated - migrate to {@link #isLoadModuleInfo()}.
*/
@@ -2492,6 +2575,12 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
return this;
}
@Override
public DatabaseConfig includeLabelInSql(boolean includeLabelInSql) {
this.includeLabelInSql = includeLabelInSql;
return this;
}
@Override
public Function<String, String> getMetricNaming() {
return metricNaming;
@@ -0,0 +1,320 @@
package io.ebean.config;
import io.ebean.Transaction;
import io.ebean.annotation.DocStoreMode;
/**
* Configuration for the Document store integration (e.g. ElasticSearch).
*/
public class DocStoreConfig {
/**
* True when the Document store integration is active/on.
*/
protected boolean active;
/**
* Set to true means Ebean will generate mapping files on startup.
*/
protected boolean generateMapping;
/**
* When true the Document store should drop and re-create document indexes.
*/
protected boolean dropCreate;
/**
* When true the Document store should create any document indexes that don't already exist.
*/
protected boolean create;
/**
* The URL of the Document store. For example: http://localhost:9200.
*/
protected String url;
/**
* Credential that be used for authentication to document store.
*/
protected String username;
/**
* Password credential that be used for authentication to document store.
*/
protected String password;
/**
* Set to true such that the client allows connections to invalid/self signed SSL certificates.
*/
protected boolean allowAllCertificates;
/**
* The default mode used by indexes.
*/
protected DocStoreMode persist = DocStoreMode.UPDATE;
/**
* The default batch size to use for the Bulk API calls.
*/
protected int bulkBatchSize = 1000;
/**
* Resource path for the Document store mapping files.
*/
protected String mappingPath;
/**
* Suffix used for mapping files.
*/
protected String mappingSuffix;
/**
* Location of resources that mapping files are generated into.
*/
protected String pathToResources = "src/main/resources";
/**
* Return true if the Document store (ElasticSearch) integration is active.
*/
public boolean isActive() {
String systemValue = System.getProperty("ebean.docstore.active");
if (systemValue != null) {
return Boolean.parseBoolean(systemValue);
}
return active;
}
/**
* Set to true to make the Document store (ElasticSearch) integration active.
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* Return the URL to the Document store.
*/
public String getUrl() {
String systemValue = System.getProperty("ebean.docstore.url");
if (systemValue != null) {
return systemValue;
}
return url;
}
/**
* Return the user credential for connecting to the document store.
*/
public String getUsername() {
return username;
}
/**
* Set the user credential for connecting to the document store.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Return the password credential for connecting to the document store.
*/
public String getPassword() {
return password;
}
/**
* Set the password credential for connecting to the document store.
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Set the URL to the Document store.
* <p>
* For a local ElasticSearch server this would be: http://localhost:9200
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Return true if Ebean should generate mapping files on server startup.
*/
public boolean isGenerateMapping() {
String systemValue = System.getProperty("ebean.docstore.generateMapping");
if (systemValue != null) {
return Boolean.parseBoolean(systemValue);
}
return generateMapping;
}
/**
* Set to true if Ebean should generate mapping files on server startup.
*/
public void setGenerateMapping(boolean generateMapping) {
this.generateMapping = generateMapping;
}
/**
* Return true if the document store should recreate mapped indexes.
*/
public boolean isDropCreate() {
String systemValue = System.getProperty("ebean.docstore.dropCreate");
if (systemValue != null) {
return Boolean.parseBoolean(systemValue);
}
return dropCreate;
}
/**
* Set to true if the document store should recreate mapped indexes.
*/
public void setDropCreate(boolean dropCreate) {
this.dropCreate = dropCreate;
}
/**
* Create true if the document store should create mapped indexes that don't yet exist.
* This is only used if dropCreate is false.
*/
public boolean isCreate() {
String systemValue = System.getProperty("ebean.docstore.create");
if (systemValue != null) {
return Boolean.parseBoolean(systemValue);
}
return create;
}
/**
* Set to true if the document store should create mapped indexes that don't yet exist.
* This is only used if dropCreate is false.
*/
public void setCreate(boolean create) {
this.create = create;
}
/**
* Return true if the client allows connections to invalid/self signed SSL certificates.
*/
public boolean isAllowAllCertificates() {
return allowAllCertificates;
}
/**
* Set to true such that the client allows connections to invalid/self signed SSL certificates.
*/
public void setAllowAllCertificates(boolean allowAllCertificates) {
this.allowAllCertificates = allowAllCertificates;
}
/**
* Return the default batch size to use for calls to the Bulk API.
*/
public int getBulkBatchSize() {
return bulkBatchSize;
}
/**
* Set the default batch size to use for calls to the Bulk API.
* <p>
* The batch size can be set on a transaction via {@link Transaction#setDocStoreBatchSize(int)}.
* </p>
*/
public void setBulkBatchSize(int bulkBatchSize) {
this.bulkBatchSize = bulkBatchSize;
}
/**
* Return the mapping path.
*/
public String getMappingPath() {
return mappingPath;
}
/**
* Set the mapping path.
*/
public void setMappingPath(String mappingPath) {
this.mappingPath = mappingPath;
}
/**
* Return the mapping suffix.
*/
public String getMappingSuffix() {
return mappingSuffix;
}
/**
* Set the mapping suffix.
*/
public void setMappingSuffix(String mappingSuffix) {
this.mappingSuffix = mappingSuffix;
}
/**
* Return the relative file system path to resources when generating mapping files.
*/
public String getPathToResources() {
return pathToResources;
}
/**
* Set the relative file system path to resources when generating mapping files.
*/
public void setPathToResources(String pathToResources) {
this.pathToResources = pathToResources;
}
/**
* Return the default behavior for when Insert, Update and Delete events occur on beans that have an associated
* Document store.
*/
public DocStoreMode getPersist() {
return persist;
}
/**
* Set the default behavior for when Insert, Update and Delete events occur on beans that have an associated
* Document store.
* <ul>
* <li>DocStoreEvent.UPDATE - build and send message to Bulk API</li>
* <li>DocStoreEvent.QUEUE - add an entry with the index type and id only into a queue for later processing</li>
* <li>DocStoreEvent.IGNORE - ignore. Most likely used when some scheduled batch job handles updating the index</li>
* </ul>
* <p>
* You might choose to use QUEUE if that particular index data is updating very frequently or the cost of indexing
* is expensive. Setting it to QUEUE can mean many changes can be batched together potentially coalescing multiple
* updates for an index entry into a single update.
* </p>
* <p>
* You might choose to use IGNORE when you have your own external process for updating the indexes. In this case
* you don't want Ebean to do anything when the data changes.
* </p>
*/
public void setPersist(DocStoreMode persist) {
this.persist = persist;
}
/**
* Load settings specified in properties files.
*/
public void loadSettings(PropertiesWrapper properties) {
active = properties.getBoolean("docstore.active", active);
url = properties.get("docstore.url", url);
username = properties.get("docstore.username", url);
password = properties.get("docstore.password", url);
persist = properties.getEnum(DocStoreMode.class, "docstore.persist", persist);
bulkBatchSize = properties.getInt("docstore.bulkBatchSize", bulkBatchSize);
generateMapping = properties.getBoolean("docstore.generateMapping", generateMapping);
dropCreate = properties.getBoolean("docstore.dropCreate", dropCreate);
create = properties.getBoolean("docstore.create", create);
allowAllCertificates = properties.getBoolean("docstore.allowAllCertificates", allowAllCertificates);
mappingPath = properties.get("docstore.mappingPath", mappingPath);
mappingSuffix = properties.get("docstore.mappingSuffix", mappingSuffix);
pathToResources = properties.get("docstore.pathToResources", pathToResources);
}
}
@@ -1,50 +1,29 @@
package io.ebean.config;
import io.ebean.ProfileLocation;
import io.ebean.bean.ObjectGraphNode;
import java.util.List;
/**
* Slow query event.
* The data for the slow query.
*/
public class SlowQueryEvent {
private final String sql;
private final long timeMillis;
private final int rowCount;
private final ObjectGraphNode originNode;
/**
* Construct with the SQL and execution time in millis.
*/
public SlowQueryEvent(String sql, long timeMillis, int rowCount, ObjectGraphNode originNode) {
this.sql = sql;
this.timeMillis = timeMillis;
this.rowCount = rowCount;
this.originNode = originNode;
}
public interface SlowQueryEvent {
/**
* Return the SQL for the slow query.
*/
public String getSql() {
return sql;
}
String getSql();
/**
* Return the execution time in millis.
*/
public long getTimeMillis() {
return timeMillis;
}
long getTimeMillis();
/**
* Return the total row count associated with the query.
*/
public int getRowCount() {
return rowCount;
}
int getRowCount();
/**
* Return the origin point for the root query.
@@ -53,7 +32,20 @@ public class SlowQueryEvent {
* shows the code that invoked the query.
* </p>
*/
public ObjectGraphNode getOriginNode() {
return originNode;
}
ObjectGraphNode getOriginNode();
/**
* Return the bind parameters.
*/
List<Object> getBindParams();
/**
* Return the label.
*/
String getLabel();
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
}
@@ -199,12 +199,21 @@ public class DatabasePlatform {
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
protected InsertSqlSyntaxExtension insertSqlSyntaxExtension = new StandardInsertSqlSyntax();
/**
* Instantiates a new database platform.
*/
public DatabasePlatform() {
}
/**
* Return the Insert SQL syntax helper.
*/
public InsertSqlSyntaxExtension insertSqlSyntaxExtension() {
return insertSqlSyntaxExtension;
}
/**
* Translate the SQLException into a specific persistence exception if possible.
*/
@@ -769,4 +778,15 @@ public class DatabasePlatform {
public boolean supportsNativeJavaTime() {
return supportsNativeJavaTime;
}
public String inlineSqlComment(String label) {
if (label == null) {
return "";
}
return "/* " + label + " */ ";
}
public String inlineSqlHint(String hint) {
return "/*+ " + hint + " */ ";
}
}
@@ -0,0 +1,32 @@
package io.ebean.config.dbplatform;
/**
* Insert SQL syntax to allow support for ClickHouse type optimisation for inserts.
*/
public interface InsertSqlSyntaxExtension {
/**
* Start the columns.
*/
String startColumns();
/**
* End of the columns.
*/
String endColumns();
/**
* Return true for insert to use standard binding.
*/
boolean useBinding();
/**
* Start types for non-standard binding (e.g. ClickHouse).
*/
String startTypes();
/**
* End types for non-standard binding (e.g. ClickHouse).
*/
String endTypes();
}
@@ -0,0 +1,32 @@
package io.ebean.config.dbplatform;
/**
* Standard Insert SQL syntax.
*/
public final class StandardInsertSqlSyntax implements InsertSqlSyntaxExtension {
@Override
public String startColumns() {
return " (";
}
@Override
public String endColumns() {
return ") values (";
}
@Override
public boolean useBinding() {
return true;
}
@Override
public String startTypes() {
throw new IllegalStateException();
}
@Override
public String endTypes() {
throw new IllegalStateException();
}
}
@@ -0,0 +1,7 @@
package io.ebean.docstore;
/**
* Document Mapping for a bean marker interface.
*/
public interface DocMapping {
}
@@ -0,0 +1,7 @@
package io.ebean.docstore;
/**
* Document query request context marker interface.
*/
public interface DocQueryContext<T> {
}
@@ -0,0 +1,7 @@
package io.ebean.docstore;
/**
* Document update context marker interface.
*/
public interface DocUpdateContext {
}
@@ -0,0 +1,102 @@
package io.ebean.docstore;
import java.util.Map;
/**
* Raw document.
*/
public class RawDoc {
private Map<String, Object> source;
private String id;
private double score;
private String index;
private String type;
/**
* Construct the document with all the meta data.
*/
public RawDoc(Map<String, Object> source, String id, double score, String index, String type) {
this.source = source;
this.id = id;
this.score = score;
this.index = index;
this.type = type;
}
/**
* Construct empty (typically for JSON marshalling).
*/
public RawDoc() {
}
/**
* Return the source document as a Map.
*/
public Map<String, Object> getSource() {
return source;
}
/**
* Return the Id value.
*/
public String getId() {
return id;
}
/**
* Return the score.
*/
public double getScore() {
return score;
}
/**
* Return the index name.
*/
public String getIndex() {
return index;
}
/**
* Return the index type.
*/
public String getType() {
return type;
}
/**
* Set the source document.
*/
public void setSource(Map<String, Object> source) {
this.source = source;
}
/**
* Set the id value.
*/
public void setId(String id) {
this.id = id;
}
/**
* Set the score.
*/
public void setScore(double score) {
this.score = score;
}
/**
* Set the index name.
*/
public void setIndex(String index) {
this.index = index;
}
/**
* Set the index type.
*/
public void setType(String type) {
this.type = type;
}
}
@@ -0,0 +1,37 @@
package io.ebean.event.readaudit;
/**
* Log that the query was executed
*/
public interface ReadAuditLogger {
/**
* Called when a new query plan is created.
* <p>
* The query plan has the full sql and logging the query plan separately means that each of
* the bean and many read events can log the query plan key and not the full sql (reducing the
* bulk size of the read audit logs).
* </p>
*/
void queryPlan(ReadAuditQueryPlan queryPlan);
/**
* Audit a find bean query that returned a bean.
* <p>
* Finds that did not return a bean are excluded.
* </p>
*/
void auditBean(ReadEvent readBean);
/**
* Audit a find many query that returned some beans.
* <p>
* Finds that did not return any beans are excluded.
* </p>
* <p>
* For large queries executed via findEach() etc the ids are collected in batches
* and logged. Hence the ids list has a maximum size of the batch size.
* </p>
*/
void auditMany(ReadEvent readMany);
}
@@ -0,0 +1,23 @@
package io.ebean.event.readaudit;
/**
* Set user context information into the read event prior to it being logged.
*/
public interface ReadAuditPrepare {
/**
* Prepare the read event by setting any user context information into the read event such as the
* application user id and ip address.
* <p>
* This method is called prior to the read event being sent to the ReadAuditLogger.
* </p>
* <p>
* Note that for findFutureList() queries prepare() is called early in the foreground thread
* prior to the query executing and at that point the ReadEvent bean only has the bean type
* and no other details (which are populated later when the query is executed in the background
* thread).
* </p>
*/
void prepare(ReadEvent readEvent);
}
@@ -0,0 +1,97 @@
package io.ebean.event.readaudit;
/**
* A SQL query and associated keys.
* <p>
* This is logged as a separate event so that the
* </p>
*/
public class ReadAuditQueryPlan {
String beanType;
String queryKey;
String sql;
/**
* Construct given the beanType, queryKey and sql.
*/
public ReadAuditQueryPlan(String beanType, String queryKey, String sql) {
this.beanType = beanType;
this.queryKey = queryKey;
this.sql = sql;
}
/**
* Construct for JSON tools.
*/
public ReadAuditQueryPlan() {
}
@Override
public String toString() {
return "beanType:" + beanType + " queryKey:" + queryKey + " sql:" + sql;
}
/**
* Return the bean type.
*/
public String getBeanType() {
return beanType;
}
/**
* Set the bean type.
*/
public void setBeanType(String beanType) {
this.beanType = beanType;
}
/**
* Return the query key (relative to the bean type).
*/
public String getQueryKey() {
return queryKey;
}
/**
* Set the query key.
*/
public void setQueryKey(String queryKey) {
this.queryKey = queryKey;
}
/**
* Return the sql statement.
*/
public String getSql() {
return sql;
}
/**
* Set the sql statement.
*/
public void setSql(String sql) {
this.sql = sql;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ReadAuditQueryPlan that = (ReadAuditQueryPlan) o;
if (!beanType.equals(that.beanType)) return false;
if (!queryKey.equals(that.queryKey)) return false;
return sql.equals(that.sql);
}
@Override
public int hashCode() {
int result = beanType.hashCode();
result = 92821 * result + queryKey.hashCode();
result = 92821 * result + sql.hashCode();
return result;
}
}
@@ -0,0 +1,261 @@
package io.ebean.event.readaudit;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Read event sent to the ReadEventLogger.
* <p>
* This is a flattened in that it contains either a read bean or list of beans. It is flattened
* in this way to simplify logging and processing and simply means that it either contains an
* id or a list of ids.
* </p>
*/
public class ReadEvent {
/**
* User defined 'source' such as the application name.
*/
protected String source;
/**
* Application user id expected to be optionally populated by ChangeLogPrepare.
*/
protected String userId;
/**
* Application user ip address expected to be optionally populated by ChangeLogPrepare.
*/
protected String userIpAddress;
/**
* Arbitrary user context information expected to be optionally populated by ChangeLogPrepare.
*/
protected Map<String, String> userContext;
/**
* The time the bean change was created.
*/
protected long eventTime;
/**
* The type of the bean(s) read.
*/
protected String beanType;
/**
* The query key (relative to the bean type).
*/
protected String queryKey;
/**
* The bind log when the query was executed.
*/
protected String bindLog;
/**
* The id of the bean read.
*/
protected Object id;
/**
* The ids of the beans read.
*/
protected List<Object> ids;
/**
* Common constructor for single bean and multi-bean read events.
*/
protected ReadEvent(String beanType, String queryKey, String bindLog) {
this.beanType = beanType;
this.queryKey = queryKey;
this.bindLog = bindLog;
this.eventTime = System.currentTimeMillis();
}
/**
* Construct for a single bean read.
*/
public ReadEvent(String beanType, String queryKey, String bindLog, Object id) {
this(beanType, queryKey, bindLog);
this.id = id;
}
/**
* Construct for many beans read.
*/
public ReadEvent(String beanType, String queryKey, String bindLog, List<Object> ids) {
this(beanType, queryKey, bindLog);
this.ids = ids;
}
/**
* Construct for many future list query.
*/
public ReadEvent(String beanType) {
this.beanType = beanType;
this.eventTime = System.currentTimeMillis();
}
/**
* Constructor for JSON tools.
*/
public ReadEvent() {
}
/**
* Return a code that identifies the source of the change (like the name of the application).
*/
public String getSource() {
return source;
}
/**
* Set the source of the change (like the name of the application).
*/
public void setSource(String source) {
this.source = source;
}
/**
* Return the application user Id.
*/
public String getUserId() {
return userId;
}
/**
* Set the application user Id.
* <p>
* This can be set by the ChangeLogListener in the prepare() method which is called
* in the foreground thread.
* </p>
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* Return the application users ip address.
*/
public String getUserIpAddress() {
return userIpAddress;
}
/**
* Set the application users ip address.
* <p>
* This can be set by the ChangeLogListener in the prepare() method which is called
* in the foreground thread.
* </p>
*/
public void setUserIpAddress(String userIpAddress) {
this.userIpAddress = userIpAddress;
}
/**
* Return a user context value - anything you set yourself in ChangeLogListener prepare().
*/
public Map<String, String> getUserContext() {
if (userContext == null) {
userContext = new LinkedHashMap<>();
}
return userContext;
}
/**
* Set a user context value (anything you like).
* <p>
* This can be set by the ChangeLogListener in the prepare() method which is called
* in the foreground thread.
* </p>
*/
public void setUserContext(Map<String, String> userContext) {
this.userContext = userContext;
}
/**
* Return the type of bean read.
*/
public String getBeanType() {
return beanType;
}
/**
* Set the type of bean read.
*/
public void setBeanType(String beanType) {
this.beanType = beanType;
}
/**
* Return the query key (relative to the bean type).
*/
public String getQueryKey() {
return queryKey;
}
/**
* Set the query key (relative to the bean type).
*/
public void setQueryKey(String queryKey) {
this.queryKey = queryKey;
}
/**
* Return the bind log used when executing the query.
*/
public String getBindLog() {
return bindLog;
}
/**
* Set the bind log used when executing the query.
*/
public void setBindLog(String bindLog) {
this.bindLog = bindLog;
}
/**
* Return the event date time.
*/
public long getEventTime() {
return eventTime;
}
/**
* Set the event date time.
*/
public void setEventTime(long eventTime) {
this.eventTime = eventTime;
}
/**
* Return the id of the bean read.
*/
public Object getId() {
return id;
}
/**
* Set the id of the bean read.
*/
public void setId(Object id) {
this.id = id;
}
/**
* Return the ids of the beans read.
*/
public List<Object> getIds() {
return ids;
}
/**
* Set the ids of the beans read.
*/
public void setIds(List<Object> ids) {
this.ids = ids;
}
}
@@ -0,0 +1,8 @@
/**
* Provides Auditing of read events including queries and L2 cache.
* <p>
* Provides a built support for supplied an audit of all the 'read events' for beans annotated
* with <code>@ReadAudit</code>
* </p>
*/
package io.ebean.event.readaudit;
@@ -0,0 +1,72 @@
package io.ebean.plugin;
import io.ebean.FetchPath;
import io.ebean.Query;
import io.ebean.docstore.DocUpdateContext;
import java.io.IOException;
/**
* Doc store functions for a specific entity bean type.
*
* @param <T> The type of entity bean
*/
public interface BeanDocType<T> {
/**
* Return the doc store index type for this bean type.
*/
String indexType();
/**
* Return the doc store index name for this bean type.
*/
String indexName();
/**
* Apply the appropriate fetch path to the query such that the query returns beans matching
* the document store structure with the expected embedded properties.
*/
void applyPath(Query<T> spiQuery);
/**
* Return the FetchPath for the embedded document.
*/
FetchPath embedded(String path);
/**
* For embedded 'many' properties we need a FetchPath relative to the root which is used to
* build and replace the embedded list.
*/
FetchPath embeddedManyRoot(String path);
/**
* Return a 'raw' property mapped for the given property.
* If none exists the given property is returned.
*/
String rawProperty(String property);
/**
* Store the bean in the doc store index.
* <p>
* This somewhat assumes the bean is fetched with appropriate path properties
* to match the expected document structure.
*/
void index(Object idValue, T bean, DocUpdateContext txn) throws IOException;
/**
* Add a delete by Id to the doc store.
*/
void deleteById(Object idValue, DocUpdateContext txn) throws IOException;
/**
* Add a embedded document update to the doc store.
*
* @param idValue the Id value of the bean holding the embedded document
* @param embeddedProperty the embedded property
* @param embeddedRawContent the content of the embedded document in JSON form
* @param txn the doc store transaction to add the update to
*/
void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocUpdateContext txn) throws IOException;
}
@@ -2,12 +2,15 @@ package io.ebean.plugin;
import io.ebean.Query;
import io.ebean.config.dbplatform.IdType;
import io.ebean.docstore.DocMapping;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
import io.ebean.event.BeanQueryAdapter;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
/**
* Information and methods on BeanDescriptors made available to plugins.
@@ -142,4 +145,73 @@ public interface BeanType<T> {
*/
IdType idType();
/**
* Return true if this bean type has doc store backing.
*/
boolean isDocStoreMapped();
/**
* Return the DocumentMapping for this bean type.
* <p>
* This is the document structure and mapping options for how this bean type is mapped
* for the document store.
* </p>
*/
DocMapping docMapping();
/**
* Return the doc store queueId for this bean type.
*/
String docStoreQueueId();
/**
* Return the doc store support for this bean type.\
*/
BeanDocType<T> docStore();
/**
* Add the discriminator value to the query if needed.
*/
void addInheritanceWhere(Query<?> query);
/**
* Return the root bean type for an inheritance hierarchy.
*/
BeanType<?> root();
/**
* Return true if this bean type has an inheritance hierarchy.
*/
boolean hasInheritance();
/**
* Return true if this object is the root level object in its entity
* inheritance.
*/
boolean isInheritanceRoot();
/**
* Returns all direct children of this beantype
*/
List<BeanType<?>> inheritanceChildren();
/**
* Returns the parent in inheritance hierarchy
*/
BeanType<?> inheritanceParent();
/**
* Visit all children recursively
*/
void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor);
/**
* Return the discriminator column.
*/
String discColumn();
/**
* Create a bean given the discriminator value.
*/
T createBeanUsingDisc(Object discValue);
}
@@ -38,6 +38,11 @@ public interface SpiServer extends Database {
*/
List<? extends BeanType<?>> beanTypes(String baseTableName);
/**
* Return the bean type for a given doc store queueId.
*/
BeanType<?> beanTypeForQueueId(String queueId);
/**
* Return a BeanLoader.
*/
@@ -0,0 +1,98 @@
package io.ebean.search;
/**
* Options for the text match and multi match expressions.
*/
public abstract class AbstractMatch {
protected boolean operatorAnd;
protected String analyzer;
protected double boost;
protected String minShouldMatch;
protected int maxExpansions;
protected String zeroTerms;
protected double cutoffFrequency;
protected String fuzziness;
protected int prefixLength;
protected String rewrite;
/**
* Return true if using the AND operator otherwise using the OR operator.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the boost.
*/
public double getBoost() {
return boost;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the zero terms option.
*/
public String getZeroTerms() {
return zeroTerms;
}
/**
* Return the cutoff frequency.
*/
public double getCutoffFrequency() {
return cutoffFrequency;
}
/**
* Return the max expansions.
*/
public int getMaxExpansions() {
return maxExpansions;
}
/**
* Return the analyzer.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the fuzziness.
*/
public String getFuzziness() {
return fuzziness;
}
/**
* Return the prefix length.
*/
public int getPrefixLength() {
return prefixLength;
}
/**
* Return the rewrite option.
*/
public String getRewrite() {
return rewrite;
}
}
@@ -0,0 +1,117 @@
package io.ebean.search;
/**
* Options for the text match expression.
*/
public class Match extends AbstractMatch {
protected boolean phrase;
protected boolean phrasePrefix;
public Match() {
}
/**
* Set this to be a "Phrase" type expression.
*/
public Match phrase() {
phrase = true;
return this;
}
/**
* Set this to be a "Phrase Prefix" type expression.
*/
public Match phrasePrefix() {
phrasePrefix = true;
return this;
}
/**
* Use the AND operator (rather than OR).
*/
public Match opAnd() {
operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public Match opOr() {
operatorAnd = false;
return this;
}
/**
* Set the zero terms.
*/
public Match zeroTerms(String zeroTerms) {
this.zeroTerms = zeroTerms;
return this;
}
/**
* Set the cutoff frequency.
*/
public Match cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set the max expansions (for phrase prefix only).
*/
public Match maxExpansions(int maxExpansions) {
this.maxExpansions = maxExpansions;
return this;
}
/**
* Set the Analyzer to use for this expression.
*/
public Match analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the boost.
*/
public Match boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the rewrite to use.
*/
public Match minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the rewrite to use.
*/
public Match rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return true if this is a phrase query.
*/
public boolean isPhrase() {
return phrase;
}
/**
* Return true if this is a phrase prefix query.
*/
public boolean isPhrasePrefix() {
return phrasePrefix;
}
}
@@ -0,0 +1,148 @@
package io.ebean.search;
/**
* Options for the text match expression.
*/
public class MultiMatch extends AbstractMatch {
/**
* The MultiMatch type.
*/
public enum Type {
BEST_FIELDS,
MOST_FIELDS,
CROSS_FIELDS,
PHRASE,
PHRASE_PREFIX
}
protected final String[] fields;
protected Type type = Type.BEST_FIELDS;
protected double tieBreaker;
/**
* Create with the given fields.
*/
public static MultiMatch fields(String... fields) {
return new MultiMatch(fields);
}
/**
* Construct with a set of fields.
*/
public MultiMatch(String... fields) {
this.fields = fields;
}
/**
* Set the type of query.
*/
public MultiMatch type(Type type) {
this.type = type;
return this;
}
/**
* Set the tieBreaker to use.
*/
public MultiMatch tieBreaker(double tieBreaker) {
this.tieBreaker = tieBreaker;
return this;
}
/**
* Use the AND operator (rather than OR).
*/
public MultiMatch opAnd() {
operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public MultiMatch opOr() {
operatorAnd = false;
return this;
}
/**
* Set the minimum should match value.
*/
public MultiMatch minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the boost.
*/
public MultiMatch boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the zero terms.
*/
public MultiMatch zeroTerms(String zeroTerms) {
this.zeroTerms = zeroTerms;
return this;
}
/**
* Set the cutoff frequency.
*/
public MultiMatch cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set the max expansions (for phrase prefix only).
*/
public MultiMatch maxExpansions(int maxExpansions) {
this.maxExpansions = maxExpansions;
return this;
}
/**
* Set the Analyzer to use for this expression.
*/
public MultiMatch analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the rewrite to use.
*/
public MultiMatch rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return the type.
*/
public Type getType() {
return type;
}
/**
* Return the fields to search.
*/
public String[] getFields() {
return fields;
}
/**
* Return the tie breaker.
*/
public double getTieBreaker() {
return tieBreaker;
}
}
@@ -0,0 +1,139 @@
package io.ebean.search;
/**
* Text common terms query.
* <p>
* This maps to an ElasticSearch "common terms query".
* </p>
* <pre>{@code
*
* TextCommonTerms options = new TextCommonTerms()
* .cutoffFrequency(0.001)
* .minShouldMatch("50%")
* .lowFreqOperatorAnd(true)
* .highFreqOperatorAnd(true);
*
* List<Customer> customers = database.find(Customer.class)
* .text()
* .textCommonTerms("the brown", options)
* .findList();
*
* }</pre>
* <pre>{@code
*
* // ElasticSearch expression
*
* "common": {
* "body": {
* "query": "the brown",
* "cutoff_frequency": 0.001,
* "low_freq_operator": "and",
* "high_freq_operator": "and",
* "minimum_should_match": "50%"
* }
* }
*
* }</pre>
*/
public class TextCommonTerms {
protected double cutoffFrequency;
protected boolean lowFreqOperatorAnd;
protected boolean highFreqOperatorAnd;
protected String minShouldMatch;
protected String minShouldMatchLowFreq;
protected String minShouldMatchHighFreq;
/**
* Set the cutoff frequency.
*/
public TextCommonTerms cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set to true if low frequency terms should use AND operator.
*/
public TextCommonTerms lowFreqOperatorAnd(boolean opAnd) {
this.lowFreqOperatorAnd = opAnd;
return this;
}
/**
* Set to true if high frequency terms should use AND operator.
*/
public TextCommonTerms highFreqOperatorAnd(boolean opAnd) {
this.highFreqOperatorAnd = opAnd;
return this;
}
/**
* Set the minimum should match.
*/
public TextCommonTerms minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the minimum should match for low frequency terms.
*/
public TextCommonTerms minShouldMatchLowFreq(String minShouldMatchLowFreq) {
this.minShouldMatchLowFreq = minShouldMatchLowFreq;
return this;
}
/**
* Set the minimum should match for high frequency terms.
*/
public TextCommonTerms minShouldMatchHighFreq(String minShouldMatchHighFreq) {
this.minShouldMatchHighFreq = minShouldMatchHighFreq;
return this;
}
/**
* Return true if low freq should use the AND operator.
*/
public boolean isLowFreqOperatorAnd() {
return lowFreqOperatorAnd;
}
/**
* Return true if high freq should use the AND operator.
*/
public boolean isHighFreqOperatorAnd() {
return highFreqOperatorAnd;
}
/**
* Return the cutoff frequency.
*/
public double getCutoffFrequency() {
return cutoffFrequency;
}
/**
* Return the minimum to match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the minimum to match for high frequency.
*/
public String getMinShouldMatchHighFreq() {
return minShouldMatchHighFreq;
}
/**
* Return the minimum to match for low frequency.
*/
public String getMinShouldMatchLowFreq() {
return minShouldMatchLowFreq;
}
}
@@ -0,0 +1,398 @@
package io.ebean.search;
/**
* Text query string options.
* <p>
* This maps to an ElasticSearch "query string query".
* </p>
* <pre>{@code
*
* TextQueryString options = new TextQueryString()
* .analyzeWildcard(true)
* .fields("name")
* .lenient(true)
* .opAnd();
*
* List<Customer> customers = database.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
* <pre>{@code
*
* // just use default options
* TextQueryString options = new TextQueryString();
*
* List<Customer> customers = database.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*/
public class TextQueryString {
public static final int DEFAULT_FUZZY_MAX_EXPANSIONS = 50;
protected final String[] fields;
/**
* Only used when multiple fields set.
*/
protected boolean useDisMax = true;
/**
* Only used when multiple fields set.
*/
protected double tieBreaker;
protected String defaultField;
protected boolean operatorAnd;
protected String analyzer;
protected boolean allowLeadingWildcard = true;
protected boolean lowercaseExpandedTerms = true;
protected int fuzzyMaxExpansions = DEFAULT_FUZZY_MAX_EXPANSIONS;
protected String fuzziness;
protected int fuzzyPrefixLength;
protected double phraseSlop;
protected double boost;
protected boolean analyzeWildcard;
protected boolean autoGeneratePhraseQueries;
protected String minShouldMatch;
protected boolean lenient;
protected String locale;
protected String timeZone;
protected String rewrite;
/**
* Create with given fields.
*/
public static TextQueryString fields(String... fields) {
return new TextQueryString(fields);
}
/**
* Construct with the fields to use.
*/
public TextQueryString(String... fields) {
this.fields = fields;
}
/**
* Use the AND operator (rather than OR).
*/
public TextQueryString opAnd() {
this.operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public TextQueryString opOr() {
this.operatorAnd = false;
return this;
}
/**
* Set the locale.
*/
public TextQueryString locale(String locale) {
this.locale = locale;
return this;
}
/**
* Set lenient mode.
*/
public TextQueryString lenient(boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* Set the minimum should match.
*/
public TextQueryString minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the analyzer.
*/
public TextQueryString analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set useDisMax option (when multiple fields only).
*/
public TextQueryString useDisMax(boolean useDisMax) {
this.useDisMax = useDisMax;
return this;
}
/**
* Set tieBreaker option (when multiple fields only).
*/
public TextQueryString tieBreaker(double tieBreaker) {
this.tieBreaker = tieBreaker;
return this;
}
/**
* Set the default field.
*/
public TextQueryString defaultField(String defaultField) {
this.defaultField = defaultField;
return this;
}
/**
* Set allow leading wildcard mode.
*/
public TextQueryString allowLeadingWildcard(boolean allowLeadingWildcard) {
this.allowLeadingWildcard = allowLeadingWildcard;
return this;
}
/**
* Set lowercase expanded terms mode.
*/
public TextQueryString lowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
this.lowercaseExpandedTerms = lowercaseExpandedTerms;
return this;
}
/**
* Set fuzzy max expansions.
*/
public TextQueryString fuzzyMaxExpansions(int fuzzyMaxExpansions) {
this.fuzzyMaxExpansions = fuzzyMaxExpansions;
return this;
}
/**
* Set fuzziness.
*/
public TextQueryString fuzziness(String fuzziness) {
this.fuzziness = fuzziness;
return this;
}
/**
* Set the fuzzy prefix length.
*/
public TextQueryString fuzzyPrefixLength(int fuzzyPrefixLength) {
this.fuzzyPrefixLength = fuzzyPrefixLength;
return this;
}
/**
* Set the phrase slop.
*/
public TextQueryString phraseSlop(double phraseSlop) {
this.phraseSlop = phraseSlop;
return this;
}
/**
* Set the boost.
*/
public TextQueryString boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the analyze wildcard mode.
*/
public TextQueryString analyzeWildcard(boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Set the auto generate phrase queries mode.
*/
public TextQueryString autoGeneratePhraseQueries(boolean autoGeneratePhraseQueries) {
this.autoGeneratePhraseQueries = autoGeneratePhraseQueries;
return this;
}
/**
* Set the time zone.
*/
public TextQueryString timeZone(String timeZone) {
this.timeZone = timeZone;
return this;
}
/**
* Set the rewrite option.
*/
public TextQueryString rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return the rewrite option.
*/
public String getRewrite() {
return rewrite;
}
/**
* Return the fields.
*/
public String[] getFields() {
return fields;
}
/**
* Return true if AND is the default operator.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the analyzer.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the locale.
*/
public String getLocale() {
return locale;
}
/**
* Return lenient mode.
*/
public boolean isLenient() {
return lenient;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the useDixMax mode.
*/
public boolean isUseDisMax() {
return useDisMax;
}
/**
* Return the tie breaker.
*/
public double getTieBreaker() {
return tieBreaker;
}
/**
* Return the default field.
*/
public String getDefaultField() {
return defaultField;
}
/**
* Return the allow leading wildcard mode.
*/
public boolean isAllowLeadingWildcard() {
return allowLeadingWildcard;
}
/**
* Return the lowercase expanded terms mode.
*/
public boolean isLowercaseExpandedTerms() {
return lowercaseExpandedTerms;
}
/**
* Return the fuzzy max expansions.
*/
public int getFuzzyMaxExpansions() {
return fuzzyMaxExpansions;
}
/**
* Return the fuzziness.
*/
public String getFuzziness() {
return fuzziness;
}
/**
* Return the fuzzy prefix length.
*/
public int getFuzzyPrefixLength() {
return fuzzyPrefixLength;
}
/**
* Return the phrase slop.
*/
public double getPhraseSlop() {
return phraseSlop;
}
/**
* Return the analyze wildcard mode.
*/
public boolean isAnalyzeWildcard() {
return analyzeWildcard;
}
/**
* Return the boost.
*/
public double getBoost() {
return boost;
}
/**
* Return the auto generate phase queries mode.
*/
public boolean isAutoGeneratePhraseQueries() {
return autoGeneratePhraseQueries;
}
/**
* Return the time zone.
*/
public String getTimeZone() {
return timeZone;
}
}
@@ -0,0 +1,193 @@
package io.ebean.search;
/**
* Simple text query options.
* <p>
* This maps to an ElasticSearch "simple text query".
* </p>
* <pre>{@code
*
* TextSimple options = new TextSimple()
* .analyzeWildcard(true)
* .fields("name")
* .lenient(true)
* .opAnd();
*
* List<Customer> customers = database.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*/
public class TextSimple {
protected String[] fields;
protected boolean operatorAnd;
protected String analyzer;
protected String flags;
protected boolean lowercaseExpandedTerms = true;
protected boolean analyzeWildcard;
protected String locale;
protected boolean lenient;
protected String minShouldMatch;
/**
* Construct
*/
public TextSimple() {
}
/**
* Set the fields.
*/
public TextSimple fields(String... fields) {
this.fields = fields;
return this;
}
/**
* Use AND as the default operator.
*/
public TextSimple opAnd() {
this.operatorAnd = true;
return this;
}
/**
* Use OR as the default operator.
*/
public TextSimple opOr() {
this.operatorAnd = false;
return this;
}
/**
* Set the analyzer
*/
public TextSimple analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the flags.
*/
public TextSimple flags(String flags) {
this.flags = flags;
return this;
}
/**
* Set the false to not use lowercase expanded terms.
*/
public TextSimple lowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
this.lowercaseExpandedTerms = lowercaseExpandedTerms;
return this;
}
/**
* Set to true to use analyze wildcard.
*/
public TextSimple analyzeWildcard(boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Set the locale.
*/
public TextSimple locale(String locale) {
this.locale = locale;
return this;
}
/**
* Set the lenient mode.
*/
public TextSimple lenient(boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* Set the minimum should match.
*/
public TextSimple minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Return lenient mode.
*/
public boolean isLenient() {
return lenient;
}
/**
* Return true to analyse wildcard.
*/
public boolean isAnalyzeWildcard() {
return analyzeWildcard;
}
/**
* Return lowercase expanded terms mode.
*/
public boolean isLowercaseExpandedTerms() {
return lowercaseExpandedTerms;
}
/**
* Return true if the default operator should be AND.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the analyzer to use.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the fields.
*/
public String[] getFields() {
return fields;
}
/**
* Return the locale.
*/
public String getLocale() {
return locale;
}
/**
* Return the flags.
*/
public String getFlags() {
return flags;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
}
@@ -0,0 +1,4 @@
/**
* Provides text search expressions like Match, TextQueryString etc.
*/
package io.ebean.search;
+3
View File
@@ -30,10 +30,13 @@ module io.ebean.api {
exports io.ebean.config;
exports io.ebean.config.dbplatform;
exports io.ebean.event;
exports io.ebean.event.readaudit;
exports io.ebean.event.changelog;
exports io.ebean.common;
exports io.ebean.docstore;
exports io.ebean.plugin;
exports io.ebean.metric;
exports io.ebean.search;
exports io.ebean.service;
exports io.ebean.text;
exports io.ebean.text.json;
@@ -76,6 +76,7 @@ class DatabaseConfigTest {
props.setProperty("skipDataSourceCheck", "true");
props.setProperty("readOnlyDatabase", "true");
props.setProperty("lengthCheck", "ON");
props.setProperty("includeLabelInSql", "true");
props.setProperty("queryPlan.enable", "true");
props.setProperty("queryPlan.thresholdMicros", "10000");
@@ -96,6 +97,7 @@ class DatabaseConfigTest {
assertTrue(settings.isLoadModuleInfo());
assertTrue(settings.skipDataSourceCheck());
assertTrue(settings.readOnlyDatabase());
assertTrue(settings.isIncludeLabelInSql());
assertThat(settings.getLengthCheck()).isEqualTo(LengthCheck.ON);
assertTrue(settings.isIdGeneratorAutomatic());
@@ -181,6 +183,7 @@ class DatabaseConfigTest {
assertEquals(10000L, config.getQueryPlanCaptureMaxTimeMillis());
assertEquals(10, config.getQueryPlanCaptureMaxCount());
assertThat(config.getLengthCheck()).isEqualTo(LengthCheck.OFF);
assertFalse(config.isIncludeLabelInSql());
config.setLoadModuleInfo(false);
assertFalse(config.isAutoLoadModuleInfo());
+26 -26
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<!-- platforms -->
@@ -193,79 +193,79 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
+38 -6
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.0.1</version>
<version>14.1.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,7 +22,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
@@ -46,7 +46,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.13.1</version>
</dependency>
<!--
@@ -147,21 +159,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>14.0.1</version>
<version>14.1.0</version>
<scope>test</scope>
</dependency>
@@ -222,6 +234,26 @@
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<doctitle>Ebean 12</doctitle>
<overview>src/main/java/io/ebean/overview.html</overview>
<excludePackageNames>io.ebeaninternal.*:io.ebeanservice:io.ebean.common:io.ebean.bean:io.ebean.service:io.ebean.metric:io.ebean.util:io.ebean.config.properties:io.ebean.config.dbplatform</excludePackageNames>
<linksource>true</linksource>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -51,4 +51,8 @@ public interface LoadContext {
*/
void register(String path, BeanPropertyAssocMany<?> many, BeanCollection<?> bc);
/**
* Use soft-references for streaming queries, so unreachable entries can be garbage collected.
*/
void useReferences(boolean useReferences);
}
@@ -43,4 +43,5 @@ public interface LoadManyBuffer {
void configureQuery(SpiQuery<?> query);
boolean isUseDocStore();
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.api;
import io.ebean.CacheMode;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.server.core.BindPadding;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -57,17 +58,22 @@ public final class LoadManyRequest extends LoadRequest {
return loadContext.fullPath();
}
private List<Object> parentIdList(SpiEbeanServer server) {
List<Object> idList = new ArrayList<>();
BeanPropertyAssocMany<?> many = many();
private List<Object> parentIdList(SpiEbeanServer server, BeanPropertyAssocMany<?> many, PersistenceContext pc) {
final var idList = new ArrayList<>(loadContext.size());
final var descriptor = many.descriptor();
for (int i = 0; i < loadContext.size(); i++) {
BeanCollection<?> bc = loadContext.get(i);
final BeanCollection<?> bc = loadContext.get(i);
if (bc != null) {
if (lazy && !originIncluded && bc == originCollection) {
originIncluded = true;
}
idList.add(many.parentId(bc.owner()));
final var parent = bc.owner();
final var parentId = descriptor.getId(parent);
idList.add(parentId);
bc.setLoader(server); // don't use the load buffer again
if (lazy) {
descriptor.contextPutIfAbsent(pc, parentId, parent);
if (!originIncluded && bc == originCollection) {
originIncluded = true;
}
}
}
}
if (originCollection != null && !originIncluded) {
@@ -75,7 +81,7 @@ public final class LoadManyRequest extends LoadRequest {
idList.add(many.parentId(originCollection.owner()));
originCollection.setLoader(server); // don't use the load buffer again
}
if (many.targetDescriptor().isPadInExpression()) {
if (descriptor.isPadInExpression()) {
BindPadding.padIds(idList);
}
return idList;
@@ -99,8 +105,9 @@ public final class LoadManyRequest extends LoadRequest {
query.where().raw(extraWhere.replace("${ta}", "t0").replace("${mta}", "int_"));
}
query.setLazyLoadForParents(many);
many.addWhereParentIdIn(query, parentIdList(server));
query.setPersistenceContext(loadContext.persistenceContext());
final var pc = loadContext.persistenceContext();
many.addWhereParentIdIn(query, parentIdList(server, many, pc), loadContext.isUseDocStore());
query.setPersistenceContext(pc);
query.setLoadDescription(lazy ? "lazy" : "query", description());
if (lazy) {
query.setLazyLoadBatchSize(loadContext.batchSize());
@@ -4,6 +4,8 @@ import io.avaje.lang.Nullable;
import io.ebean.*;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.CallOrigin;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetricVisitor;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiQuery.Type;
@@ -104,6 +106,11 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectio
*/
BeanDescriptor<?> descriptorById(String className);
/**
* Return BeanDescriptor using it's unique doc store queueId.
*/
BeanDescriptor<?> descriptorByQueueId(String queueId);
/**
* Return BeanDescriptors mapped to this table.
*/
@@ -188,6 +195,17 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectio
*/
boolean isSupportedType(java.lang.reflect.Type genericType);
/**
* Return the ReadAuditLogger to use for logging all read audit events.
*/
ReadAuditLogger readAuditLogger();
/**
* Return the ReadAuditPrepare used to populate the read audit events with
* user context information (user id, user ip address etc).
*/
ReadAuditPrepare readAuditPrepare();
/**
* Return the DataTimeZone to use when reading/writing timestamps via JDBC.
*/
@@ -3,6 +3,9 @@ package io.ebeaninternal.api;
import io.ebean.Expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.expression.DocQueryContext;
import java.io.IOException;
/**
@@ -18,6 +21,16 @@ public interface SpiExpression extends Expression {
*/
void simplify();
/**
* Write the expression as an elastic search expression.
*/
void writeDocQuery(DocQueryContext context) throws IOException;
/**
* Return the nested path for this expression.
*/
String nestedPath(BeanDescriptor<?> desc);
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
@@ -71,7 +84,7 @@ public interface SpiExpression extends Expression {
*
* @param request the associated request.
*/
void addBindValues(SpiExpressionRequest request);
void addBindValues(SpiExpressionBind request);
/**
* Validate all the properties/paths associated with this expression.
@@ -0,0 +1,29 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Expression bind values capture.
*/
public interface SpiExpressionBind {
/**
* Return the bean descriptor for the root type.
*/
BeanDescriptor<?> descriptor();
/**
* Add an encryption key to bind to this request.
*/
void addBindEncryptKey(Object encryptKey);
/**
* Add a bind value to this request.
*/
void addBindValue(Object bindValue);
/**
* Escapes a string to use it as exact match in Like clause.
*/
String escapeLikeString(String value);
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.api;
import io.ebean.ExpressionList;
import io.ebean.Junction;
import io.ebeaninternal.server.expression.DocQueryContext;
import java.io.IOException;
import java.util.List;
@@ -31,6 +32,11 @@ public interface SpiExpressionList<T> extends ExpressionList<T>, SpiExpression {
*/
boolean isEmpty();
/**
* Write the top level where expressions taking into account possible extra idEquals expression.
*/
void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException;
/**
* Apply firstRow maxRows limits on the filterMany query.
*/
@@ -1,15 +1,15 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.expression.platform.DbExpressionRequest;
import java.util.List;
/**
* Request object used for gathering expression sql and bind values.
*/
public interface SpiExpressionRequest {
public interface SpiExpressionRequest extends SpiExpressionBind, DbExpressionRequest {
/**
* Return the DB specific handler for JSON and ARRAY expressions.
@@ -21,11 +21,6 @@ public interface SpiExpressionRequest {
*/
String parseDeploy(String logicalProp);
/**
* Return the bean descriptor for the root type.
*/
BeanDescriptor<?> descriptor();
/**
* Return the associated QueryRequest.
*/
@@ -34,11 +29,13 @@ public interface SpiExpressionRequest {
/**
* Append to the expression sql without any parsing.
*/
@Override
SpiExpressionRequest append(String expression);
/**
* Append to the expression sql without any parsing.
*/
@Override
SpiExpressionRequest append(char c);
/**
@@ -47,6 +44,7 @@ public interface SpiExpressionRequest {
* This is a fast path case when expression is a bean property path and falls back to using parse()
* when that isn't the case.
*/
@Override
SpiExpressionRequest property(String expression);
/**
@@ -54,16 +52,6 @@ public interface SpiExpressionRequest {
*/
SpiExpressionRequest parse(String expression);
/**
* Add an encryption key to bind to this request.
*/
void addBindEncryptKey(Object encryptKey);
/**
* Add a bind value to this request.
*/
void addBindValue(Object bindValue);
/**
* Return the accumulated expression sql for all expressions in this request.
*/
@@ -84,11 +72,6 @@ public interface SpiExpressionRequest {
*/
void appendLike(boolean rawLikeExpression);
/**
* Escapes a string to use it as exact match in Like clause.
*/
String escapeLikeString(String value);
/**
* Append IN expression taking into account platform and type support for Multi-value.
*/
@@ -1,10 +1,17 @@
package io.ebeaninternal.api;
import io.ebean.Junction;
import io.ebeaninternal.server.expression.DocQueryContext;
import java.io.IOException;
/**
* SPI methods for Junction.
*/
public interface SpiJunction<T> extends Junction<T> {
/**
* Write the Junction taking into account it is implied.
*/
void writeDocQueryJunction(DocQueryContext context) throws IOException;
}
@@ -11,6 +11,7 @@ import io.ebean.Query;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.plugin.BeanType;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
@@ -169,6 +170,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
SOFT_DELETED(false),
/**
* Query runs against draft tables.
*/
DRAFT(false),
/**
* Query runs against current data (normal).
*/
@@ -220,6 +226,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
ProfileLocation profileLocation();
/**
* Return the SQL hint to include in the query.
*/
String hint();
/**
* Return the label set on the query.
*/
@@ -286,6 +297,17 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
SpiRawSql rawSql();
/**
* Return true if this query should be executed against the doc store.
*/
boolean isUseDocStore();
/**
* For doc store query return the document index name to search against.
* This is for partitioned indexes (like daily logstash indexes etc).
*/
String getDocIndexName();
/**
* Return the PersistenceContextScope that this query should use.
* <p>
@@ -360,6 +382,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
boolean isAsOfQuery();
/**
* Return true if this is a 'As Draft' query.
*/
boolean isAsDraft();
/**
* Return true if this query includes soft deleted rows.
*/
@@ -488,6 +515,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
NaturalKeyBindParam naturalKeyBindParam();
/**
* Prepare the query for docstore execution with nested paths.
*/
void prepareDocNested();
/**
* Set the query to be a delete query.
*/
@@ -703,6 +735,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
SpiExpressionList<T> havingExpressions();
/**
* Return the text expressions.
*/
SpiExpressionList<T> textExpression();
/**
* Returns true if either firstRow or maxRows has been set.
*/
@@ -769,6 +806,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
boolean tuneFetchProperties(OrmQueryDetail detail);
/**
* If this is a RawSql based entity set the default RawSql if not set.
*/
void setDefaultRawSqlIfRequired();
/**
* Set to true if this query has been tuned by autoTune.
*/
@@ -846,6 +888,21 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
int bufferFetchSizeHint();
/**
* Return true if read auditing is disabled on this query.
*/
boolean isDisableReadAudit();
/**
* Set the readEvent for future queries (as prepared in foreground thread).
*/
void setFutureFetchAudit(ReadEvent event);
/**
* Read the readEvent for future queries (null otherwise).
*/
ReadEvent futureFetchAudit();
/**
* Return the base table to use if user defined on the query.
*/
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import jakarta.persistence.PersistenceException;
import java.sql.Connection;
@@ -102,6 +103,19 @@ public interface SpiTransaction extends Transaction {
*/
Boolean isUpdateAllLoadedProperties();
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
* Returning 0 implies to use the system wide default batch size.
*/
DocStoreMode docStoreMode();
/**
* Return the batch size to us for ElasticSearch Bulk API calls
* as a result of this transaction.
*/
int getDocStoreBatchSize();
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
@@ -273,6 +287,11 @@ public interface SpiTransaction extends Transaction {
*/
void sendChangeLog(ChangeSet changeSet);
/**
* Return a document store transaction.
*/
DocStoreTransaction docStoreTransaction();
/**
* Set the current Tenant Id.
*/
@@ -2,12 +2,14 @@ package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import jakarta.persistence.PersistenceException;
import java.sql.Connection;
@@ -110,6 +112,32 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.tenantId();
}
@Override
public DocStoreTransaction docStoreTransaction() {
return transaction.docStoreTransaction();
}
@Override
public DocStoreMode docStoreMode() {
return transaction.docStoreMode();
}
@Override
public void setDocStoreMode(DocStoreMode mode) {
transaction.setDocStoreMode(mode);
}
@Override
public int getDocStoreBatchSize() {
return transaction.getDocStoreBatchSize();
}
@Override
public void setDocStoreBatchSize(int batchSize) {
transaction.setDocStoreBatchSize(batchSize);
}
@Override
public boolean isLogSql() {
return transaction.isLogSql();
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.transaction.DeleteByIdMap;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import java.io.Serializable;
import java.util.ArrayList;
@@ -124,6 +125,18 @@ public final class TransactionEvent implements Serializable {
return changeSet;
}
/**
* Add any relevant PersistRequestBean's to DocStoreUpdates for later processing.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
List<PersistRequestBean<?>> requests = listenerNotify();
if (requests != null) {
for (PersistRequestBean<?> persistRequestBean : requests) {
persistRequestBean.addDocStoreUpdates(docStoreUpdates);
}
}
}
/**
* Return the CacheChangeSet that we add cache notification messages to.
* <p>
@@ -15,6 +15,7 @@ public final class CachedBeanData implements Externalizable {
private long whenCreated;
private long version;
private String discValue;
private Map<String, Object> data;
/**
* The sharable bean is effectively transient (near cache only).
@@ -24,9 +25,10 @@ public final class CachedBeanData implements Externalizable {
/**
* Construct from a loaded bean.
*/
public CachedBeanData(Object sharableBean, Map<String, Object> data, long version) {
public CachedBeanData(Object sharableBean, String discValue, Map<String, Object> data, long version) {
this.whenCreated = System.currentTimeMillis();
this.sharableBean = sharableBean;
this.discValue = discValue;
this.data = data;
this.version = version;
}
@@ -41,6 +43,11 @@ public final class CachedBeanData implements Externalizable {
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(version);
out.writeLong(whenCreated);
boolean hasDisc = discValue != null;
out.writeBoolean(hasDisc);
if (hasDisc) {
out.writeUTF(discValue);
}
out.writeInt(data.size());
for (Map.Entry<String, Object> entry : data.entrySet()) {
out.writeUTF(entry.getKey());
@@ -52,6 +59,9 @@ public final class CachedBeanData implements Externalizable {
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
version = in.readLong();
whenCreated = in.readLong();
if (in.readBoolean()) {
discValue = in.readUTF();
}
data = new LinkedHashMap<>();
int count = in.readInt();
for (int i = 0; i < count; i++) {
@@ -75,7 +85,7 @@ public final class CachedBeanData implements Externalizable {
Map<String, Object> copy = new HashMap<>();
copy.putAll(data);
copy.putAll(changes);
return new CachedBeanData(null, copy, version);
return new CachedBeanData(null, discValue, copy, version);
}
/**
@@ -92,6 +102,13 @@ public final class CachedBeanData implements Externalizable {
return version;
}
/**
* Return the raw discriminator value.
*/
public String getDiscValue() {
return discValue;
}
/**
* Return a sharable (immutable read only) bean. Near cache only use.
*/
@@ -41,7 +41,7 @@ public final class CachedBeanDataFromBean {
long version = desc.getVersion(bean);
EntityBean sharableBean = createSharableBean(desc, bean, ebi);
return new CachedBeanData(sharableBean, data, version);
return new CachedBeanData(sharableBean, desc.discValue(), data, version);
}
private static EntityBean createSharableBean(BeanDescriptor<?> desc, EntityBean bean, EntityBeanIntercept beanEbi) {
@@ -14,6 +14,9 @@ public final class CachedBeanDataToBean {
// any future lazy loading skips L2 bean cache
ebi.setLoadedFromCache(true);
BeanProperty idProperty = desc.idProperty();
if (desc.inheritInfo() != null) {
desc = desc.inheritInfo().readType(bean.getClass()).desc();
}
if (idProperty != null) {
// load the id property
loadProperty(bean, cacheBeanData, ebi, idProperty, context);
@@ -0,0 +1,54 @@
package io.ebeaninternal.server.cache;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Bean Id value plus discriminator type.
* <p>
* Put into L2 cache such that we know the type of a bean with inheritance.
*/
public final class CachedBeanId implements Externalizable {
private String discValue;
private Object id;
public CachedBeanId(String discValue, Object id) {
this.discValue = discValue;
this.id = id;
}
/**
* Construct from serialisation.
*/
public CachedBeanId() {
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(discValue);
out.writeObject(id);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
discValue = in.readUTF();
id = in.readObject();
}
@Override
public String toString() {
return discValue + ":" + id;
}
public String getDiscValue() {
return discValue;
}
public Object getId() {
return id;
}
}
@@ -0,0 +1,70 @@
package io.ebeaninternal.server.core;
import io.ebean.ProfileLocation;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.SlowQueryEvent;
import java.util.List;
/**
* Slow query event.
*/
final class DSlowQueryEvent implements SlowQueryEvent {
private final String sql;
private final long timeMillis;
private final int rowCount;
private final ObjectGraphNode originNode;
private final List<Object> bindParams;
private final String label;
private final ProfileLocation profileLocation;
/**
* Construct with the SQL and execution time in millis.
*/
DSlowQueryEvent(String sql, long timeMillis, int rowCount, ObjectGraphNode originNode,
List<Object> bindParams, String label, ProfileLocation profileLocation) {
this.sql = sql;
this.timeMillis = timeMillis;
this.rowCount = rowCount;
this.originNode = originNode;
this.bindParams = bindParams;
this.profileLocation = profileLocation;
this.label = label != null ? label : profileLocation == null ? null : profileLocation.label();
}
@Override
public String getSql() {
return sql;
}
@Override
public long getTimeMillis() {
return timeMillis;
}
@Override
public int getRowCount() {
return rowCount;
}
@Override
public ObjectGraphNode getOriginNode() {
return originNode;
}
@Override
public List<Object> getBindParams() {
return bindParams;
}
@Override
public String getLabel() {
return label;
}
@Override
public ProfileLocation getProfileLocation() {
return profileLocation;
}
}
@@ -63,8 +63,8 @@ final class DefaultBeanLoader {
Object parentId = parentDesc.getId(parentBean);
if (pc == null) {
pc = new DefaultPersistenceContext();
parentDesc.contextPut(pc, parentId, parentBean);
}
parentDesc.contextPutIfAbsent(pc, parentId, parentBean);
boolean useManyIdCache = beanCollection != null && parentDesc.isManyPropCaching() && many.isUseCache();
if (useManyIdCache) {
Boolean readOnly = null;
@@ -178,11 +178,12 @@ final class DefaultBeanLoader {
desc.contextPut(pc, id, bean);
ebi.setPersistenceContext(pc);
}
boolean draft = desc.isDraftInstance(bean);
if (embeddedOwnerIndex == -1) {
if (desc.lazyLoadMany(ebi)) {
return;
}
if (Mode.LAZYLOAD_BEAN == mode && desc.isBeanCaching()) {
if (!draft && Mode.LAZYLOAD_BEAN == mode && desc.isBeanCaching()) {
// lazy loading and the bean cache is active
if (desc.cacheBeanLoad(bean, ebi, id, pc)) {
return;
@@ -191,7 +192,9 @@ final class DefaultBeanLoader {
}
SpiQuery<?> query = server.createQuery(desc.type());
query.setLazyLoadProperty(ebi.lazyLoadProperty());
if (mode == SpiQuery.Mode.LAZYLOAD_BEAN && desc.isSoftDelete()) {
if (draft) {
query.asDraft();
} else if (mode == SpiQuery.Mode.LAZYLOAD_BEAN && desc.isSoftDelete()) {
query.setIncludeSoftDeletes();
}
if (embeddedOwnerIndex > -1) {
@@ -92,12 +92,16 @@ public final class DefaultContainer implements SpiContainer {
BootupClasses bootupClasses = bootupClasses(config);
boolean online = true;
TenantMode tenantMode = config.getTenantMode();
if (TenantMode.DB != tenantMode) {
setDataSource(config);
if (!tenantMode.isDynamicDataSource()) {
// check the autoCommit and Transaction Isolation
online = checkDataSource(config);
if (config.isDocStoreOnly()) {
config.databasePlatform(new DatabasePlatform());
} else {
TenantMode tenantMode = config.getTenantMode();
if (TenantMode.DB != tenantMode) {
setDataSource(config);
if (!tenantMode.isDynamicDataSource()) {
// check the autoCommit and Transaction Isolation
online = checkDataSource(config);
}
}
}
@@ -13,6 +13,8 @@ import io.ebean.config.*;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.BeanPersistController;
import io.ebean.event.ShutdownManager;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.*;
import io.ebean.migration.auto.AutoMigrationRunner;
import io.ebean.plugin.BeanType;
@@ -28,9 +30,11 @@ import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.dto.DtoBeanDescriptor;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.el.ElFilter;
import io.ebeaninternal.server.grammer.EqlParser;
import io.ebeaninternal.server.query.*;
import io.ebeaninternal.server.querydefn.*;
import io.ebeaninternal.server.rawsql.SpiRawSql;
@@ -39,6 +43,7 @@ import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.util.ParamTypeHelper;
import io.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import io.ebeanservice.docstore.api.DocStoreIntegration;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.NonUniqueResultException;
@@ -88,6 +93,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final DtoBeanManager dtoBeanManager;
private final BeanDescriptorManager descriptorManager;
private final AutoTuneService autoTuneService;
private final ReadAuditPrepare readAuditPrepare;
private final ReadAuditLogger readAuditLogger;
private final CQueryEngine cqueryEngine;
private final List<Plugin> serverPlugins;
private final SpiDdlGenerator ddlGenerator;
@@ -97,6 +104,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final DefaultBeanLoader beanLoader;
private final EncryptKeyManager encryptKeyManager;
private final SpiJsonContext jsonContext;
private final DocumentStore documentStore;
private final MetaInfoManager metaInfoManager;
private final CurrentTenantProvider currentTenantProvider;
private final SpiLogManager logManager;
@@ -138,12 +146,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.relationalQueryEngine = config.createRelationalQueryEngine();
this.dtoQueryEngine = config.createDtoQueryEngine();
this.autoTuneService = config.createAutoTuneService(this);
this.readAuditPrepare = config.getReadAuditPrepare();
this.readAuditLogger = config.getReadAuditLogger();
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
this.dataTimeZone = config.getDataTimeZone();
this.clockService = config.getClockService();
this.transactionManager = config.createTransactionManager(this);
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor());
this.documentStore = docStoreComponents.documentStore();
this.queryPlanManager = config.initQueryPlanManager(transactionManager);
this.metaInfoManager = new DefaultMetaInfoManager(this, this.config.getMetricNaming());
this.serverPlugins = config.getPlugins();
@@ -177,7 +189,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Execute all the plugins with an online flag indicating the DB is up or not.
*/
public void executePlugins(boolean online) {
ddlGenerator.execute(online);
if (!config.isDocStoreOnly()) {
ddlGenerator.execute(online);
}
for (Plugin plugin : serverPlugins) {
plugin.online(online);
}
@@ -269,6 +283,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return transactionManager.readOnlyDataSource();
}
@Override
public ReadAuditPrepare readAuditPrepare() {
return readAuditPrepare;
}
@Override
public ReadAuditLogger readAuditLogger() {
return readAuditLogger;
}
/**
* Run any initialisation required before registering with the ClusterManager.
*/
@@ -594,7 +618,25 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return (T) existing;
}
}
return (T) desc.contextRef(pc, null, false, id);
InheritInfo inheritInfo = desc.inheritInfo();
if (inheritInfo == null || inheritInfo.isConcrete()) {
return (T) desc.contextRef(pc, null, false, id);
}
return referenceFindOne(type, id, desc);
}
private <T> T referenceFindOne(Class<T> type, Object id, BeanDescriptor<?> desc) {
BeanProperty idProp = desc.idProperty();
if (idProp == null) {
throw new PersistenceException("No ID properties for this type? " + desc);
}
// we actually need to do a query because we don't know the type without the discriminator
// value, just select the id property and discriminator column (auto added)
T bean = find(type).select(idProp.name()).setId(id).findOne();
if (bean == null) {
throw new EntityNotFoundException("Could not find reference bean " + id + " for " + desc);
}
return bean;
}
@Override
@@ -796,6 +838,29 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return query;
}
@Override
public <T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery) {
BeanDescriptor<T> desc = desc(beanType);
String named = desc.namedQuery(namedQuery);
if (named != null) {
return createQuery(beanType, named);
}
SpiRawSql rawSql = desc.namedRawSql(namedQuery);
if (rawSql != null) {
DefaultOrmQuery<T> query = createQuery(beanType);
query.setRawSql(rawSql);
return query;
}
throw new PersistenceException("No named query called " + namedQuery + " for bean:" + beanType.getName());
}
@Override
public <T> DefaultOrmQuery<T> createQuery(Class<T> beanType, String eql) {
DefaultOrmQuery<T> query = createQuery(beanType);
EqlParser.parse(eql, query);
return query;
}
@Override
public <T> DefaultOrmQuery<T> createQuery(Class<T> beanType) {
return new DefaultOrmQuery<>(desc(beanType), this, expressionFactory);
@@ -812,6 +877,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultDtoQuery<>(this, descriptor, sql.trim());
}
@Override
public <T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.descriptor(dtoType);
String sql = descriptor.namedRawSql(namedQuery);
if (sql == null) {
throw new PersistenceException("No named query called " + namedQuery + " for bean:" + dtoType.getName());
}
return new DefaultDtoQuery<>(this, descriptor, sql);
}
@Override
public <T> DtoQuery<T> findDto(Class<T> dtoType, SpiQuery<?> ormQuery) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.descriptor(dtoType);
@@ -875,6 +950,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
transaction = currentServerTransaction();
}
if (!query.isRawSql()) {
query.setDefaultRawSqlIfRequired();
if (!query.isAutoTunable() || !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
@@ -957,6 +1033,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
SpiOrmQueryRequest<T> request = buildQueryRequest(query);
request.prepareQuery();
if (request.isUseDocStore()) {
return docStore().find(request);
}
try {
request.initTransIfRequired();
return (T) request.findId();
@@ -1225,6 +1304,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
spiQuery.usingFuture();
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
if (!spiQuery.isDisableReadAudit()) {
BeanDescriptor<T> desc = descriptorManager.descriptor(spiQuery.getBeanType());
desc.readAuditFutureList(spiQuery);
}
// Create a new transaction solely to execute the findList() at some future time
boolean createdTransaction = false;
SpiTransaction transaction = query.transaction();
@@ -1247,6 +1330,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (maxRows == 0) {
throw new PersistenceException("maxRows must be specified for findPagedList() query");
}
if (query.isUseDocStore()) {
return docStore().findPagedList(createQueryRequest(Type.LIST, query));
}
return new LimitOffsetPagedList<>(this, query);
}
@@ -1274,6 +1360,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> void findEach(SpiQuery<T> query, Consumer<T> consumer) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query);
if (request.isUseDocStore()) {
docStore().findEach(request, consumer);
return;
}
request.initTransIfRequired();
request.findEach(consumer);
// no try finally - findEach guarantee's cleanup of the transaction if required
@@ -1282,6 +1372,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> void findEach(SpiQuery<T> query, int batch, Consumer<List<T>> consumer) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query);
// if (request.isUseDocStore()) {
// docStore().findEach(request, consumer);
// return;
// }
request.initTransIfRequired();
request.findEach(batch, consumer);
// no try finally - findEach guarantee's cleanup of the transaction if required
@@ -1290,6 +1384,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> void findEachWhile(SpiQuery<T> query, Predicate<T> consumer) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query);
if (request.isUseDocStore()) {
docStore().findEachWhile(request, consumer);
return;
}
request.initTransIfRequired();
request.findEachWhile(consumer);
// no try finally - findEachWhile guarantee's cleanup of the transaction if required
@@ -1324,6 +1422,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (result != null) {
return (List<T>) result;
}
if (request.isUseDocStore()) {
return docStore().findList(request);
}
try {
request.initTransIfRequired();
return request.findList();
@@ -1592,6 +1693,54 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}, transaction);
}
@Override
public <T> List<T> publish(Query<T> query, @Nullable Transaction transaction) {
return executeInTrans((txn) -> persister.publish(query, txn), transaction);
}
@Nullable
@Override
public <T> T publish(Class<T> beanType, Object id) {
return publish(beanType, id, null);
}
@Override
public <T> List<T> publish(Query<T> query) {
return publish(query, null);
}
@Nullable
@Override
public <T> T publish(Class<T> beanType, Object id, @Nullable Transaction transaction) {
Query<T> query = find(beanType).setId(id);
List<T> liveBeans = publish(query, transaction);
return (liveBeans.size() == 1) ? liveBeans.get(0) : null;
}
@Override
public <T> List<T> draftRestore(Query<T> query, @Nullable Transaction transaction) {
return executeInTrans((txn) -> persister.draftRestore(query, txn), transaction);
}
@Nullable
@Override
public <T> T draftRestore(Class<T> beanType, Object id, @Nullable Transaction transaction) {
Query<T> query = find(beanType).setId(id);
List<T> beans = draftRestore(query, transaction);
return (beans.size() == 1) ? beans.get(0) : null;
}
@Nullable
@Override
public <T> T draftRestore(Class<T> beanType, Object id) {
return draftRestore(beanType, id, null);
}
@Override
public <T> List<T> draftRestore(Query<T> query) {
return draftRestore(query, null);
}
private EntityBean checkEntityBean(Object bean) {
return (EntityBean) Objects.requireNonNull(bean);
}
@@ -1900,6 +2049,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return descriptorManager.beanTypes(tableName);
}
@Override
public BeanType<?> beanTypeForQueueId(String queueId) {
return descriptorByQueueId(queueId);
}
@Override
public BeanDescriptor<?> descriptorByQueueId(String queueId) {
return descriptorManager.descriptorByQueueId(queueId);
}
/**
* Return the SPI bean types for the given bean class.
*/
@@ -2006,6 +2165,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return callStackFactory.createCallOrigin();
}
@Override
public DocumentStore docStore() {
return documentStore;
}
@Override
public JsonContext json() {
// immutable thread safe so return shared instance
@@ -2020,7 +2184,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void slowQueryCheck(long timeMicros, int rowCount, SpiQuery<?> query) {
if (timeMicros > slowQueryMicros && slowQueryListener != null) {
slowQueryListener.process(new SlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount, query.parentNode()));
List<Object> bindParams = new SlowQueryBindCapture(query).capture();
slowQueryListener.process(new DSlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount,
query.parentNode(), bindParams, query.label(), query.profileLocation()));
}
}
@@ -1,10 +1,12 @@
package io.ebeaninternal.server.core;
import io.avaje.applog.AppLog;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.ProfileLocation;
import io.ebean.config.SlowQueryEvent;
import io.ebean.config.SlowQueryListener;
import java.util.List;
import static java.lang.System.Logger.Level.WARNING;
/**
@@ -16,11 +18,10 @@ final class DefaultSlowQueryListener implements SlowQueryListener {
@Override
public void process(SlowQueryEvent event) {
String firstStack = "";
ObjectGraphNode node = event.getOriginNode();
if (node != null) {
firstStack = node.origin().top();
}
log.log(WARNING, "Slow query warning - millis:{0} rows:{1} caller[{2}] sql[{3}]", event.getTimeMillis(), event.getRowCount(), firstStack, event.getSql());
ProfileLocation profileLocation = event.getProfileLocation();
String loc = profileLocation == null ? "" : profileLocation.fullLocation();
List<Object> bindParams = event.getBindParams();
log.log(WARNING, "Slow query warning - millis:{0} rows:{1} location:{2} sql[{3}] params{4}",
event.getTimeMillis(), event.getRowCount(), loc, event.getSql(), bindParams);
}
}
@@ -0,0 +1,69 @@
package io.ebeaninternal.server.core;
import io.ebeaninternal.api.CoreLog;
import io.ebeaninternal.server.dto.DtoNamedQueries;
import io.ebeaninternal.xmapping.api.XmapDto;
import io.ebeaninternal.xmapping.api.XmapEbean;
import io.ebeaninternal.xmapping.api.XmapRawSql;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.System.Logger.Level.ERROR;
/**
* Reads the Xml deployment information.
*/
final class InternalConfigXmlMap {
private final List<XmapEbean> xmlEbeanList;
private final ClassLoader classLoader;
private final Map<Class<?>, DtoNamedQueries> dtoNamedQueries = new HashMap<>();
InternalConfigXmlMap(List<XmapEbean> xmlEbeanList, ClassLoader classLoader) {
this.xmlEbeanList = xmlEbeanList;
this.classLoader = classLoader;
initDtoMapping();
}
void initDtoMapping() {
if (xmlEbeanList != null) {
for (XmapEbean mapping : xmlEbeanList) {
List<XmapDto> dtoList = mapping.getDto();
for (XmapDto dto : dtoList) {
readDtoMapping(dto);
}
}
}
}
/**
* Return the XML deployment information for entity beans.
*/
List<XmapEbean> xmlDeployment() {
return xmlEbeanList;
}
/**
* Return the named queries for Dto beans.
*/
Map<Class<?>, DtoNamedQueries> readDtoMapping() {
return dtoNamedQueries;
}
private void readDtoMapping(XmapDto dto) {
Class<?> dtoClass;
try {
dtoClass = Class.forName(dto.getClazz(), false, classLoader);
} catch (Exception e) {
CoreLog.internal.log(ERROR, "Could not load dto bean class " + dto.getClazz() + " for ebean xml entry");
return;
}
DtoNamedQueries namedQueries = dtoNamedQueries.computeIfAbsent(dtoClass, aClass -> new DtoNamedQueries());
for (XmapRawSql sql : dto.getRawSql()) {
namedQueries.addRawSql(sql.getName(), sql.getQuery());
}
}
}
@@ -11,6 +11,8 @@ import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.*;
@@ -27,6 +29,7 @@ import io.ebeaninternal.server.core.timezone.*;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import io.ebeaninternal.server.deploy.parse.DeployInherit;
import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
@@ -39,10 +42,18 @@ import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.persist.platform.PostgresMultiValueBind;
import io.ebeaninternal.server.query.*;
import io.ebeaninternal.server.readaudit.DefaultReadAuditLogger;
import io.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
import io.ebeaninternal.server.json.DJsonContext;
import io.ebeaninternal.server.transaction.*;
import io.ebeaninternal.server.type.DefaultTypeManager;
import io.ebeaninternal.server.type.TypeManager;
import io.ebeaninternal.xmapping.api.XmapEbean;
import io.ebeaninternal.xmapping.api.XmapService;
import io.ebeanservice.docstore.api.DocStoreFactory;
import io.ebeanservice.docstore.api.DocStoreIntegration;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
import java.util.*;
@@ -61,6 +72,7 @@ public final class InternalConfiguration {
private final DatabaseBuilder.Settings config;
private final BootupClasses bootupClasses;
private final DatabasePlatform databasePlatform;
private final DeployInherit deployInherit;
private final TypeManager typeManager;
private final DtoBeanManager dtoBeanManager;
private final ClockService clockService;
@@ -77,6 +89,7 @@ public final class InternalConfiguration {
private final ExpressionFactory expressionFactory;
private final SpiBackgroundExecutor backgroundExecutor;
private final JsonFactory jsonFactory;
private final DocStoreFactory docStoreFactory;
private final List<Plugin> plugins = new ArrayList<>();
private final MultiValueBind multiValueBind;
private final SpiLogManager logManager;
@@ -93,6 +106,7 @@ public final class InternalConfiguration {
this.clockService = new ClockService(config.settings().getClock());
this.tableModState = new TableModState();
this.logManager = initLogManager();
this.docStoreFactory = initDocStoreFactory(service(DocStoreFactory.class));
this.jsonFactory = config.getJsonFactory();
this.clusterManager = clusterManager;
this.backgroundExecutor = backgroundExecutor;
@@ -101,24 +115,32 @@ public final class InternalConfiguration {
this.expressionFactory = initExpressionFactory(config);
this.typeManager = new DefaultTypeManager(config, bootupClasses);
this.multiValueBind = createMultiValueBind(databasePlatform.platform());
this.deployInherit = new DeployInherit(bootupClasses);
this.deployCreateProperties = new DeployCreateProperties(typeManager);
this.deployUtil = new DeployUtil(typeManager, config);
this.serverCachePlugin = initServerCachePlugin();
this.cacheManager = initCacheManager();
this.dtoBeanManager = new DtoBeanManager(typeManager);
final InternalConfigXmlMap xmlMap = initExternalMapping();
this.dtoBeanManager = new DtoBeanManager(typeManager, xmlMap.readDtoMapping());
this.beanDescriptorManager = new BeanDescriptorManager(this);
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy(xmlMap.xmlDeployment());
Map<String, String> draftTableMap = beanDescriptorManager.draftTableMap();
beanDescriptorManager.scheduleBackgroundTrim();
this.dataTimeZone = initDataTimeZone();
this.binder = getBinder(typeManager, databasePlatform, dataTimeZone);
this.cQueryEngine = new CQueryEngine(config, databasePlatform, binder, asOfTableMapping);
this.cQueryEngine = new CQueryEngine(config, databasePlatform, binder, asOfTableMapping, draftTableMap);
}
public boolean isJacksonCorePresent() {
return jacksonCorePresent;
}
private InternalConfigXmlMap initExternalMapping() {
final List<XmapEbean> xmEbeans = readExternalMapping();
return new InternalConfigXmlMap(xmEbeans, config.getClassLoadConfig().getClassLoader());
}
private <S> S service(Class<S> cls) {
S service = config.getServiceObject(cls);
if (service != null) {
@@ -128,6 +150,14 @@ public final class InternalConfiguration {
}
}
private List<XmapEbean> readExternalMapping() {
final XmapService xmapService = service(XmapService.class);
if (xmapService == null) {
return Collections.emptyList();
}
return xmapService.read(config.getClassLoadConfig().getClassLoader(), config.getMappingLocations());
}
private SpiLogManager initLogManager() {
// allow plugin - i.e. capture executed SQL for testing/asserts
SpiLoggerFactory loggerFactory = service(SpiLoggerFactory.class);
@@ -148,6 +178,17 @@ public final class InternalConfiguration {
return new DefaultExpressionFactory(config.isExpressionEqualsWithNullAsNoop(), nativeIlike);
}
private DocStoreFactory initDocStoreFactory(DocStoreFactory service) {
return service == null ? new NoneDocStoreFactory() : service;
}
/**
* Return the doc store factory.
*/
public DocStoreFactory getDocStoreFactory() {
return docStoreFactory;
}
ClockService getClockService() {
return clockService;
}
@@ -204,6 +245,22 @@ public final class InternalConfiguration {
return plugin((listener != null) ? listener : jacksonCorePresent ? new DefaultChangeLogListener() : null);
}
/**
* Return the ReadAuditLogger implementation to use.
*/
ReadAuditLogger getReadAuditLogger() {
ReadAuditLogger found = bootupClasses.getReadAuditLogger();
return plugin(found != null ? found : jacksonCorePresent ? new DefaultReadAuditLogger() : null);
}
/**
* Return the ReadAuditPrepare implementation to use.
*/
ReadAuditPrepare getReadAuditPrepare() {
ReadAuditPrepare found = bootupClasses.getReadAuditPrepare();
return plugin(found != null ? found : new DefaultReadAuditPrepare());
}
/**
* For 'As Of' queries return the number of bind variables per predicate.
*/
@@ -290,6 +347,10 @@ public final class InternalConfiguration {
return beanDescriptorManager;
}
public DeployInherit getDeployInherit() {
return deployInherit;
}
public DeployCreateProperties getDeployCreateProperties() {
return deployCreateProperties;
}
@@ -311,19 +372,29 @@ public final class InternalConfiguration {
return new GeneratedPropertyFactory(offlineMode, config, bootupClasses.getIdGenerators());
}
/**
* Create the DocStoreIntegration components for the given server.
*/
DocStoreIntegration createDocStoreIntegration(SpiServer server) {
return plugin(docStoreFactory.create(server));
}
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
TransactionManager createTransactionManager(SpiServer server) {
TransactionManager createTransactionManager(SpiServer server, DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionScopeManager scopeManager = createTransactionScopeManager();
boolean notifyL2CacheInForeground = cacheManager.isLocalL2Caching() || config.isNotifyL2CacheInForeground();
TransactionManagerOptions options =
new TransactionManagerOptions(server, notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
beanDescriptorManager, dataSource(), profileHandler(), logManager,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
if (config.isDocStoreOnly()) {
return new DocStoreTransactionManager(options);
}
return new TransactionManager(options);
}
@@ -114,6 +114,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return server.databasePlatform().escapeLikeString(value);
}
@Override
public void executeSecondaryQueries(boolean forEach) {
// disable lazy loading leaves loadContext null
if (loadContext != null) {
@@ -147,6 +148,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return loadContext;
}
@Override
public boolean isUseDocStore() {
return query.isUseDocStore();
}
/**
* Run BeanQueryAdapter preQuery() if needed.
*/
@@ -219,6 +225,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
persistenceContext.beginIterate();
}
loadContext = new DLoadContext(this, secondaryQueries);
loadContext.useReferences(Type.ITERATE == query.type());
}
/**
@@ -241,6 +248,23 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
}
/**
* Return the JsonReadOptions taking into account lazy loading and persistence context.
*/
@Override
public JsonReadOptions createJsonReadOptions() {
persistenceContext = persistenceContext(query, transaction);
if (query.persistenceContext() == null) {
query.setPersistenceContext(persistenceContext);
}
JsonReadOptions jsonRead = new JsonReadOptions();
jsonRead.setPersistenceContext(persistenceContext);
if (!query.isDisableLazyLoading()) {
loadContext = new DLoadContext(this, secondaryQueries);
jsonRead.setLoadContext(loadContext);
}
return jsonRead;
}
/**
* Get the TransactionContext either explicitly set on the query or
@@ -615,6 +639,17 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
Object cached = beanDescriptor.queryCacheGet(cacheKey);
if (cached != null && isAuditReads() && readAuditQueryType()) {
if (cached instanceof BeanCollection) {
// raw sql can't use L2 cache so normal queries only in here
Collection<T> actualDetails = ((BeanCollection<T>) cached).actualDetails();
List<Object> ids = new ArrayList<>(actualDetails.size());
for (T bean : actualDetails) {
ids.add(beanDescriptor.idForJson(bean));
}
beanDescriptor.readAuditMany(queryPlanKey.partialKey(), "l2-query-cache", ids);
}
}
if (Boolean.FALSE.equals(query.isReadOnly())) {
// return shallow copies if readonly is explicitly set to false
if (cached instanceof BeanCollection) {
@@ -630,6 +665,24 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return cached;
}
/**
* Return true if the query type contains bean data (not just ids etc) and hence we want to include
* it in read auditing. Return false for row count and find ids queries.
*/
private boolean readAuditQueryType() {
Type type = query.type();
switch (type) {
case BEAN:
case ITERATE:
case LIST:
case SET:
case MAP:
return true;
default:
return false;
}
}
public void putToQueryCache(Object result) {
beanDescriptor.queryCachePut(cacheKey, new QueryCacheEntry(result, dependentTables, transaction.startNanoTime()));
}
@@ -656,6 +709,15 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return (batchSize > 0) ? batchSize : server.lazyLoadBatchSize();
}
/**
* Return true if read auditing is on for this query request.
* <p>
* This means that read audit is on for this bean type and that query has not explicitly disabled it.
*/
public boolean isAuditReads() {
return beanDescriptor.isReadAuditing() && !query.isDisableReadAudit();
}
/**
* Return the base table alias for this query.
*/
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
import io.ebean.InsertOptions;
import io.ebean.ValuePair;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PreGetterCallback;
@@ -16,16 +17,21 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.persist.*;
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
import io.ebeanservice.docstore.api.DocStoreUpdate;
import io.ebeanservice.docstore.api.DocStoreUpdateContext;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.OptimisticLockException;
import jakarta.persistence.PersistenceException;
import java.io.IOException;
import java.sql.Statement;
import java.util.*;
/**
* PersistRequest for insert update or delete of a bean.
*/
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T>, PreGetterCallback, SpiProfileTransactionEvent {
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T>, DocStoreUpdate, PreGetterCallback, SpiProfileTransactionEvent {
private final BeanManager<T> beanManager;
private final BeanDescriptor<T> beanDescriptor;
@@ -39,8 +45,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private final Object parentBean;
private final boolean dirty;
private final boolean publish;
private int flags;
private boolean saveRecurse;
private DocStoreMode docStoreMode;
private final ConcurrencyMode concurrencyMode;
/**
* The unique id used for logging summary.
@@ -131,6 +139,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.parentBean = parentBean;
this.controller = beanDescriptor.persistController();
this.type = type;
this.docStoreMode = calcDocStoreMode(transaction, type);
this.flags = flags;
if (Flags.isRecurse(flags)) {
this.persistCascade = t.isPersistCascade();
@@ -146,6 +155,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
this.concurrencyMode = beanDescriptor.concurrencyMode(intercept);
this.publish = Flags.isPublish(flags);
if (isMarkDraftDirty(publish)) {
beanDescriptor.setDraftDirty(entityBean, true);
}
this.dirty = intercept.isDirty();
}
@@ -174,11 +187,29 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
profileBase(type.profileEventId, offset, beanDescriptor.name(), flushCount);
}
/**
* Return the document store event that should be used for this request.
* <p>
* Used to check if the Transaction has set the mode to IGNORE when doing large batch inserts that we
* don't want to send to the doc store.
*/
private DocStoreMode calcDocStoreMode(SpiTransaction txn, Type type) {
DocStoreMode txnMode = (txn == null) ? null : txn.docStoreMode();
return beanDescriptor.docStoreMode(type, txnMode);
}
@Override
public boolean isCascade() {
return Flags.isRecurse(flags);
}
/**
* Return true if the draftDirty property should be set to true for this request.
*/
private boolean isMarkDraftDirty(boolean publish) {
return !publish && type != Type.DELETE && beanDescriptor.isDraftable();
}
/**
* Set the transaction from prior persist request.
* Only used when hard deleting draft & associated live beans.
@@ -193,7 +224,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Init the transaction and also check for batch on cascade escalation.
*/
public void initTransIfRequiredWithBatchCascade() {
createImplicitTransIfRequired();
if (createImplicitTransIfRequired()) {
docStoreMode = calcDocStoreMode(transaction, type);
}
checkBatchEscalationOnCascade();
}
@@ -394,14 +427,21 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Set the cache notify status.
*/
private void setNotifyCache() {
this.notifyCache = beanDescriptor.isCacheNotify(type);
this.notifyCache = beanDescriptor.isCacheNotify(type, publish);
}
/**
* Return true if this change should notify persist listener or doc store (and keep the request).
*/
private boolean isNotifyListeners() {
return isNotifyPersistListener();
return isNotifyPersistListener() || isDocStoreNotify();
}
/**
* Return true if this request should update the document store.
*/
private boolean isDocStoreNotify() {
return docStoreMode != DocStoreMode.IGNORE;
}
private boolean isNotifyPersistListener() {
@@ -430,6 +470,46 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Process the persist request updating the document store.
*/
@Override
public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException {
switch (type) {
case INSERT:
beanDescriptor.docStoreInsert(idValue, this, txn);
break;
case UPDATE:
case DELETE_SOFT:
beanDescriptor.docStoreUpdate(idValue, this, txn);
break;
case DELETE:
beanDescriptor.docStoreDeleteById(idValue, txn);
break;
default:
throw new IllegalStateException("Invalid type " + type);
}
}
/**
* Add this event to the queue entries in IndexUpdates.
*/
@Override
public void addToQueue(DocStoreUpdates docStoreUpdates) {
switch (type) {
case INSERT:
case UPDATE:
case DELETE_SOFT:
docStoreUpdates.queueIndex(beanDescriptor.docStoreQueueId(), idValue);
break;
case DELETE:
docStoreUpdates.queueDelete(beanDescriptor.docStoreQueueId(), idValue);
break;
default:
throw new IllegalStateException("Invalid type " + type);
}
}
public void addToPersistMap(BeanPersistIdMap beanPersistMap) {
beanPersistMap.add(beanDescriptor, type, idValue);
}
@@ -572,6 +652,28 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return beanDescriptor.createRef(beanId(), null);
}
/**
* Return true if the bean type is a Draftable.
*/
public boolean isDraftable() {
return beanDescriptor.isDraftable();
}
/**
* Return true if this request is a hard delete of a draftable bean.
* If this is true Ebean is expected to auto-publish and delete the associated live bean.
*/
public boolean isHardDeleteDraft() {
if (type == Type.DELETE && beanDescriptor.isDraftable() && !beanDescriptor.isDraftableElement()) {
// deleting a top level draftable bean
if (beanDescriptor.isLiveInstance(entityBean)) {
throw new PersistenceException("Explicit Delete is not allowed on a 'live' bean - only draft beans");
}
return true;
}
return false;
}
/**
* Return true if this was a hard/permanent delete request (and should cascade as such).
*/
@@ -579,6 +681,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return (type == Type.DELETE && beanDescriptor.isSoftDelete());
}
/**
* Checks for @Draftable entity beans with @Draft property that the bean is a 'draft'.
* Save or Update is not allowed to execute using 'live' beans - must use publish().
*/
public void checkDraft() {
if (beanDescriptor.isDraftable() && beanDescriptor.isLiveInstance(entityBean)) {
throw new PersistenceException("Save or update is not allowed on a 'live' bean - only draft beans");
}
}
/**
* Return the parent bean for cascading save with unidirectional relationship.
*/
@@ -764,7 +876,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
setNotifyCache();
boolean isChangeLog = beanDescriptor.isChangeLog();
if (type == Type.UPDATE) {
// get the dirty properties for update notification to the doc store
// get the dirty properties for notify cache & orphanRemoval of vanilla collection detection
dirtyProperties = intercept.dirtyProperties();
}
if (isChangeLog) {
@@ -807,38 +919,45 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
private void controllerPost() {
switch (type) {
case INSERT:
controller.postInsert(this);
break;
case UPDATE:
controller.postUpdate(this);
break;
case DELETE_SOFT:
controller.postSoftDelete(this);
break;
case DELETE:
controller.postDelete(this);
break;
default:
break;
boolean old = transaction.isFlushOnQuery();
transaction.setFlushOnQuery(false);
try {
switch (type) {
case INSERT:
controller.postInsert(this);
break;
case UPDATE:
controller.postUpdate(this);
break;
case DELETE_SOFT:
controller.postSoftDelete(this);
break;
case DELETE:
controller.postDelete(this);
break;
default:
break;
}
} finally {
transaction.setFlushOnQuery(old);
}
}
private void logSummaryMessage() {
String draft = (beanDescriptor.isDraftable() && !publish) ? " draft[true]" : "";
String name = beanDescriptor.name();
switch (type) {
case INSERT:
transaction.logSummary("Inserted [{0}] [{1}]", name, (idValue == null ? "" : idValue));
transaction.logSummary("Inserted [{0}] [{1}]{2}", name, (idValue == null ? "" : idValue), draft);
break;
case UPDATE:
transaction.logSummary("Updated [{0}] [{1}]", name, idValue);
transaction.logSummary("Updated [{0}] [{1}]{2}", name, idValue , draft);
break;
case DELETE:
transaction.logSummary("Deleted [{0}] [{1}]", name, idValue);
transaction.logSummary("Deleted [{0}] [{1}]{2}", name, idValue , draft);
break;
case DELETE_SOFT:
transaction.logSummary("SoftDelete [{0}] [{1}]", name, idValue);
transaction.logSummary("SoftDelete [{0}] [{1}]{2}", name, idValue , draft);
break;
default:
break;
@@ -876,6 +995,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private void postInsert() {
// mark all properties as loaded after an insert to support immediate update
beanDescriptor.setAllLoaded(entityBean);
if (!publish) {
beanDescriptor.setDraft(entityBean);
}
if (transaction.isAutoPersistUpdates() && idValue != null) {
// with getGeneratedKeys off we will not have a idValue
beanDescriptor.contextPut(transaction.persistenceContext(), idValue, entityBean);
@@ -972,6 +1094,32 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* For requests that update document store add this event to either the list
* of queue events or list of update events.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
if (type == Type.UPDATE) {
beanDescriptor.docStoreUpdateEmbedded(this, docStoreUpdates);
}
switch (docStoreMode) {
case UPDATE: {
docStoreUpdates.addPersist(this);
return;
}
case QUEUE: {
if (type == Type.DELETE) {
docStoreUpdates.queueDelete(beanDescriptor.docStoreQueueId(), idValue);
} else {
docStoreUpdates.queueIndex(beanDescriptor.docStoreQueueId(), idValue);
}
}
break;
default:
break;
}
}
/**
* Determine if all loaded properties should be used for an update.
* <p>
@@ -997,6 +1145,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return flags;
}
/**
* Return true if this request is a 'publish' action.
*/
public boolean isPublish() {
return publish;
}
/**
* Return the key for an update persist request.
*/
@@ -1013,14 +1168,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
key.append('v');
}
}
if (publish) {
key.append('p');
}
return key.toString();
}
/**
* Return the table to update.
* Return the table to update depending if the request is a 'publish' one or normal.
*/
public String updateTable() {
return beanDescriptor.baseTable();
return publish ? beanDescriptor.baseTable() : beanDescriptor.draftTable();
}
/**
@@ -1089,6 +1247,30 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return 0;
}
/**
* Persist to the document store now (via buffer, not post commit).
*/
public void docStorePersist() {
idValue = beanDescriptor.getId(entityBean);
if (type == Type.UPDATE) {
dirtyProperties = intercept.dirtyProperties();
}
// processing now so set IGNORE (unlike DB + DocStore processing with post-commit)
docStoreMode = DocStoreMode.IGNORE;
try {
docStoreUpdate(transaction.docStoreTransaction().obtain());
postExecute();
if (type == Type.UPDATE
&& beanDescriptor.isDocStoreEmbeddedInvalidation()
&& transaction.isPersistCascade()) {
// queue embedded/nested updates for later processing
beanDescriptor.docStoreUpdateEmbedded(this, transaction.docStoreTransaction().queue());
}
} catch (IOException e) {
throw new PersistenceException("Error persisting doc store bean", e);
}
}
/**
* Use a common 'now' value across both when created and when updated etc.
*/
@@ -1208,7 +1390,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Return the SQL used to fetch the last inserted id value.
*/
public String selectLastInsertedId() {
return beanDescriptor.selectLastInsertedId();
return beanDescriptor.selectLastInsertedId(publish);
}
/**
@@ -78,6 +78,16 @@ public interface Persister {
*/
int executeCallable(CallableSql callable, Transaction t);
/**
* Publish the draft beans matching the given query.
*/
<T> List<T> publish(Query<T> query, Transaction transaction);
/**
* Restore the draft beans back to the matching live beans.
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
/**
* Visit the metrics.
*/
@@ -0,0 +1,71 @@
package io.ebeaninternal.server.core;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiExpressionBind;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import java.util.ArrayList;
import java.util.List;
final class SlowQueryBindCapture implements SpiExpressionBind {
private final SpiQuery<?> query;
private final List<Object> bindParams = new ArrayList<>();
SlowQueryBindCapture(SpiQuery<?> query) {
this.query = query;
}
List<Object> capture() {
var params = query.bindParams();
if (params != null) {
var positionedParameters = params.positionedParameters();
for (BindParams.Param param : positionedParameters) {
if (param.isInParam()) {
add(param.inValue());
}
}
}
Object id = query.getId();
if (id != null) {
add(id);
}
SpiExpressionList<?> spiExpressionList = query.whereExpressions();
if (spiExpressionList != null) {
spiExpressionList.addBindValues(this);
}
return bindParams;
}
private void add(Object value) {
if (value instanceof MultiValueWrapper) {
var mvw = (MultiValueWrapper) value;
bindParams.add(mvw.getValues());
} else {
bindParams.add(value);
}
}
@Override
public BeanDescriptor<?> descriptor() {
return query.descriptor();
}
@Override
public void addBindValue(Object bindValue) {
add(bindValue);
}
@Override
public void addBindEncryptKey(Object encryptKey) {
bindParams.add("*");
}
@Override
public String escapeLikeString(String value) {
return query.descriptor().ebeanServer().databasePlatform().escapeLikeString(value);
}
}
@@ -5,6 +5,7 @@ import io.ebean.Version;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeanservice.docstore.api.DocQueryRequest;
import java.util.Collection;
import java.util.List;
@@ -16,7 +17,7 @@ import java.util.function.Predicate;
/**
* Defines the ORM query request api.
*/
public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T> {
public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequest<T> {
/**
* Return the query.
@@ -177,6 +178,11 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T> {
*/
void markNotQueryOnly();
/**
* Return true if this query is expected to use the doc store.
*/
boolean isUseDocStore();
/**
* Return true if delete by statement is allowed for this type given cascade rules etc.
*/
@@ -9,6 +9,8 @@ import io.ebean.event.*;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.CoreLog;
@@ -66,10 +68,14 @@ public class BootupClasses implements Predicate<Class<?>> {
private Class<? extends ChangeLogPrepare> changeLogPrepareClass;
private Class<? extends ChangeLogListener> changeLogListenerClass;
private Class<? extends ChangeLogRegister> changeLogRegisterClass;
private Class<? extends ReadAuditPrepare> readAuditPrepareClass;
private Class<? extends ReadAuditLogger> readAuditLoggerClass;
private ChangeLogPrepare changeLogPrepare;
private ChangeLogListener changeLogListener;
private ChangeLogRegister changeLogRegister;
private ReadAuditPrepare readAuditPrepare;
private ReadAuditLogger readAuditLogger;
public BootupClasses() {
}
@@ -167,11 +173,19 @@ public class BootupClasses implements Predicate<Class<?>> {
}
public void addChangeLogInstances(DatabaseBuilder.Settings config) {
readAuditPrepare = config.getReadAuditPrepare();
readAuditLogger = config.getReadAuditLogger();
changeLogPrepare = config.getChangeLogPrepare();
changeLogListener = config.getChangeLogListener();
changeLogRegister = config.getChangeLogRegister();
// if not already set create the implementations found
// via classpath scanning
if (readAuditPrepare == null && readAuditPrepareClass != null) {
readAuditPrepare = create(readAuditPrepareClass, false);
}
if (readAuditLogger == null && readAuditLoggerClass != null) {
readAuditLogger = create(readAuditLoggerClass, false);
}
if (changeLogPrepare == null && changeLogPrepareClass != null) {
changeLogPrepare = create(changeLogPrepareClass, false);
}
@@ -238,6 +252,14 @@ public class BootupClasses implements Predicate<Class<?>> {
return changeLogRegister;
}
public ReadAuditPrepare getReadAuditPrepare() {
return readAuditPrepare;
}
public ReadAuditLogger getReadAuditLogger() {
return readAuditLogger;
}
public List<IdGenerator> getIdGenerators() {
return createAdd(idGeneratorInstances, idGeneratorCandidates);
}
@@ -402,6 +424,15 @@ public class BootupClasses implements Predicate<Class<?>> {
interesting = true;
}
if (ReadAuditPrepare.class.isAssignableFrom(cls)) {
readAuditPrepareClass = (Class<? extends ReadAuditPrepare>) cls;
interesting = true;
}
if (ReadAuditLogger.class.isAssignableFrom(cls)) {
readAuditLoggerClass = (Class<? extends ReadAuditLogger>) cls;
interesting = true;
}
return interesting;
}
@@ -0,0 +1,101 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.query.SqlTreeJoin;
import java.sql.SQLException;
/**
* Helper for BeanPropertyAssocOne imported reference but with inheritance.
*/
final class AssocOneHelpRefInherit extends AssocOneHelp {
private final InheritInfo inherit;
AssocOneHelpRefInherit(BeanPropertyAssocOne<?> property) {
super(property);
this.inherit = property.targetInheritInfo;
}
@Override
void loadIgnore(DbReadContext ctx) {
property.targetIdBinder.loadIgnore(ctx);
ctx.dataReader().incrementPos(1);
}
/**
* Read and set a Reference bean.
*/
@Override
Object read(DbReadContext ctx) throws SQLException {
// read discriminator to determine the type
InheritInfo rowInheritInfo = inherit.readType(ctx);
BeanDescriptor<?> desc;
if (rowInheritInfo != null) {
desc = rowInheritInfo.desc();
} else if (!inherit.hasChildren()) {
desc = inherit.desc();
} else {
// ignore the id property
property.targetIdBinder.loadIgnore(ctx);
return null;
}
Object id = property.targetIdBinder.read(ctx);
if (id == null) {
return null;
}
// check transaction context to see if it already exists
PersistenceContext pc = ctx.persistenceContext();
Object existing = desc.contextGet(pc, id);
if (existing != null) {
return existing;
}
// for inheritance hierarchy create the correct type for this row...
boolean disableLazyLoading = ctx.isDisableLazyLoading();
Object ref = desc.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
if (!disableLazyLoading) {
ctx.registerBeanInherit(property, ((EntityBean) ref)._ebean_getIntercept());
}
return ref;
}
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
// add join to support the discriminator column
String relativePrefix = ctx.relativePrefix(property.name);
ctx.addExtraJoin(new Extra(relativePrefix, joinType));
}
/**
* Extra join to support the discriminator column.
*/
final class Extra implements SqlTreeJoin {
final String relativePrefix;
final SqlJoinType joinType;
Extra(String relativePrefix, SqlJoinType joinType) {
this.relativePrefix = relativePrefix;
this.joinType = joinType;
}
@Override
public void addJoin(DbSqlContext ctx) {
// add join to support the discriminator column *IF* join is not already present
property.tableJoin.addJoin(joinType, relativePrefix, ctx);
}
}
/**
* Append columns for foreign key columns.
*/
@Override
void appendSelect(DbSqlContext ctx, boolean subQuery) {
if (!subQuery) {
// add discriminator column
String relativePrefix = ctx.relativePrefix(property.name());
String tableAlias = ctx.tableAlias(relativePrefix);
ctx.appendColumn(tableAlias, property.targetInheritInfo.getDiscriminatorColumn());
}
property.importedId.sqlAppend(ctx);
}
}
@@ -14,9 +14,13 @@ import io.ebean.event.*;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeLogFilter;
import io.ebean.event.changelog.ChangeType;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanInit;
import io.ebean.plugin.BeanDocType;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.ExpressionPath;
import io.ebean.plugin.Property;
@@ -41,8 +45,15 @@ import io.ebeaninternal.server.query.*;
import io.ebeaninternal.server.querydefn.DefaultOrmQuery;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.util.SortByClause;
import io.ebeaninternal.util.SortByClauseParser;
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
import io.ebeanservice.docstore.api.DocStoreUpdateContext;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import io.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import io.ebeanservice.docstore.api.mapping.DocPropertyMapping;
import io.ebeanservice.docstore.api.mapping.DocumentMapping;
import jakarta.persistence.PersistenceException;
import java.io.IOException;
@@ -78,6 +89,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private final ConcurrentHashMap<String, STreeProperty> dynamicProperty = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Map<String, String>> pathMaps = new ConcurrentHashMap<>();
private final Map<String, SpiRawSql> namedRawSql;
private final Map<String, String> namedQuery;
private final boolean multiValueSupported;
private boolean batchEscalateOnCascadeInsert;
private boolean batchEscalateOnCascadeDelete;
@@ -95,6 +108,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* getGeneratedKeys is not supported.
*/
private final String selectLastInsertedId;
private final String selectLastInsertedIdDraft;
private final boolean autoTunable;
private final ConcurrencyMode concurrencyMode;
private final IndexDefinition[] indexDefinitions;
@@ -106,12 +120,18 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private final TableJoin primaryKeyJoin;
private final BeanProperty softDeleteProperty;
private final boolean softDelete;
private final String draftTable;
private final PartitionMeta partitionMeta;
private final TablespaceMeta tablespaceMeta;
private final String storageEngine;
private final String dbComment;
private final boolean readAuditing;
private final boolean draftable;
private final boolean draftableElement;
private final BeanProperty unmappedJson;
private final BeanProperty tenant;
private final BeanProperty draft;
private final BeanProperty draftDirty;
private final LinkedHashMap<String, BeanProperty> propMap;
/**
* Map of DB column to property path (for nativeSql mapping).
@@ -134,6 +154,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private final BeanQueryAdapter queryAdapter;
private final BeanFindController beanFinder;
private final ChangeLogFilter changeLogFilter;
final InheritInfo inheritInfo;
private final boolean abstractType;
private final BeanProperty idProperty;
private final int idPropertyIndex;
@@ -145,6 +166,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* Properties that are initialised in the constructor need to be 'unloaded' to support partial object queries.
*/
private final int[] unloadProperties;
/**
* Properties local to this type (not from a super type).
*/
private final BeanProperty[] propertiesLocal;
/**
* Scalar mutable properties (need to dirty check on update).
*/
@@ -198,8 +223,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private final String name;
private final String baseTableAlias;
private final boolean cacheSharableBeans;
private final String docStoreQueueId;
private final BeanDescriptorDraftHelp<T> draftHelp;
private final BeanDescriptorCacheHelp<T> cacheHelp;
private final BeanDescriptorJsonHelp<T> jsonHelp;
private DocStoreBeanAdapter<T> docStoreAdapter;
private DocumentMapping docMapping;
private boolean docStoreEmbeddedInvalidation;
private final String defaultSelectClause;
private SpiEbeanServer ebeanServer;
@@ -215,6 +245,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
this.rootBeanType = PersistenceContextUtil.root(beanType);
this.prototypeEntityBean = createPrototypeEntityBean(beanType);
this.iudMetrics = new BeanIudMetrics(name);
this.namedQuery = deploy.getNamedQuery();
this.namedRawSql = deploy.getNamedRawSql();
this.inheritInfo = deploy.getInheritInfo();
this.beanFinder = deploy.getBeanFinder();
this.persistController = deploy.getPersistController();
this.persistListener = deploy.getPersistListener();
@@ -228,9 +261,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
this.idGeneratedValue = deploy.isIdGeneratedValue();
this.idGenerator = deploy.getIdGenerator();
this.selectLastInsertedId = deploy.getSelectLastInsertedId();
this.selectLastInsertedIdDraft = deploy.getSelectLastInsertedIdDraft();
this.concurrencyMode = deploy.getConcurrencyMode();
this.indexDefinitions = deploy.getIndexDefinitions();
this.readAuditing = deploy.isReadAuditing();
this.draftable = deploy.isDraftable();
this.draftableElement = deploy.isDraftableElement();
this.historySupport = deploy.isHistorySupport();
this.draftTable = deploy.getDraftTable();
this.baseTable = InternString.intern(deploy.getBaseTable());
this.baseTableAsOf = deploy.getBaseTableAsOf();
this.primaryKeyJoin = deploy.getPrimaryKeyJoin();
@@ -252,11 +290,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
this.versionProperty = listHelper.getVersionProperty();
this.unmappedJson = listHelper.getUnmappedJson();
this.tenant = listHelper.getTenant();
this.draft = listHelper.getDraft();
this.draftDirty = listHelper.getDraftDirty();
this.propMap = listHelper.getPropertyMap();
this.propertiesTransient = listHelper.getTransients();
this.propertiesNonTransient = listHelper.getNonTransients();
this.propertiesBaseScalar = listHelper.getBaseScalar();
this.propertiesEmbedded = listHelper.getEmbedded();
this.propertiesLocal = listHelper.getLocal();
this.propertiesMutable = listHelper.getMutable();
this.unidirectional = listHelper.getUnidirectional();
this.orderColumn = listHelper.getOrderColumn();
@@ -279,6 +320,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly();
this.cacheHelp = new BeanDescriptorCacheHelp<>(this, owner.cacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
this.jsonHelp = initJsonHelp();
this.draftHelp = new BeanDescriptorDraftHelp<>(this);
this.docStoreAdapter = owner.createDocStoreBeanAdapter(this, deploy);
this.docStoreQueueId = docStoreAdapter.queueId();
// Check if there are no cascade save associated beans ( subject to change
// in initialiseOther()). Note that if we are in an inheritance hierarchy
// then we also need to check every BeanDescriptors in the InheritInfo as
@@ -438,10 +482,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* as they are used to get the imported and exported properties.
*/
void initialiseId(BeanDescriptorInitContext initContext) {
if (draftable) {
initContext.addDraft(baseTable, draftTable);
}
if (historySupport) {
// add mapping (used to swap out baseTable for asOf queries)
initContext.addHistory(baseTable, baseTableAsOf);
}
if (inheritInfo != null) {
inheritInfo.setDescriptor(this);
}
// initialise just the Id property only
if (idProperty != null) {
idProperty.initialise(initContext);
@@ -452,6 +503,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* Initialise the exported and imported parts for associated properties.
*/
public void initialiseOther(BeanDescriptorInitContext initContext) {
for (BeanPropertyAssocMany<?> many : propertiesManyToMany) {
// register associated draft table for M2M intersection
many.registerDraftIntersectionTable(initContext);
}
if (historySupport) {
// history support on this bean so check all associated intersection tables
// and if they are not excluded register the associated 'with history' table
@@ -552,6 +607,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
}
}
docStoreEmbeddedInvalidation = docStoreAdapter.hasEmbeddedInvalidation();
}
private void addUniqueColumns(IndexDefinition indexDef) {
@@ -587,6 +643,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
for (BeanPropertyAssocOne<?> one : propertiesOne) {
one.initialisePostTarget();
}
if (inheritInfo != null && !inheritInfo.isRoot()) {
docStoreAdapter = (DocStoreBeanAdapter<T>) inheritInfo.getRoot().desc().docStoreAdapter();
}
docMapping = docStoreAdapter.createDocMapping();
docStoreAdapter.registerPaths();
cacheHelp.deriveNotifyFlags();
}
@@ -614,6 +675,18 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return type == PersistRequest.Type.INSERT ? batchEscalateOnCascadeInsert : batchEscalateOnCascadeDelete;
}
void initInheritInfo() {
if (inheritInfo != null) {
// need to check every BeanDescriptor in the inheritance hierarchy
if (saveRecurseSkippable) {
saveRecurseSkippable = inheritInfo.isSaveRecurseSkippable();
}
if (deleteRecurseSkippable) {
deleteRecurseSkippable = inheritInfo.isDeleteRecurseSkippable();
}
}
}
public void metricPersistBatch(PersistRequest.Type type, long startNanos, int size) {
iudMetrics.addBatch(type, startNanos, size);
}
@@ -650,6 +723,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
}
/**
* Return the ReadAuditLogger for logging read audit events.
*/
public ReadAuditLogger readAuditLogger() {
return ebeanServer.readAuditLogger();
}
/**
* Return the ReadAuditPrepare for preparing read audit events prior to logging.
*/
private ReadAuditPrepare readAuditPrepare() {
return ebeanServer.readAuditPrepare();
}
public boolean isChangeLog() {
return changeLogFilter != null;
}
@@ -852,6 +939,166 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return defaultSelectClause;
}
/**
* Return true if this object is the root level object in its entity
* inheritance.
*/
@Override
public boolean isInheritanceRoot() {
return inheritInfo == null || inheritInfo.isRoot();
}
/**
* Return true if this type maps to a root type of a doc store document (not embedded or ignored).
*/
@Override
public boolean isDocStoreMapped() {
return docStoreAdapter.mapped();
}
/**
* Return true if this bean type has embedded doc store invalidation.
*/
public boolean isDocStoreEmbeddedInvalidation() {
return docStoreEmbeddedInvalidation;
}
/**
* Return the queueId used to uniquely identify this type when queuing an index updateAdd.
*/
@Override
public String docStoreQueueId() {
return docStoreQueueId;
}
@Override
public DocumentMapping docMapping() {
return docMapping;
}
/**
* Return the doc store helper for this bean type.
*/
@Override
public BeanDocType<T> docStore() {
return docStoreAdapter;
}
/**
* Return doc store adapter for internal use for processing persist requests.
*/
public DocStoreBeanAdapter<T> docStoreAdapter() {
return docStoreAdapter;
}
/**
* Build the Document mapping recursively with the given prefix relative to the root of the document.
*/
public void docStoreMapping(final DocMappingBuilder mapping, final String prefix) {
if (prefix != null && idProperty != null) {
// id property not included in the
idProperty.docStoreMapping(mapping, prefix);
}
if (inheritInfo != null) {
String discCol = inheritInfo.getDiscriminatorColumn();
if (Types.VARCHAR == inheritInfo.getDiscriminatorType()) {
mapping.add(new DocPropertyMapping(discCol, DocPropertyType.ENUM));
} else {
mapping.add(new DocPropertyMapping(discCol, DocPropertyType.INTEGER));
}
}
for (BeanProperty prop : propertiesNonTransient) {
prop.docStoreMapping(mapping, prefix);
}
if (inheritInfo != null) {
inheritInfo.visitChildren(inheritInfo1 -> {
for (BeanProperty localProperty : inheritInfo1.localProperties()) {
localProperty.docStoreMapping(mapping, prefix);
}
});
}
}
/**
* Return the root bean type if part of inheritance hierarchy.
*/
@Override
public BeanType<?> root() {
if (inheritInfo != null && !inheritInfo.isRoot()) {
return inheritInfo.getRoot().desc();
}
return this;
}
/**
* Return the full name taking into account inheritance.
*/
public String rootName() {
if (inheritInfo != null && !inheritInfo.isRoot()) {
return inheritInfo.getRoot().desc().name();
}
return name;
}
/**
* Return the named ORM query.
*/
public String namedQuery(String name) {
return namedQuery.get(name);
}
/**
* Return the named RawSql query.
*/
public SpiRawSql namedRawSql(String named) {
return namedRawSql.get(named);
}
/**
* Return the type of DocStoreMode that should occur for this type of persist request
* given the transactions requested mode.
*/
public DocStoreMode docStoreMode(PersistRequest.Type persistType, DocStoreMode txnMode) {
return docStoreAdapter.mode(persistType, txnMode);
}
public void docStoreInsert(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException {
docStoreAdapter.insert(idValue, persistRequest, bulkUpdate);
}
public void docStoreUpdate(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException {
docStoreAdapter.update(idValue, persistRequest, bulkUpdate);
}
/**
* Check if this update invalidates an embedded part of a doc store document.
*/
public void docStoreUpdateEmbedded(PersistRequestBean<T> request, DocStoreUpdates docStoreUpdates) {
docStoreAdapter.updateEmbedded(request, docStoreUpdates);
}
public void docStoreDeleteById(Object idValue, DocStoreUpdateContext txn) throws IOException {
docStoreAdapter.deleteById(idValue, txn);
}
public T publish(T draftBean, T liveBean) {
return draftHelp.publish(draftBean, liveBean);
}
/**
* Reset properties on the draft bean based on @DraftDirty and @DraftReset.
*/
public boolean draftReset(T draftBean) {
return draftHelp.draftReset(draftBean);
}
/**
* Return the draft dirty boolean property or null if there is not one assigned to this bean type.
*/
BeanProperty draftDirty() {
return draftDirty;
}
/**
* Prepare the query for multi-tenancy check for document store only use.
*/
@@ -862,6 +1109,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
tenant.addTenant(query, tenantId);
}
}
if (isDocStoreOnly()) {
query.setUseDocStore(true);
}
}
/**
@@ -901,7 +1151,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
/**
* Return true if the persist request needs to notify the cache.
*/
public boolean isCacheNotify(PersistRequest.Type type) {
public boolean isCacheNotify(PersistRequest.Type type, boolean publish) {
if (draftable && !publish) {
// no caching when editing draft beans
return false;
}
return cacheHelp.isCacheNotify(type);
}
@@ -1118,6 +1372,52 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
cacheHelp.cacheBeanUpdate(key, changes, updateNaturalKey, version);
}
/**
* Prepare the read audit of a findFutureList() query.
*/
public void readAuditFutureList(SpiQuery<T> spiQuery) {
if (isReadAuditing()) {
ReadEvent event = new ReadEvent(fullName);
// prepare in the foreground thread while we have the user context
// information (query is processed/executed later in bg thread)
readAuditPrepare(event);
spiQuery.setFutureFetchAudit(event);
}
}
/**
* Write a bean read to the read audit log.
*/
public void readAuditBean(String queryKey, String bindLog, Object bean) {
ReadEvent event = new ReadEvent(fullName, queryKey, bindLog, idForJson(bean));
readAuditPrepare(event);
readAuditLogger().auditBean(event);
}
private void readAuditPrepare(ReadEvent event) {
ReadAuditPrepare prepare = readAuditPrepare();
if (prepare != null) {
prepare.prepare(event);
}
}
/**
* Write a many bean read to the read audit log.
*/
public void readAuditMany(String queryKey, String bindLog, List<Object> ids) {
ReadEvent event = new ReadEvent(fullName, queryKey, bindLog, ids);
readAuditPrepare(event);
readAuditLogger().auditMany(event);
}
/**
* Write a futureList many read to the read audit log.
*/
public void readAuditFutureMany(ReadEvent event) {
// this has already been prepared (in foreground thread)
readAuditLogger().auditMany(event);
}
/**
* Return the base table alias. This is always the first letter of the bean name.
*/
@@ -1495,11 +1795,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
if (d != null) {
Object shareableBean = d.getSharableBean();
if (shareableBean != null) {
if (isReadAuditing()) {
readAuditBean("ref", "", shareableBean);
}
return (T) shareableBean;
}
}
}
try {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
id = convertSetId(id, eb);
EntityBeanIntercept ebi = eb._ebean_getIntercept();
@@ -1534,6 +1840,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* Create a non read only reference bean without checking cacheSharableBeans.
*/
public T createReference(Object id, PersistenceContext pc) {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
return createRef(id, pc);
}
@@ -1632,7 +1941,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* visible.
*/
public BeanPropertyAssocOne<?> unidirectional() {
return unidirectional;
BeanDescriptor<?> other = this;
while (true) {
if (other.unidirectional != null) {
return other.unidirectional;
}
if (other.inheritInfo != null && !other.inheritInfo.isRoot()) {
other = other.inheritInfo.getParent().desc();
continue;
}
return null;
}
}
/**
@@ -1896,6 +2215,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
if (lazyLoadProperty == -1) {
return false;
}
if (inheritInfo != null) {
return descOf(ebi.owner().getClass()).lazyLoadMany(ebi, lazyLoadProperty, parent);
}
return lazyLoadMany(ebi, lazyLoadProperty, parent);
}
@@ -1923,6 +2245,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return false;
}
/**
* Return the correct BeanDescriptor based on the bean class type.
*/
BeanDescriptor<?> descOf(Class<?> type) {
return inheritInfo.readType(type).desc();
}
/**
* Return a Comparator for local sorting of lists.
*
@@ -2115,8 +2444,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
BeanProperty _findBeanProperty(String propName) {
BeanProperty prop = propMap.get(propName);
if (prop == null && "_$IdClass$".equals(propName)) {
return idProperty;
if (prop == null) {
if ("_$IdClass$".equals(propName)) {
return idProperty;
} else if (inheritInfo != null) {
// search in sub types...
return inheritInfo.findSubTypeProperty(propName);
}
}
return prop;
}
@@ -2150,6 +2484,45 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return autoTunable;
}
/**
* Returns the Inheritance mapping information. This will be null if this type
* of bean is not involved in any ORM inheritance mapping.
*/
@Override
public InheritInfo inheritInfo() {
return inheritInfo;
}
@Override
public boolean hasInheritance() {
return inheritInfo != null;
}
@Override
public String discColumn() {
return inheritInfo.getDiscriminatorColumn();
}
/**
* Return the discriminator value for this bean type (or null when there is no inheritance).
*/
public String discValue() {
return inheritInfo == null ? null : inheritInfo.getDiscriminatorStringValue();
}
@Override
@SuppressWarnings("unchecked")
public T createBeanUsingDisc(Object discValue) {
return (T) inheritInfo.getType(discValue.toString()).desc().createBean();
}
@Override
public void addInheritanceWhere(Query<?> query) {
if (inheritInfo != null && !inheritInfo.isRoot()) {
query.where().eq(inheritInfo.getDiscriminatorColumn(), inheritInfo.getDiscriminatorValue());
}
}
/**
* Return true if this is an embedded bean.
*/
@@ -2342,6 +2715,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
@Override
public String baseTable(SpiQuery.TemporalMode mode) {
switch (mode) {
case DRAFT:
return draftTable;
case VERSIONS:
return baseTableVersionsBetween;
case AS_OF:
@@ -2351,6 +2726,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
}
/**
* Return the associated draft table.
*/
public String draftTable() {
return draftTable;
}
/**
* Return true if read auditing is on this entity bean.
*/
public boolean isReadAuditing() {
return readAuditing;
}
@Override
public boolean isSoftDelete() {
return softDelete;
@@ -2382,6 +2771,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
}
/**
* Return true if this entity type is draftable.
*/
public boolean isDraftable() {
return draftable;
}
/**
* Return true if this entity type is a draftable element (child).
*/
public boolean isDraftableElement() {
return draftableElement;
}
@Override
public Map<String, String> pathMap(String prefix) {
return pathMaps.computeIfAbsent(prefix, s -> {
@@ -2447,6 +2850,28 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
}
/**
* Set the draft to true for this entity bean instance.
* This bean is being loaded via asDraft() query.
*/
@Override
public void setDraft(EntityBean entityBean) {
if (draft != null) {
draft.setValue(entityBean, true);
}
}
/**
* Return true if the bean is considered a 'draft' instance (not 'live').
*/
public boolean isDraftInstance(EntityBean entityBean) {
if (draft != null) {
return Boolean.TRUE == draft.getValue(entityBean);
}
// no draft property - so return false
return false;
}
@Override
public boolean isToManyDirty(EntityBean bean) {
final EntityBeanIntercept ebi = bean._ebean_getIntercept();
@@ -2461,6 +2886,39 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return false;
}
/**
* Return true if the bean is draftable and considered a 'live' instance.
*/
public boolean isLiveInstance(EntityBean entityBean) {
if (draft != null) {
return Boolean.FALSE == draft.getValue(entityBean);
}
// no draft property - so return false
return false;
}
/**
* If there is a @DraftDirty property set it's value on the bean.
*/
public void setDraftDirty(EntityBean entityBean, boolean value) {
if (draftDirty != null) {
// check to see if the dirty property has already
// been set and if so do not set the value
if (!entityBean._ebean_getIntercept().isChangedProperty(draftDirty.propertyIndex())) {
draftDirty.setValueIntercept(entityBean, value);
}
}
}
/**
* Optimise the draft query fetching any draftable element relationships.
*/
public void draftQueryOptimise(Query<T> query) {
// use per query PersistenceContext to ensure fresh beans loaded
query.setPersistenceContextScope(PersistenceContextScope.QUERY);
draftHelp.draftQueryOptimise(query);
}
/**
* Return true if this entity bean has history support.
*/
@@ -2506,8 +2964,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* This is only used with Identity columns and getGeneratedKeys is not
* supported.
*/
public String selectLastInsertedId() {
return selectLastInsertedId;
public String selectLastInsertedId(boolean publish) {
return publish ? selectLastInsertedId : selectLastInsertedIdDraft;
}
/**
@@ -2877,6 +3335,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return propertiesBaseScalar;
}
/**
* Return the properties local to this type for inheritance.
*/
public BeanProperty[] propertiesLocal() {
return propertiesLocal;
}
/**
* Return the properties set as generated values on insert.
*/
@@ -2939,4 +3404,27 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return propertiesUnique;
}
@Override
public List<BeanType<?>> inheritanceChildren() {
if (hasInheritance()) {
return inheritInfo().getChildren()
.stream()
.map(InheritInfo::desc)
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
@Override
public BeanType<?> inheritanceParent() {
return inheritInfo() == null ? null : inheritInfo().getParent().desc();
}
@Override
public void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor) {
if (hasInheritance()) {
inheritInfo().visitChildren(info -> visitor.accept(info.desc()));
}
}
}
@@ -449,7 +449,44 @@ final class BeanDescriptorCacheHelp<T> {
}
void beanPutAll(Collection<EntityBean> beans) {
beanCachePutAllDirect(beans);
if (desc.inheritInfo != null) {
Class<?> aClass = theClassOf(beans);
// check if all beans have the same class
for (EntityBean bean : beans) {
if (!bean.getClass().equals(aClass)) {
aClass = null;
break;
}
}
if (aClass == null) {
// there are different bean types in the collection, so we add one by one to the cache
for (EntityBean bean : beans) {
desc.descOf(bean.getClass()).cacheBeanPutDirect(bean);
}
} else {
desc.descOf(aClass).cacheBeanPutAllDirect(beans);
}
} else {
beanCachePutAllDirect(beans);
}
}
private Class<?> theClassOf(Collection<EntityBean> beans) {
if (beans instanceof List) {
return ((List<?>) beans).get(0).getClass();
}
return beans.iterator().next().getClass();
}
/**
* Put a bean into the bean cache.
*/
void beanCachePut(EntityBean bean) {
if (desc.inheritInfo != null) {
desc.descOf(bean.getClass()).cacheBeanPutDirect(bean);
} else {
beanCachePutDirect(bean);
}
}
void beanCachePutAllDirect(Collection<EntityBean> beans) {
@@ -483,13 +520,6 @@ final class BeanDescriptorCacheHelp<T> {
}
}
/**
* Put a bean into the bean cache.
*/
void beanCachePut(EntityBean bean) {
beanCachePutDirect(bean);
}
/**
* Put the bean into the bean cache.
*/
@@ -564,6 +594,9 @@ final class BeanDescriptorCacheHelp<T> {
if (beanLog.isLoggable(TRACE)) {
beanLog.log(TRACE, " GET {0}({1}) - hit shared bean", cacheName, id);
}
if (desc.isReadAuditing()) {
desc.readAuditBean("l2", "", bean);
}
return (T) bean;
}
}
@@ -574,7 +607,19 @@ final class BeanDescriptorCacheHelp<T> {
* Load the entity bean taking into account inheritance.
*/
private EntityBean loadBean(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
return loadBeanDirect(id, readOnly, data, context);
String discValue = data.getDiscValue();
if (discValue == null) {
return loadBeanDirect(id, readOnly, data, context);
} else {
return rootDescriptor(discValue).cacheBeanLoadDirect(id, readOnly, data, context);
}
}
/**
* Return the root BeanDescriptor for inheritance.
*/
private BeanDescriptor<?> rootDescriptor(String discValue) {
return desc.inheritInfo.readType(discValue).desc();
}
/**
@@ -603,6 +648,9 @@ final class BeanDescriptorCacheHelp<T> {
}
CachedBeanDataToBean.load(desc, bean, data, context);
if (desc.isReadAuditing()) {
desc.readAuditBean("l2", "", bean);
}
return bean;
}
@@ -610,7 +658,12 @@ final class BeanDescriptorCacheHelp<T> {
* Load the embedded bean checking for inheritance.
*/
EntityBean embeddedBeanLoad(CachedBeanData data, PersistenceContext context) {
return embeddedBeanLoadDirect(data, context);
String discValue = data.getDiscValue();
if (discValue == null) {
return embeddedBeanLoadDirect(data, context);
} else {
return rootDescriptor(discValue).cacheEmbeddedBeanLoadDirect(data, context);
}
}
/**
@@ -0,0 +1,99 @@
package io.ebeaninternal.server.deploy;
import io.ebean.Query;
import io.ebean.bean.EntityBean;
import java.util.ArrayList;
import java.util.List;
/**
* Helper for BeanDescriptor that manages draft entity beans.
*
* @param <T> The entity bean type
*/
final class BeanDescriptorDraftHelp<T> {
private final BeanDescriptor<T> desc;
private final BeanProperty draftDirty;
private final BeanProperty[] resetProperties;
BeanDescriptorDraftHelp(BeanDescriptor<T> desc) {
this.desc = desc;
this.draftDirty = desc.draftDirty();
this.resetProperties = resetProperties();
}
/**
* Return the properties that are reset on draft beans after publish.
*/
private BeanProperty[] resetProperties() {
List<BeanProperty> list = new ArrayList<>();
for (BeanProperty prop : desc.propertiesNonMany()) {
if (prop.isDraftReset()) {
list.add(prop);
}
}
return list.toArray(new BeanProperty[0]);
}
/**
* Set the value of all the 'reset properties' to null on the draft bean.
*/
boolean draftReset(T draftBean) {
EntityBean draftEntityBean = (EntityBean) draftBean;
if (draftDirty != null) {
// set @DraftDirty property to false
draftDirty.setValueIntercept(draftEntityBean, false);
}
// set to null on all @DraftReset properties
for (BeanProperty resetProperty : resetProperties) {
resetProperty.setValueIntercept(draftEntityBean, null);
}
// return true if the bean is dirty (and should be persisted)
return draftEntityBean._ebean_getIntercept().isDirty();
}
/**
* Transfer the values from the draftBean to the liveBean.
* <p>
* This will recursive transfer values to all @DraftableElement properties.
* </p>
*/
@SuppressWarnings("unchecked")
public T publish(T draftBean, T liveBean) {
if (liveBean == null) {
liveBean = (T) desc.createEntityBean();
}
EntityBean draft = (EntityBean) draftBean;
EntityBean live = (EntityBean) liveBean;
BeanProperty idProperty = desc.idProperty();
if (idProperty != null) {
idProperty.publish(draft, live);
}
for (BeanProperty prop : desc.propertiesNonMany()) {
prop.publish(draft, live);
}
for (BeanPropertyAssocMany<?> many : desc.propertiesMany()) {
if (many.targetDescriptor().isDraftable()) {
many.publishMany(draft, live);
}
}
return liveBean;
}
/**
* Fetch draftable element relationships.
*/
void draftQueryOptimise(Query<T> query) {
for (BeanPropertyAssocOne<?> anOne : desc.propertiesOne()) {
if (anOne.targetDescriptor().isDraftableElement()) {
query.fetch(anOne.name());
}
}
for (BeanPropertyAssocMany<?> aMany : desc.propertiesMany()) {
if (aMany.targetDescriptor().isDraftableElement()) {
query.fetch(aMany.name());
}
}
}
}
@@ -5,14 +5,20 @@ import java.util.Map;
class BeanDescriptorInitContext {
private final Map<String, String> withHistoryTables;
private final Map<String, String> draftTables;
private final String asOfViewSuffix;
private String embeddedPrefix;
BeanDescriptorInitContext(Map<String, String> withHistoryTables, String asOfViewSuffix) {
BeanDescriptorInitContext(Map<String, String> withHistoryTables, Map<String, String> draftTables, String asOfViewSuffix) {
this.withHistoryTables = withHistoryTables;
this.draftTables = draftTables;
this.asOfViewSuffix = asOfViewSuffix;
}
void addDraft(String baseTable, String draftTable) {
draftTables.put(baseTable, draftTable);
}
void addHistory(String baseTable, String baseTableAsOf) {
withHistoryTables.put(baseTable, baseTableAsOf);
}
@@ -21,6 +27,10 @@ class BeanDescriptorInitContext {
withHistoryTables.put(intersectionTableName, intersectionTableName + asOfViewSuffix);
}
void addDraftIntersection(String intersectionPublishTable, String intersectionDraftTable) {
draftTables.put(intersectionPublishTable, intersectionDraftTable);
}
public void setEmbeddedPrefix(String embeddedPrefix) {
this.embeddedPrefix = embeddedPrefix;
}
@@ -19,14 +19,24 @@ import static io.ebeaninternal.server.persist.DmlUtil.isNullOrZero;
final class BeanDescriptorJsonHelp<T> {
private final BeanDescriptor<T> desc;
private final InheritInfo inheritInfo;
BeanDescriptorJsonHelp(BeanDescriptor<T> desc) {
this.desc = desc;
this.inheritInfo = desc.inheritInfo;
}
void jsonWrite(SpiJsonWriter writeJson, EntityBean bean, String key) throws IOException {
writeJson.writeStartObject(key);
jsonWriteProperties(writeJson, bean);
if (inheritInfo == null) {
jsonWriteProperties(writeJson, bean);
} else {
InheritInfo localInheritInfo = inheritInfo.readType(bean.getClass());
String discValue = localInheritInfo.getDiscriminatorStringValue();
String discColumn = localInheritInfo.getDiscriminatorColumn();
writeJson.gen().writeStringField(discColumn, discValue);
localInheritInfo.desc().jsonWriteProperties(writeJson, bean);
}
writeJson.writeEndObject();
}
@@ -35,7 +45,11 @@ final class BeanDescriptorJsonHelp<T> {
}
void jsonWriteDirty(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
if (inheritInfo == null) {
jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
} else {
desc.descOf(bean.getClass()).jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
}
}
void jsonWriteDirtyProperties(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
@@ -66,7 +80,31 @@ final class BeanDescriptorJsonHelp<T> {
throw new JsonParseException(parser, "Unexpected token " + token + " - expecting start_object", parser.getCurrentLocation());
}
}
return jsonReadObject(jsonRead, path, target);
if (desc.inheritInfo == null || !withInheritance) {
return jsonReadObject(jsonRead, path, target);
}
ObjectNode node = jsonRead.mapper().readTree(parser);
if (node.isNull()) {
return null;
}
JsonParser newParser = node.traverse();
SpiJsonReader newReader = jsonRead.forJson(newParser);
// check for the discriminator value to determine the correct sub type
String discColumn = inheritInfo.getRoot().getDiscriminatorColumn();
JsonNode discNode = node.get(discColumn);
if (discNode == null || discNode.isNull()) {
if (!desc.isAbstractType()) {
return desc.jsonReadObject(newReader, path, target);
}
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
throw new JsonParseException(newParser, msg, parser.getCurrentLocation());
}
BeanDescriptor<T> inheritDesc = (BeanDescriptor<T>) inheritInfo.readType(discNode.asText()).desc();
return inheritDesc.jsonReadObject(newReader, path, target);
}
private T jsonReadObject(SpiJsonReader readJson, String path, T target) throws IOException {
@@ -3,7 +3,9 @@ package io.ebeaninternal.server.deploy;
import io.ebean.BackgroundExecutor;
import io.ebean.DatabaseBuilder;
import io.ebean.Model;
import io.ebean.RawSqlBuilder;
import io.ebean.annotation.ConstraintMode;
import io.ebean.annotation.Platform;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.config.*;
@@ -35,6 +37,12 @@ import io.ebeaninternal.server.properties.BeanPropertiesReader;
import io.ebeaninternal.server.properties.BeanPropertyAccess;
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
import io.ebeaninternal.server.type.TypeManager;
import io.ebeaninternal.xmapping.api.XmapEbean;
import io.ebeaninternal.xmapping.api.XmapEntity;
import io.ebeaninternal.xmapping.api.XmapNamedQuery;
import io.ebeaninternal.xmapping.api.XmapRawSql;
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
import io.ebeanservice.docstore.api.DocStoreFactory;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PersistenceException;
@@ -60,6 +68,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
private final ReadAnnotations readAnnotations;
private final TransientProperties transientProperties;
private final DeployInherit deplyInherit;
private final BeanPropertyAccess beanPropertyAccess = new EnhanceBeanPropertyAccess();
private final DeployUtil deployUtil;
private final PersistControllerManager persistControllerManager;
@@ -75,6 +84,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
private final ChangeLogListener changeLogListener;
private final ChangeLogRegister changeLogRegister;
private final ChangeLogPrepare changeLogPrepare;
private final DocStoreFactory docStoreFactory;
private final MultiValueBind multiValueBind;
private final TypeManager typeManager;
private final BootupClasses bootupClasses;
@@ -82,6 +92,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
private final List<BeanDescriptor<?>> elementDescriptors = new ArrayList<>();
private final Map<Class<?>, BeanTable> beanTableMap = new HashMap<>();
private final Map<String, BeanDescriptor<?>> descMap = new HashMap<>();
private final Map<String, BeanDescriptor<?>> descQueueMap = new HashMap<>();
private final Map<String, BeanManager<?>> beanManagerMap = new HashMap<>();
private final Map<String, List<BeanDescriptor<?>>> tableToDescMap = new HashMap<>();
private final Map<String, List<BeanDescriptor<?>>> tableToViewDescMap = new HashMap<>();
@@ -103,6 +114,10 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
* Map of base tables to 'with history views' used to support 'as of' queries.
*/
private final Map<String, String> asOfTableMap = new HashMap<>();
/**
* Map of base tables to 'draft' tables.
*/
private final Map<String, String> draftTableMap = new HashMap<>();
// temporary collections used during startup and then cleared
private Map<Class<?>, DeployBeanInfo<?>> deployInfoMap = new HashMap<>();
@@ -116,6 +131,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
this.config = config.getConfig();
this.serverName = InternString.intern(this.config.getName());
this.cacheManager = config.getCacheManager();
this.docStoreFactory = config.getDocStoreFactory();
this.backgroundExecutor = config.getBackgroundExecutor();
this.dataSource = this.config.getDataSource();
this.encryptKeyManager = this.config.getEncryptKeyManager();
@@ -130,6 +146,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
this.createProperties = config.getDeployCreateProperties();
this.namingConvention = this.config.getNamingConvention();
this.dbIdentity = config.getDatabasePlatform().dbIdentity();
this.deplyInherit = config.getDeployInherit();
this.deployUtil = config.getDeployUtil();
this.typeManager = deployUtil.typeManager();
this.beanManagerFactory = new BeanManagerFactory(config.getDatabasePlatform());
@@ -219,6 +236,15 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
return config;
}
@Override
public <T> DocStoreBeanAdapter<T> createDocStoreBeanAdapter(BeanDescriptor<T> descriptor, DeployBeanDescriptor<T> deploy) {
return docStoreFactory.createAdapter(descriptor, deploy);
}
public BeanDescriptor<?> descriptorByQueueId(String queueId) {
return descQueueMap.get(queueId);
}
@Override
public SpiBeanType beanType(Class<?> entityType) {
return descriptor(entityType);
@@ -264,15 +290,24 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
return idBinderFactory.createIdBinder(idProperty);
}
/**
* Return the map of base tables to draft tables.
*/
public Map<String, String> draftTableMap() {
return draftTableMap;
}
/**
* Deploy returning the asOfTableMap (which is required by the SQL builders).
*/
public Map<String, String> deploy() {
public Map<String, String> deploy(List<XmapEbean> mappings) {
try {
createListeners();
readEntityDeploymentInitial();
readXmlMapping(mappings);
readEntityBeanTable();
readEntityDeploymentAssociations();
readInheritedIdGenerators();
// creates the BeanDescriptors
readEntityRelationships();
List<BeanDescriptor<?>> list = new ArrayList<>(descMap.values());
@@ -296,6 +331,56 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
}
}
private void readXmlMapping(List<XmapEbean> mappings) {
if (mappings != null) {
ClassLoader classLoader = config.getClassLoadConfig().getClassLoader();
for (XmapEbean mapping : mappings) {
List<XmapEntity> entityDeploy = mapping.getEntity();
for (XmapEntity deploy : entityDeploy) {
readEntityMapping(classLoader, deploy);
}
}
}
}
private void readEntityMapping(ClassLoader classLoader, XmapEntity entityDeploy) {
String entityClassName = entityDeploy.getClazz();
Class<?> entityClass;
try {
entityClass = Class.forName(entityClassName, false, classLoader);
} catch (Exception e) {
log.log(ERROR, "Could not load entity bean class " + entityClassName + " for ebean.xml entry");
return;
}
DeployBeanInfo<?> info = deployInfoMap.get(entityClass);
if (info == null) {
log.log(ERROR, "No entity bean for ebean.xml entry " + entityClassName);
} else {
for (XmapRawSql sql : entityDeploy.getRawSql()) {
RawSqlBuilder builder;
try {
builder = RawSqlBuilder.parse(sql.getQuery());
} catch (RuntimeException e) {
builder = RawSqlBuilder.unparsed(sql.getQuery());
}
for (Map.Entry<String, String> columnMapping : sql.getColumnMapping().entrySet()) {
builder.columnMapping(columnMapping.getKey(), columnMapping.getValue());
}
for (Map.Entry<String, String> aliasMapping : sql.getAliasMapping().entrySet()) {
builder.tableAliasMapping(aliasMapping.getKey(), aliasMapping.getValue());
}
info.addRawSql(sql.getName(), builder.create());
}
for (XmapNamedQuery namedQuery : entityDeploy.getNamedQuery()) {
info.addNamedQuery(namedQuery.getName(), namedQuery.getQuery());
}
}
}
/**
* Return the Encrypt key given the table and column name.
*/
@@ -403,7 +488,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
// now that all the BeanDescriptors are in their map
// we can initialise them which sorts out circular
// dependencies for OneToMany and ManyToOne etc
BeanDescriptorInitContext initContext = new BeanDescriptorInitContext(asOfTableMap, asOfViewSuffix);
BeanDescriptorInitContext initContext = new BeanDescriptorInitContext(asOfTableMap, draftTableMap, asOfViewSuffix);
// PASS 1:
// initialise the ID properties of all the beans
@@ -413,7 +498,12 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
d.initialiseId(initContext);
}
// PASS 2: Was to initialise all the inherit info
// PASS 2:
// now initialise all the inherit info
for (BeanDescriptor<?> d : descMap.values()) {
d.initInheritInfo();
}
// PASS 3:
// now initialise all the associated properties
for (BeanDescriptor<?> d : descMap.values()) {
@@ -543,8 +633,13 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
}
private void registerDescriptor(DeployBeanInfo<?> info) {
platformUsesColumnDefinitions(info);
BeanDescriptor<?> desc = new BeanDescriptor<>(this, info.getDescriptor());
descMap.put(desc.type().getName(), desc);
if (desc.isDocStoreMapped()) {
descQueueMap.put(desc.docStoreQueueId(), desc);
}
for (BeanPropertyAssocMany<?> many : desc.propertiesMany()) {
if (many.isElementCollection()) {
elementDescriptors.add(many.elementDescriptor());
@@ -552,6 +647,23 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
}
}
private void platformUsesColumnDefinitions(DeployBeanInfo<?> info) {
if (databasePlatform.isPlatform(Platform.CLICKHOUSE) && !DbOffline.isGenerateMigration()) {
// ClickHouse uses column definition to optimise JDBC bulk inserts
DbPlatformTypeMapping typeMapping = databasePlatform.dbTypeMap();
for (DeployBeanProperty property : info.getDescriptor().propertiesAll()) {
if (property.getDbColumnDefn() == null) {
DbPlatformType dbPlatformType = typeMapping.get(property.getDbType());
String columnDefn = dbPlatformType.renderType(property.getDbLength(), property.getDbScale());
if (property.isNullable()) {
columnDefn = "Nullable(" + columnDefn + ")";
}
property.setDbColumnDefn(columnDefn);
}
}
}
}
/**
* Read the initial deployment information for the entities.
* <p>
@@ -614,6 +726,20 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
}
}
private void readInheritedIdGenerators() {
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
DeployBeanDescriptor<?> descriptor = info.getDescriptor();
InheritInfo inheritInfo = descriptor.getInheritInfo();
if (inheritInfo != null && !inheritInfo.isRoot()) {
DeployBeanInfo<?> rootBeanInfo = deployInfoMap.get(inheritInfo.getRoot().getType());
PlatformIdGenerator rootIdGen = rootBeanInfo.getDescriptor().getIdGenerator();
if (rootIdGen != null) {
descriptor.setIdGenerator(rootIdGen);
}
}
}
}
/**
* Create the BeanTable from the deployment information gathered so far.
*/
@@ -636,6 +762,9 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
secondaryPropsJoins(info);
}
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
setInheritanceInfo(info);
}
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
if (!info.isEmbedded()) {
registerDescriptor(info);
@@ -643,6 +772,28 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
}
}
/**
* Sets the inheritance info.
*/
private void setInheritanceInfo(DeployBeanInfo<?> info) {
for (DeployBeanPropertyAssocOne<?> oneProp : info.getDescriptor().propertiesAssocOne()) {
if (!oneProp.isTransient()) {
DeployBeanInfo<?> assoc = deployInfoMap.get(oneProp.getTargetType());
if (assoc != null) {
oneProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
}
}
}
for (DeployBeanPropertyAssocMany<?> manyProp : info.getDescriptor().propertiesAssocMany()) {
if (!manyProp.isTransient()) {
DeployBeanInfo<?> assoc = deployInfoMap.get(manyProp.getTargetType());
if (assoc != null) {
manyProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
}
}
}
}
private void secondaryPropsJoins(DeployBeanInfo<?> info) {
DeployBeanDescriptor<?> descriptor = info.getDescriptor();
for (DeployBeanProperty prop : descriptor.propertiesBase()) {
@@ -776,6 +927,13 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
orderProperty.setDbUpdateable(orderColumn.isUpdatable());
orderProperty.setDbRead(true);
orderProperty.setOwningType(targetDesc.getBeanType());
final InheritInfo targetInheritInfo = targetDesc.getInheritInfo();
if (targetInheritInfo != null) {
for (InheritInfo child : targetInheritInfo.getChildren()) {
final DeployBeanDescriptor<?> childDescriptor = deployInfoMap.get(child.getType()).getDescriptor();
childDescriptor.setOrderColumn(orderProperty);
}
}
targetDesc.setOrderColumn(orderProperty);
}
@@ -891,6 +1049,13 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
// skip mapping check
return;
}
DeployBeanDescriptor<?> targetDesc = targetDescriptor(prop);
if (targetDesc.isDraftableElement()) {
// automatically turning on orphan removal and CascadeType.ALL
prop.setModifyListenMode(BeanCollection.ModifyListenMode.REMOVALS);
prop.getCascadeInfo().setSaveDelete(true, true);
}
if (prop.hasOrderColumn()) {
makeOrderColumn(prop);
}
@@ -916,7 +1081,6 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
String mappedBy = prop.getMappedBy();
// get the mappedBy property
DeployBeanDescriptor<?> targetDesc = targetDescriptor(prop);
DeployBeanPropertyAssocOne<?> mappedAssocOne = mappedManyToOne(prop, targetDesc, mappedBy);
DeployTableJoin tableJoin = prop.getTableJoin();
if (!tableJoin.hasJoinColumns()) {
@@ -958,6 +1122,9 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
// get the bean descriptor that holds the mappedBy property
String mappedBy = prop.getMappedBy();
if (mappedBy == null) {
if (targetDescriptor(prop).isDraftable()) {
prop.setIntersectionDraftTable();
}
return;
}
@@ -982,6 +1149,10 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
DeployTableJoin inverseJoin = new DeployTableJoin();
mappedIntJoin.copyTo(inverseJoin, false, intTableName);
prop.setInverseJoin(inverseJoin);
if (targetDesc.isDraftable()) {
prop.setIntersectionDraftTable();
}
}
private DeployBeanPropertyAssocMany<?> mappedManyToMany(DeployBeanPropertyAssocMany<?> prop, String mappedBy, DeployBeanDescriptor<?> targetDesc) {
@@ -1023,6 +1194,9 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
beanLifecycleAdapterFactory.addLifecycleMethods(desc);
// set bean controller, finder and listener
setBeanControllerFinderListener(desc);
deplyInherit.process(desc);
desc.checkInheritanceMapping();
createProperties.createProperties(desc);
DeployBeanInfo<T> info = new DeployBeanInfo<>(deployUtil, desc);
readAnnotations.readInitial(info);
@@ -1095,7 +1269,8 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
if (identityMode.isIdentity()) {
// used when getGeneratedKeys is not supported (SQL Server 2000, SAP Hana)
String selectLastInsertedId = dbIdentity.getSelectLastInsertedId(desc.getBaseTable());
desc.setSelectLastInsertedId(selectLastInsertedId);
String selectLastInsertedIdDraft = (!desc.isDraftable()) ? selectLastInsertedId : dbIdentity.getSelectLastInsertedId(desc.getDraftTable());
desc.setSelectLastInsertedId(selectLastInsertedId, selectLastInsertedIdDraft);
return;
}
@@ -7,6 +7,7 @@ import io.ebean.core.type.ScalarType;
import io.ebeaninternal.server.cache.SpiCacheManager;
import io.ebeaninternal.server.deploy.id.IdBinder;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
/**
* Provides a method to find a BeanDescriptor.
@@ -56,6 +57,11 @@ public interface BeanDescriptorMap {
*/
IdBinder createIdBinder(BeanProperty id);
/**
* Create a doc store specific adapter for this bean type.
*/
<T> DocStoreBeanAdapter<T> createDocStoreBeanAdapter(BeanDescriptor<T> descriptor, DeployBeanDescriptor<T> deploy);
/**
* Return the scalarType for the given JDBC type.
*/
@@ -37,8 +37,12 @@ import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.type.*;
import io.ebeaninternal.util.ValueUtil;
import io.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import io.ebeanservice.docstore.api.mapping.DocPropertyMapping;
import io.ebeanservice.docstore.api.mapping.DocPropertyOptions;
import io.ebeanservice.docstore.api.support.DocStructure;
import jakarta.persistence.PersistenceException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
@@ -140,6 +144,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
@SuppressWarnings("rawtypes")
final ScalarType scalarType;
private final DocPropertyOptions docOptions;
/**
* The length or precision for DB column.
*/
@@ -168,6 +173,10 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
final boolean jsonDeserialize;
private final boolean unmappedJson;
private final boolean tenantId;
private final boolean draft;
private final boolean draftOnly;
private final boolean draftDirty;
private final boolean draftReset;
private final boolean softDelete;
private final String softDeleteDbSet;
private final String softDeleteDbPredicate;
@@ -194,6 +203,10 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
this.excludedFromHistory = deploy.isExcludedFromHistory();
this.unmappedJson = deploy.isUnmappedJson();
this.tenantId = deploy.isTenantId();
this.draft = deploy.isDraft();
this.draftDirty = deploy.isDraftDirty();
this.draftOnly = deploy.isDraftOnly();
this.draftReset = deploy.isDraftReset();
this.secondaryTable = deploy.isSecondaryTable();
if (secondaryTable) {
this.secondaryTableJoin = new TableJoin(deploy.getSecondaryTableJoin());
@@ -232,6 +245,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
this.lob = isLobType(dbType);
this.propertyType = deploy.getPropertyType();
this.field = deploy.getField();
this.docOptions = deploy.getDocPropertyOptions();
this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), false, null);
this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), dbEncrypted, dbColumn);
this.elPrefix = deploy.getElPrefix();
@@ -285,6 +299,10 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
this.aggregation = null;
this.excludedFromHistory = source.excludedFromHistory;
this.tenantId = source.tenantId;
this.draft = source.draft;
this.draftDirty = source.draftDirty;
this.draftOnly = source.draftOnly;
this.draftReset = source.draftReset;
this.softDelete = source.softDelete;
this.softDeleteDbSet = source.softDeleteDbSet;
this.softDeleteDbPredicate = source.softDeleteDbPredicate;
@@ -323,6 +341,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
this.lob = isLobType(dbType);
this.propertyType = source.type();
this.field = source.field();
this.docOptions = source.docOptions;
this.unmappedJson = source.unmappedJson;
this.elPrefix = override.replace(source.elPrefix, source.dbColumn);
this.elPlaceHolder = override.replace(source.elPlaceHolder, source.dbColumn);
@@ -468,7 +487,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
ctx.appendFormulaSelect(aggregation);
} else if (formula) {
ctx.appendFormulaSelect(sqlFormulaSelect);
} else if (!isTransient) {
} else if (!isTransient && !ignoreDraftOnlyProperty(ctx.isDraftQuery())) {
if (secondaryTableJoin != null) {
ctx.pushTableAlias(ctx.relativePrefix(secondaryTableJoinPrefix));
}
@@ -587,6 +606,17 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return local;
}
/**
* Copy/set the property value from the draft bean to the live bean.
*/
public void publish(EntityBean draftBean, EntityBean liveBean) {
if (!version && !draftOnly) {
// set property value from draft to live
Object value = getValueIntercept(draftBean);
setValueIntercept(liveBean, value);
}
}
/**
* Return the DB literal expression to set the deleted state to true.
*/
@@ -1076,8 +1106,16 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return true if this property is loadable from a resultSet.
*/
public boolean isLoadProperty() {
return !isTransient || formula;
public boolean isLoadProperty(boolean draftQuery) {
return !ignoreDraftOnlyProperty(draftQuery) && (!isTransient || formula);
}
/**
* Return true if this is a draftOnly property on a non-asDraft query and as such this
* property should not be included in a sql query.
*/
private boolean ignoreDraftOnlyProperty(boolean draftQuery) {
return draftOnly && !draftQuery;
}
/**
@@ -1144,14 +1182,14 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
* Returns true if this <code>isLob()</code> or the type will effectively map to a lob.
*/
@Override
public boolean isDbLob() {
public boolean isLobForPlatform() {
if (lob) {
return true;
}
switch (dbType) {
case DbPlatformType.JSON:
case DbPlatformType.JSONB:
return dbLength == 0; // must be analog to DbPlatformTypeMapping.lookup
return dbLength == 0 || dbLength > 4000; // must be analog to DbPlatformTypeMapping.lookup
case DbPlatformType.JSONBlob:
case DbPlatformType.JSONClob:
return true;
@@ -1226,6 +1264,36 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return tenantId;
}
/**
* Return true if this property only exists on the draft table.
*/
public boolean isDraftOnly() {
return draftOnly;
}
/**
* Return true if this property is a boolean flag on a draftable bean
* indicating if the instance is a draft or live bean.
*/
public boolean isDraft() {
return draft;
}
/**
* Return true if this property is a boolean flag only on the draft table
* indicating that when the draft is different from the published row.
*/
public boolean isDraftDirty() {
return draftDirty;
}
/**
* Return true if this property is reset/cleared on publish (on the draft bean).
*/
boolean isDraftReset() {
return draftReset;
}
/**
* Return true if this property is the soft delete property.
*/
@@ -1292,6 +1360,15 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return name;
}
/**
* Append this property to the document store based on includeByDefault setting.
*/
public void docStoreInclude(boolean includeByDefault, DocStructure docStructure) {
if (includeByDefault) {
docStructure.addProperty(name);
}
}
public boolean isJsonSerialize() {
return jsonSerialize;
}
@@ -1401,6 +1478,24 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
}
}
/**
* Add to the document mapping if this property is included for this index.
*/
public void docStoreMapping(DocMappingBuilder mapping, String prefix) {
if (mapping.includesProperty(prefix, name)) {
DocPropertyType type = scalarType.docType();
DocPropertyOptions options = docOptions.copy();
if (isKeywordType(type, options)) {
type = DocPropertyType.KEYWORD;
}
mapping.add(new DocPropertyMapping(name, type, options));
}
}
private boolean isKeywordType(DocPropertyType type, DocPropertyOptions docOptions) {
return type == DocPropertyType.TEXT && (docOptions.isCode() || id || discriminator);
}
public void merge(EntityBean bean, EntityBean existing) {
// do nothing unless Many property
}

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