Compare commits

...
Author SHA1 Message Date
Rob BygraveandGitHub d92abbbe9c Merge branch 'master' into feature/sequencedSet 2024-02-10 14:13:21 +13:00
Rob Bygrave 39500567c3 Build needs to use Java 21 to support the multi-release jar 2024-02-10 13:29:40 +13:00
Rob Bygrave 261b13ef4b Version 13.26.1 2024-02-10 12:40:30 +13:00
Rob Bygrave 7639034d7c Bump ebean-agent to 13.26.1 (no functional change here) 2024-02-10 12:17:47 +13:00
Rob BygraveandGitHub c71971eb5c Merge pull request #3322 from ebean-orm/fix/3319
(fix) Future queries do not trigger flush on BatchedPstmtHolder for #3319
2024-02-10 12:12:47 +13:00
Rob BygraveandGitHub 357af58db4 Merge pull request #3324 from ebean-orm/feature/postgres_insertOnConflict
Add support for Postgres INSERT ON CONFLICT update | nothing
2024-02-10 12:08:44 +13:00
Rob BygraveandGitHub 0abdb1550a Merge pull request #3310 from Ichtil/one_to_many_list_clear_test
Previous entities are not deleted when replacing OneToMany collection with a new one
2024-02-10 12:03:16 +13:00
Rob Bygrave c32fc814f6 Add support for insert/insertAll with both options and explicit transaction 2024-02-10 12:00:32 +13:00
Rob Bygrave 8e035939dc Add support for Postgres INSERT ON CONFLICT update | nothing
Adds InsertOptions with ability to control the options used
for insert with Postgres around ON CONFLICT.
2024-02-10 01:07:44 +13:00
Rob Bygrave edf98ee250 Fix test OneToManyListMarkAsDirtyTest with some wait time 2024-02-09 15:46:34 +13:00
Rob Bygrave 5f0a75a130 No effective change, update test renaming internal variables 2024-02-09 15:26:28 +13:00
Rob Bygrave d2f16cbf6a #3310 Fix for orphanRemoval with updated parent and 'vanilla collection'
The issue fixed here is a timing one where the parent bean is being updated.
The updated parent bean has its internal state reset before the cascade down
to the SaveManyBeans where for this case it needs to identify is the 'vanilla'
collection has set (via setChildren(new ArrayList()).

This fix is a change that for updated beans the dirtyProperties is always obtained
and then use this for the isChangedProperty() check.
2024-02-09 15:24:47 +13:00
Rob Bygrave 04ba81d12f #3310 Update test only, reuse existing entity beans
- Reuse the existing OmBeanListParent and child
- Simplify the test setup code
- Rename test methods to maybe better reflect what I think is failing

Noting that markAsDirty doesn't specifically have anything to do with this bug but it's more on whether the parent bean is dirty or dirty (markAsDirty is just a way to make the parent bean dirty).
2024-02-09 08:30:42 +13:00
Rob Bygrave f63e7ed5af Add test only for orphan removal to TestOrphanRemovalOverwrite
Using clear() and add() when the same id value is used
2024-02-08 00:14:22 +13:00
Rob Bygrave 66940c0f14 Update tests only for BeanList BeanSet BeanMap
Use Product entity with equals/hashCode implementation added via ebean enhancement
2024-02-07 22:29:05 +13:00
Rob Bygrave 1f48b3a6b3 (fix) Future queries do not trigger flush on BatchedPstmtHolder for #3319
This change is that findFutureCount, findFutureList, findFutureIds do not trigger a flush on BatchedPstmtHolder.

This is to address the possible ConcurrentModificationException that could occur at BatchedPstmtHolder.closeStatements(BatchedPstmtHolder.java:153)
2024-02-03 08:20:58 +13:00
Rob BygraveandGitHub 7d368097e9 Merge pull request #3321 from ebean-orm/feature/tidy-OrmQueryRequest-readOnly
Tidy OrmQueryRequest isReadOnly, just use query.isReadOnly instead
2024-02-02 14:44:06 +13:00
Rob Bygrave c849e29316 Tidy OrmQueryRequest isReadOnly, just use query.isReadOnly instead 2024-02-02 14:43:31 +13:00
Rob BygraveandGitHub c78a3dd6c4 Merge pull request #3318 from FOCONIS/fix-db2-test
Exclude test for DB2
2024-01-31 22:57:49 +13:00
Noemi Praml a4455ddbf2 Exclude test for DB2 2024-01-31 10:17:16 +01:00
Rob BygraveandGitHub 9787af77e4 Merge pull request #3315 from FOCONIS/db2-select-for-update
ADD: DB2 supports select .. for update queries
2024-01-30 22:53:19 +13:00
Noemi Praml 3786fb09a5 ADD: DB2 supports select .. for update queries 2024-01-29 15:12:21 +01:00
Rob Bygrave f34e7b6003 Version 13.26.0 2024-01-20 22:01:30 +13:00
Rob BygraveandGitHub 089dd8e364 Merge pull request #3311 from ebean-orm/feature/findSingleAttributeOrEmpty
enh: Add findSingleAttributeOrEmpty() returning Optional
2024-01-19 15:23:27 +13:00
Rob Bygrave 44770382fb enh: Add findSingleAttributeOrEmpty() returning Optional 2024-01-19 10:41:54 +13:00
Rob BygraveandGitHub 2a62d413cc Merge pull request #3308 from Incanus3/fix/querybean_or_with_exists
fix TQRootBean.exists() when used with .or()
2024-01-19 08:29:29 +13:00
Rob Bygrave 0046d983cb Adjust test such that table alias does not clash
The .raw("contact.customer_id = customer.id") clashes as "customer.id" is
a logical path from contact
2024-01-19 08:14:58 +13:00
Jan Klička 006e33cb3e One to many replacing collection failing test cases 2024-01-18 17:29:24 +01:00
Jan Klička e80e8e027a One to many replacing collection failing test cases 2024-01-18 16:49:04 +01:00
Jakub Kaláb 2e1b52d7d4 WIP: fix TQRootBean.exists() when used with .or() 2024-01-18 11:32:34 +01:00
Rob BygraveandGitHub 09d5163396 Merge pull request #3307 from ebean-orm/feature/bump-avaje-config-310
Bump avaje-config to version 3.10
2024-01-18 22:30:42 +13:00
Rob Bygrave a6a38b9a92 Bump avaje-config to version 3.10 2024-01-18 22:30:09 +13:00
Rob BygraveandGitHub bd4521b240 Merge pull request #3306 from ebean-orm/feature/querybean-deprecated
Add forRemoval=true for existing Deprecated methods in query beans
2024-01-18 22:28:23 +13:00
Rob Bygrave 8a7379879b Add forRemoval=true for existing Deprecated methods in query beans 2024-01-18 20:15:57 +13:00
Rob BygraveandGitHub b78dd6f79a Merge pull request #3304 from ebean-orm/feature/3296-querybean-copy
#3296 Add copy() method to query beans
2024-01-15 22:38:30 +13:00
Rob Bygrave 4ebb5fd112 #3296 Add copy() method to query beans 2024-01-15 22:30:17 +13:00
Rob Bygrave 487368976f Tidy SimpleQueryBeanWriter internals 2024-01-15 22:03:17 +13:00
Rob Bygrave c6defceec9 Add additional test for filterMany() with fetch() 2024-01-15 21:02:47 +13:00
Rob Bygrave ded6fd71b2 Refactor rename method getBeanCollectionAdd() -> collectionAdd() 2024-01-12 16:42:58 +13:00
Rob Bygrave cbc4883672 Refactor BeanSet, BeanList, BeanMap replacing setActualSet|List|Map
Replace with collectionAdd() and refresh() methods.
2024-01-12 16:41:32 +13:00
Rob Bygrave 64e227117b Add support for Java 21 SequencedSet and SequencedMap
Such that these can be used in place of Set and Map if desired.
2024-01-12 16:14:08 +13:00
Rob BygraveandGitHub 8ed3b765ec Merge pull request #3299 from ebean-orm/feature/3294-warnings
#3294 Suppress compiler warnings on generated query bean `filterMany()`
2023-12-22 08:33:31 +13:00
Rob BygraveandGitHub fcbcc53e27 Merge pull request #3298 from ebean-orm/feature/add-usingConnection-dtoQuery
Add usingConnection() to DtoQuery and SqlQuery
2023-12-22 08:25:48 +13:00
Rob Bygrave b377e1a618 #3294 Suppress compiler warnings on generated query bean filterMany() 2023-12-22 08:24:54 +13:00
Rob Bygrave a0f612ed71 Add usingConnection() to DtoQuery and SqlQuery
For the case where we have a java.sql.Connection and wish
to execute a DtoQuery or SqlQuery using that connection.
2023-12-21 21:24:02 +13:00
Rob Bygrave 47e97a77a8 Version 13.25.2 2023-12-07 20:18:40 +13:00
Rob Bygrave e8d3f50bb4 Bump ebean-agent to 13.25.2 2023-12-07 19:57:55 +13:00
119 changed files with 2969 additions and 481 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
java_version: [11]
java_version: [21]
os: [ubuntu-latest]
steps:
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<artifactId>composites</artifactId>
+49 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<name>ebean api</name>
@@ -39,7 +39,7 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-config</artifactId>
<version>3.9</version>
<version>3.10</version>
</dependency>
<dependency>
@@ -126,6 +126,53 @@
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>11</release>
</configuration>
</execution>
<execution>
<id>compile-21</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>21</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java21</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
<!-- <manifest>-->
<!-- <addDefaultImplementationEntries>true</addDefaultImplementationEntries>-->
<!-- </manifest>-->
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,104 @@
package io.ebean;
final class DInsertOptionsBuilder implements InsertOptions.Builder {
private Boolean getGeneratedKeys;
private boolean onConflictUpdate;
private boolean onConflictNothing;
private String constraint;
private String uniqueColumns;
private String updateSet;
@Override
public InsertOptions.Builder onConflictNothing() {
this.onConflictNothing = true;
return this;
}
@Override
public InsertOptions.Builder onConflictUpdate() {
this.onConflictUpdate = true;
return this;
}
@Override
public InsertOptions.Builder constraint(String constraint) {
this.constraint = constraint;
return this;
}
@Override
public InsertOptions.Builder uniqueColumns(String uniqueColumns) {
this.uniqueColumns = uniqueColumns;
return this;
}
@Override
public InsertOptions.Builder updateSet(String updateSet) {
this.updateSet = updateSet;
return this;
}
@Override
public InsertOptions.Builder getGeneratedKeys(boolean getGeneratedKeys) {
this.getGeneratedKeys = getGeneratedKeys;
return this;
}
@Override
public InsertOptions build() {
return new Options(constraint, uniqueColumns, updateSet, onConflictUpdate, onConflictNothing, getGeneratedKeys);
}
static final class Options implements InsertOptions {
private static final String UPDATE = "U";
private static final String NOTHING = "N";
private static final String NORMAL = "_";
private final String key;
private final Boolean getGeneratedKeys;
private final String constraint;
private final String uniqueColumns;
private final String updateSet;
Options(String constraint, String uniqueColumns, String updateSet, boolean onConflictUpdate, boolean onConflictNothing, Boolean getGeneratedKeys) {
this.constraint = constraint;
this.uniqueColumns = uniqueColumns;
this.updateSet = updateSet;
this.getGeneratedKeys = getGeneratedKeys;
this.key = (onConflictUpdate ? UPDATE : onConflictNothing ? NOTHING : NORMAL)
+ '+' + plus(constraint)
+ '+' + plus(uniqueColumns)
+ '+' + plus(updateSet);
}
private String plus(String val) {
return val == null ? "" : val;
}
@Override
public String key() {
return key;
}
@Override
public String constraint() {
return constraint;
}
@Override
public String uniqueColumns() {
return uniqueColumns;
}
@Override
public String updateSet() {
return updateSet;
}
@Override
public Boolean getGetGeneratedKeys() {
return getGeneratedKeys;
}
}
}
@@ -1227,22 +1227,53 @@ public interface Database {
*/
void insert(Object bean);
/**
* Insert the bean with options (ON CONFLICT DO UPDATE | DO NOTHING).
* <p>
* Currently, this is limited to use with Postgres only,
* <p>
* When using this ebean will look to determine the unique columns by looking at
* the mapping like {@code @Column(unique=true} and {@code @Index(unique=true}.
*/
void insert(Object bean, InsertOptions insertOptions);
/**
* Insert the bean with a transaction.
*/
void insert(Object bean, Transaction transaction);
/**
* Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insert(Object bean, InsertOptions insertOptions, Transaction transaction);
/**
* Insert a collection of beans. If there is no current transaction one is created and used to
* insert all the beans in the collection.
*/
void insertAll(Collection<?> beans);
/**
* Insert the beans with options - typically ON CONFLICT DO UPDATE | DO NOTHING.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insertAll(Collection<?> beans, InsertOptions options);
/**
* Insert a collection of beans with an explicit transaction.
*/
void insertAll(Collection<?> beans, Transaction transaction);
/**
* Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insertAll(Collection<?> beans, InsertOptions options, Transaction transaction);
/**
* Execute explicitly passing a transaction.
*/
@@ -3,6 +3,8 @@ package io.ebean;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -206,14 +208,19 @@ public interface DtoQuery<T> extends CancelableQuery {
*/
DtoQuery<T> usingTransaction(Transaction transaction);
/**
* Execute the query using the given connection.
*/
DtoQuery<T> usingConnection(Connection connection);
/**
* 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
* say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(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
* source. We use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
DtoQuery<T> usingMaster();
@@ -0,0 +1,119 @@
package io.ebean;
import io.avaje.lang.Nullable;
/**
* Options to be used with insert such as ON CONFLICT DO UPDATE | NOTHING.
*/
public interface InsertOptions {
/**
* Use ON CONFLICT UPDATE with automatic determination of the unique columns to conflict on.
* <p>
* Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} .
*/
InsertOptions ON_CONFLICT_UPDATE = InsertOptions.builder()
.onConflictUpdate()
.build();
/**
* Use ON CONFLICT DO NOTHING with automatic determination of the unique columns to conflict on.
* <p>
* Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} .
*/
InsertOptions ON_CONFLICT_NOTHING = InsertOptions.builder()
.onConflictNothing()
.build();
/**
* Return a builder for InsertOptions.
*/
static Builder builder() {
return new DInsertOptionsBuilder();
}
/**
* Return the constraint name that is used for ON CONFLICT.
*/
@Nullable
String constraint();
/**
* Return the unique columns that is used for ON CONFLICT.
* <p>
* When not explicitly set will use mapping like {@code @Column(unique=true)} to determine the
* non-unique columns.
*/
@Nullable
String uniqueColumns();
/**
* Return the ON CONFLICT UPDATE SET clause.
* <p>
* When not set will use the non-unique columns.
*/
@Nullable
String updateSet();
/**
* Return if GetGeneratedKeys should be used to fetch the generated keys after insert.
*/
@Nullable
Boolean getGetGeneratedKeys();
/**
* Return the key for these build options.
*/
String key();
/**
* The builder for InsertOptions.
*/
interface Builder {
/**
* Use a ON CONFLICT UPDATE automatically determining the unique columns.
*/
Builder onConflictUpdate();
/**
* Use a ON CONFLICT DO NOTHING automatically determining the unique columns.
*/
Builder onConflictNothing();
/**
* Specify an explicit conflict constraint name.
* <p>
* When this is used then unique columns will not be used.
*/
Builder constraint(String constraint);
/**
* Specify the unique columns for the conflict target.
* <p>
* When not specified and constraint is also not specified then
* it will automatically determine the unique columns
* based on mapping like {@code @Column(unique=true)} and
* {@code @Index(unique=true)} .
*/
Builder uniqueColumns(String uniqueColumns);
/**
* Specify the ON CONFLICT DO UPDATE SET clause.
* <p>
* When not specified ebean will include all the non-unique columns.
*/
Builder updateSet(String updateSet);
/**
* Specify if GetGeneratedKeys should be used to return generated keys.
*/
Builder getGeneratedKeys(boolean getGeneratedKeys);
/**
* Build and return the insert options.
*/
InsertOptions build();
}
}
+21 -2
View File
@@ -702,9 +702,9 @@ public interface Query<T> extends CancelableQuery {
* 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
* say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(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
* source. We use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
Query<T> usingMaster();
@@ -970,6 +970,25 @@ public interface Query<T> extends CancelableQuery {
@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();
/**
* Return true if this is countDistinct query.
*/
@@ -3,7 +3,9 @@ package io.ebean;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import javax.sql.DataSource;
import java.io.Serializable;
import java.sql.Connection;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@@ -45,14 +47,19 @@ public interface SqlQuery extends Serializable, CancelableQuery {
*/
SqlQuery usingTransaction(Transaction transaction);
/**
* Execute the query using the given connection.
*/
SqlQuery usingConnection(Connection connection);
/**
* 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
* say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(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
* source. We use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
SqlQuery usingMaster();
@@ -141,14 +141,16 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
}
/**
* Set the actual underlying list.
* <p>
* This is primarily for the deferred fetching function.
*/
@SuppressWarnings("unchecked")
public void setActualList(List<?> list) {
this.list = (List<E>) list;
public BeanCollectionAdd collectionAdd() {
if (list == null) {
list = new ArrayList<>();
}
return this;
}
public void refresh(ModifyListenMode modifyListenMode, BeanList<E> newList) {
setModifyListening(modifyListenMode);
this.list = newList.actualList();
}
/**
@@ -1,9 +1,6 @@
package io.ebean.common;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.ToStringBuilder;
import io.ebean.bean.*;
import java.util.*;
@@ -17,12 +14,12 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
/**
* The underlying map implementation.
*/
private Map<K, E> map;
private LinkedHashMap<K, E> map;
/**
* Create with a given Map.
*/
public BeanMap(Map<K, E> map) {
public BeanMap(LinkedHashMap<K, E> map) {
this.map = map;
}
@@ -160,18 +157,23 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
}
}
/**
* Set the actual underlying map. Used for performing lazy fetch.
*/
public LinkedHashMap<K, E> collectionAdd() {
if (map == null) {
map = new LinkedHashMap<>();
}
return map;
}
@SuppressWarnings("unchecked")
public void setActualMap(Map<?, ?> map) {
this.map = (Map<K, E>) map;
public void refresh(ModifyListenMode modifyListenMode, BeanMap<?, ?> newMap) {
setModifyListening(modifyListenMode);
this.map = (LinkedHashMap<K, E>) newMap.actualMap();
}
/**
* Return the actual underlying map.
*/
public Map<K, E> actualMap() {
public LinkedHashMap<K, E> actualMap() {
return map;
}
@@ -3,10 +3,7 @@ package io.ebean.common;
import io.ebean.bean.*;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.*;
/**
* Set capable of lazy loading and modification aware.
@@ -18,12 +15,12 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
/**
* The underlying Set implementation.
*/
private Set<E> set;
private LinkedHashSet<E> set;
/**
* Create with a specific Set implementation.
*/
public BeanSet(Set<E> set) {
public BeanSet(LinkedHashSet<E> set) {
this.set = set;
}
@@ -144,18 +141,22 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
}
}
/**
* Set the underlying set (used for lazy fetch).
*/
@SuppressWarnings("unchecked")
public void setActualSet(Set<?> set) {
this.set = (Set<E>) set;
public BeanCollectionAdd collectionAdd() {
if (set == null) {
set = new LinkedHashSet<>();
}
return this;
}
public void refresh(ModifyListenMode modifyListenMode, BeanSet<E> newSet) {
setModifyListening(modifyListenMode);
this.set = newSet.actualSet();
}
/**
* Return the actual underlying set.
*/
public Set<E> actualSet() {
public LinkedHashSet<E> actualSet() {
return set;
}
@@ -0,0 +1,450 @@
package io.ebean.common;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.ToStringBuilder;
import java.util.*;
/**
* Map capable of lazy loading and modification aware.
*/
public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements SequencedMap<K, E> {
private static final long serialVersionUID = 1L;
/**
* The underlying map implementation.
*/
private LinkedHashMap<K, E> map;
/**
* Create with a given Map.
*/
public BeanMap(LinkedHashMap<K, E> map) {
this.map = map;
}
/**
* Create using a underlying LinkedHashMap.
*/
public BeanMap() {
this(new LinkedHashMap<>());
}
public BeanMap(BeanCollectionLoader ebeanServer, EntityBean ownerBean, String propertyName) {
super(ebeanServer, ownerBean, propertyName);
}
@Override
public void toString(ToStringBuilder builder) {
if (map == null || map.isEmpty()) {
builder.addRaw("{}");
} else {
builder.addRaw("{");
for (Entry<K, E> entry : map.entrySet()) {
builder.add(String.valueOf(entry.getKey()), entry.getValue());
}
builder.addRaw("}");
}
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.map = null;
}
@Override
public boolean isSkipSave() {
return map == null || (map.isEmpty() && !holdsModifications());
}
@Override
@SuppressWarnings("unchecked")
public void loadFrom(BeanCollection<?> other) {
BeanMap<K, E> otherMap = (BeanMap<K, E>) other;
internalPutNull();
map.putAll(otherMap.actualMap());
}
public void internalPutNull() {
if (map == null) {
map = new LinkedHashMap<>();
}
}
@SuppressWarnings("unchecked")
public void internalPut(Object key, Object bean) {
if (map == null) {
map = new LinkedHashMap<>();
}
if (key != null) {
map.put((K) key, (E) bean);
}
}
public void internalPutWithCheck(Object key, Object bean) {
if (map == null || key == null || !map.containsKey(key)) {
internalPut(key, bean);
}
}
@Override
public void internalAddWithCheck(Object bean) {
throw new RuntimeException("Not allowed for map");
}
@Override
public void internalAdd(Object bean) {
throw new RuntimeException("Not allowed for map");
}
/**
* Return true if the underlying map has been populated. Returns false if it
* has a deferred fetch pending.
*/
@Override
public boolean isPopulated() {
return map != null;
}
/**
* Return true if this is a reference (lazy loading) bean collection. This is
* the same as !isPopulated();
*/
@Override
public boolean isReference() {
return map == null;
}
@Override
public boolean checkEmptyLazyLoad() {
if (map == null) {
map = new LinkedHashMap<>();
return true;
} else {
return false;
}
}
private void initClear() {
lock.lock();
try {
if (map == null) {
if (!disableLazyLoad && modifyListening) {
lazyLoadCollection(true);
} else {
map = new LinkedHashMap<>();
}
}
} finally {
lock.unlock();
}
}
private void init() {
lock.lock();
try {
if (map == null) {
if (disableLazyLoad) {
map = new LinkedHashMap<>();
} else {
lazyLoadCollection(false);
}
}
} finally {
lock.unlock();
}
}
public LinkedHashMap<K, E> collectionAdd() {
if (map == null) {
map = new LinkedHashMap<>();
}
return map;
}
@SuppressWarnings("unchecked")
public void refresh(ModifyListenMode modifyListenMode, BeanMap<?, ?> newMap) {
setModifyListening(modifyListenMode);
this.map = (LinkedHashMap<K, E>) newMap.actualMap();
}
/**
* Return the actual underlying map.
*/
public LinkedHashMap<K, E> actualMap() {
return map;
}
/**
* Returns the collection of beans (map values).
*/
@Override
public Collection<E> actualDetails() {
return map.values();
}
/**
* Returns the map entrySet.
*/
@Override
public Collection<?> actualEntries() {
return map.entrySet();
}
@Override
public String toString() {
if (map == null) {
return "BeanMap<deferred>";
} else {
return map.toString();
}
}
/**
* Equal if object is a Map and equal in a Map sense.
*/
@Override
public boolean equals(Object object) {
init();
return map.equals(object);
}
@Override
public int hashCode() {
init();
return map.hashCode();
}
@Override
public void clear() {
checkReadOnly();
initClear();
if (modifyListening) {
// add all beans to the removal list
for (E bean : map.values()) {
modifyRemoval(bean);
}
}
map.clear();
}
@Override
public boolean containsKey(Object key) {
init();
return map.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
init();
return map.containsValue(value);
}
@Override
public Set<Entry<K, E>> entrySet() {
init();
if (readOnly) {
return Collections.unmodifiableSet(map.entrySet());
}
return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet();
}
@Override
public E get(Object key) {
init();
return map.get(key);
}
@Override
public boolean isEmpty() {
init();
return map.isEmpty();
}
@Override
public Set<K> keySet() {
init();
if (readOnly) {
return Collections.unmodifiableSet(map.keySet());
}
return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet();
}
@Override
public E put(K key, E value) {
checkReadOnly();
init();
if (modifyListening) {
E oldBean = map.put(key, value);
if (value != oldBean) {
// register the add of the new and the removal of the old
modifyAddition(value);
modifyRemoval(oldBean);
}
return oldBean;
} else {
return map.put(key, value);
}
}
@Override
public void putAll(Map<? extends K, ? extends E> puts) {
checkReadOnly();
init();
if (modifyListening) {
for (Entry<? extends K, ? extends E> entry : puts.entrySet()) {
Object oldBean = map.put(entry.getKey(), entry.getValue());
if (entry.getValue() != oldBean) {
modifyAddition(entry.getValue());
modifyRemoval(oldBean);
}
}
} else {
map.putAll(puts);
}
}
@Override
public void addBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
}
@Override
public void removeBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
}
@Override
public E remove(Object key) {
checkReadOnly();
init();
if (modifyListening) {
E o = map.remove(key);
modifyRemoval(o);
return o;
}
return map.remove(key);
}
@Override
public int size() {
init();
return map.size();
}
@Override
public Collection<E> values() {
init();
if (readOnly) {
return Collections.unmodifiableCollection(map.values());
}
return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values();
}
@Override
public BeanCollection<E> shallowCopy() {
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
copy.setFromOriginal(this);
return copy;
}
@Override
public SequencedMap<K, E> reversed() {
init();
if (modifyListening) {
throw new UnsupportedOperationException("Not supported on modify listening map");
}
return map.reversed();
}
@Override
public Entry<K, E> firstEntry() {
init();
return map.firstEntry();
}
@Override
public Entry<K, E> lastEntry() {
init();
return map.lastEntry();
}
@Override
public Entry<K, E> pollFirstEntry() {
checkReadOnly();
init();
var entry = map.pollFirstEntry();
if (modifyListening && entry != null) {
modifyRemoval(entry.getValue());
}
return entry;
}
@Override
public Entry<K, E> pollLastEntry() {
checkReadOnly();
init();
var entry = map.pollLastEntry();
if (modifyListening && entry != null) {
modifyRemoval(entry.getValue());
}
return entry;
}
@Override
public E putFirst(K key, E value) {
checkReadOnly();
init();
if (modifyListening) {
E oldBean = map.putFirst(key, value);
if (value != oldBean) {
// register the add of the new and the removal of the old
modifyAddition(value);
modifyRemoval(oldBean);
}
return oldBean;
} else {
return map.putFirst(key, value);
}
}
@Override
public E putLast(K key, E value) {
checkReadOnly();
init();
if (modifyListening) {
E oldBean = map.putLast(key, value);
if (value != oldBean) {
// register the add of the new and the removal of the old
modifyAddition(value);
modifyRemoval(oldBean);
}
return oldBean;
} else {
return map.putLast(key, value);
}
}
@Override
public SequencedSet<K> sequencedKeySet() {
init();
return map.sequencedKeySet();
}
@Override
public SequencedCollection<E> sequencedValues() {
init();
return map.sequencedValues();
}
@Override
public SequencedSet<Entry<K, E>> sequencedEntrySet() {
init();
return map.sequencedEntrySet();
}
}
@@ -0,0 +1,460 @@
package io.ebean.common;
import io.ebean.bean.*;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.SequencedSet;
/**
* Set capable of lazy loading and modification aware.
*/
public final class BeanSet<E> extends AbstractBeanCollection<E> implements SequencedSet<E>, BeanCollectionAdd {
private static final long serialVersionUID = 1L;
/**
* The underlying Set implementation.
*/
private LinkedHashSet<E> set;
/**
* Create with a specific Set implementation.
*/
public BeanSet(LinkedHashSet<E> set) {
this.set = set;
}
/**
* Create using an underlying LinkedHashSet.
*/
public BeanSet() {
this(new LinkedHashSet<>());
}
public BeanSet(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) {
super(loader, ownerBean, propertyName);
}
@Override
public void toString(ToStringBuilder builder) {
builder.addCollection(set);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.set = null;
}
@Override
public boolean isSkipSave() {
return set == null || (set.isEmpty() && !holdsModifications());
}
@Override
@SuppressWarnings("unchecked")
public void addEntityBean(EntityBean bean) {
set.add((E) bean);
}
@Override
@SuppressWarnings("unchecked")
public void loadFrom(BeanCollection<?> other) {
if (set == null) {
set = new LinkedHashSet<>();
}
set.addAll((Collection<? extends E>) other.actualDetails());
}
@Override
public void internalAddWithCheck(Object bean) {
// set add() already de-dups so just add it
internalAdd(bean);
}
@Override
@SuppressWarnings("unchecked")
public void internalAdd(Object bean) {
if (set == null) {
set = new LinkedHashSet<>();
}
if (bean != null) {
set.add((E) bean);
}
}
/**
* Returns true if the underlying set has its data.
*/
@Override
public boolean isPopulated() {
return set != null;
}
/**
* Return true if this is a reference (lazy loading) bean collection. This is
* the same as !isPopulated();
*/
@Override
public boolean isReference() {
return set == null;
}
@Override
public boolean checkEmptyLazyLoad() {
if (set == null) {
set = new LinkedHashSet<>();
return true;
} else {
return false;
}
}
private void initClear() {
lock.lock();
try {
if (set == null) {
if (!disableLazyLoad && modifyListening) {
lazyLoadCollection(false);
} else {
set = new LinkedHashSet<>();
}
}
} finally {
lock.unlock();
}
}
private void init() {
lock.lock();
try {
if (set == null) {
if (disableLazyLoad) {
set = new LinkedHashSet<>();
} else {
lazyLoadCollection(false);
}
}
} finally {
lock.unlock();
}
}
public BeanCollectionAdd collectionAdd() {
if (set == null) {
set = new LinkedHashSet<>();
}
return this;
}
public void refresh(ModifyListenMode modifyListenMode, BeanSet<E> newSet) {
setModifyListening(modifyListenMode);
this.set = newSet.actualSet();
}
/**
* Return the actual underlying set.
*/
public LinkedHashSet<E> actualSet() {
return set;
}
@Override
public Collection<E> actualDetails() {
return set;
}
@Override
public Collection<?> actualEntries() {
return set;
}
@Override
public String toString() {
if (set == null) {
return "BeanSet<deferred>";
} else {
return set.toString();
}
}
/**
* Equal if obj is a Set and equal in a Set sense.
*/
@Override
public boolean equals(Object obj) {
init();
return set.equals(obj);
}
@Override
public int hashCode() {
init();
return set.hashCode();
}
@Override
public void addBean(E bean) {
add(bean);
}
@Override
public void removeBean(E bean) {
if (set.remove(bean)) {
getModifyHolder().modifyRemoval(bean);
}
}
// -----------------------------------------------------//
// proxy method for map
// -----------------------------------------------------//
@Override
public boolean add(E bean) {
checkReadOnly();
init();
if (modifyListening) {
if (set.add(bean)) {
modifyAddition(bean);
return true;
} else {
return false;
}
}
return set.add(bean);
}
@Override
public boolean addAll(Collection<? extends E> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
for (E bean : beans) {
if (set.add(bean)) {
// register the addition of the bean
modifyAddition(bean);
changed = true;
}
}
return changed;
}
return set.addAll(beans);
}
@Override
public void clear() {
checkReadOnly();
initClear();
if (modifyListening) {
for (E bean : set) {
modifyRemoval(bean);
}
}
set.clear();
}
@Override
public boolean contains(Object bean) {
init();
return set.contains(bean);
}
@Override
public boolean containsAll(Collection<?> beans) {
init();
return set.containsAll(beans);
}
@Override
public boolean isEmpty() {
init();
return set.isEmpty();
}
@Override
public Iterator<E> iterator() {
init();
if (readOnly) {
return new ReadOnlyIterator<>(set.iterator());
}
if (modifyListening) {
return new ModifyIterator<>(this, set.iterator());
}
return set.iterator();
}
@Override
public boolean remove(Object bean) {
checkReadOnly();
init();
if (modifyListening) {
if (set.remove(bean)) {
modifyRemoval(bean);
return true;
}
return false;
}
return set.remove(bean);
}
@Override
public boolean removeAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
for (Object bean : beans) {
if (set.remove(bean)) {
modifyRemoval(bean);
changed = true;
}
}
return changed;
}
return set.removeAll(beans);
}
@Override
public boolean retainAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
Iterator<?> it = set.iterator();
while (it.hasNext()) {
Object bean = it.next();
if (!beans.contains(bean)) {
// not retaining this bean so add it to the removal list
it.remove();
modifyRemoval(bean);
changed = true;
}
}
return changed;
}
return set.retainAll(beans);
}
@Override
public int size() {
init();
return set.size();
}
@Override
public Object[] toArray() {
init();
return set.toArray();
}
@Override
public <T> T[] toArray(T[] array) {
init();
//noinspection SuspiciousToArrayCall
return set.toArray(array);
}
@Override
public SequencedSet<E> reversed() {
init();
if (modifyListening) {
throw new UnsupportedOperationException("Not supported on modify listening set");
}
return set.reversed();
}
@Override
public void addFirst(E bean) {
checkReadOnly();
init();
if (modifyListening) {
modifyAddition(bean);
}
set.addFirst(bean);
}
@Override
public void addLast(E bean) {
checkReadOnly();
init();
if (modifyListening) {
modifyAddition(bean);
}
set.addLast(bean);
}
@Override
public E getFirst() {
init();
return set.getFirst();
}
@Override
public E getLast() {
init();
return set.getLast();
}
@Override
public E removeFirst() {
checkReadOnly();
init();
if (modifyListening) {
var bean = set.removeFirst();
modifyRemoval(bean);
return bean;
}
return set.removeFirst();
}
@Override
public E removeLast() {
checkReadOnly();
init();
if (modifyListening) {
var bean = set.removeLast();
modifyRemoval(bean);
return bean;
}
return set.removeLast();
}
private static final class ReadOnlyIterator<E> implements Iterator<E>, Serializable {
private static final long serialVersionUID = 2577697326745352605L;
private final Iterator<E> it;
ReadOnlyIterator(Iterator<E> it) {
this.it = it;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public E next() {
return it.next();
}
@Override
public void remove() {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
@Override
public BeanCollection<E> shallowCopy() {
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
copy.setFromOriginal(this);
return copy;
}
}
@@ -136,13 +136,13 @@ class ToStringBuilderTest {
@Test
void beanSet_null_empty() {
assertThat(toStringFor(new BeanSet<String>(null))).isEqualTo("[]");
assertThat(toStringFor(new BeanSet<String>(Collections.emptySet()))).isEqualTo("[]");
assertThat(toStringFor(new BeanSet<String>(new LinkedHashSet<>()))).isEqualTo("[]");
}
@Test
void beanMap_null_empty() {
assertThat(toStringFor(new BeanMap<String, String>(null))).isEqualTo("{}");
assertThat(toStringFor(new BeanMap<String, String>(Collections.emptyMap()))).isEqualTo("{}");
assertThat(toStringFor(new BeanMap<String, String>(new LinkedHashMap<>()))).isEqualTo("{}");
}
@Test
@@ -159,7 +159,7 @@ class ToStringBuilderTest {
@Test
void beanMap_some() {
Map<String, Recurse> under = new LinkedHashMap<>();
var under = new LinkedHashMap<String, Recurse>();
under.put("a", new Recurse(1, "a"));
under.put("b", new Recurse(2, "b"));
BeanMap<String, Recurse> list = new BeanMap<>(under);
+26 -26
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<!-- platforms -->
@@ -193,79 +193,79 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
+6 -6
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,7 +22,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -46,7 +46,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -159,21 +159,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
@@ -654,6 +654,16 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
ObjectGraphNode parentNode();
/**
* Set that this is a future query that will execute in the background.
*/
void usingFuture();
/**
* Return true if this is a future query.
*/
boolean isUsingFuture();
/**
* Return false when this is a lazy load or refresh query for a bean.
* <p>
@@ -1273,6 +1273,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> FutureRowCount<T> findFutureCount(SpiQuery<T> query) {
SpiQuery<T> copy = query.copy();
copy.usingFuture();
boolean createdTransaction = false;
SpiTransaction transaction = query.transaction();
if (transaction == null) {
@@ -1291,6 +1292,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> FutureIds<T> findFutureIds(SpiQuery<T> query) {
SpiQuery<T> copy = query.copy();
copy.usingFuture();
boolean createdTransaction = false;
SpiTransaction transaction = query.transaction();
if (transaction == null) {
@@ -1309,6 +1311,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> FutureList<T> findFutureList(SpiQuery<T> query) {
SpiQuery<T> spiQuery = query.copy();
spiQuery.usingFuture();
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
if (!spiQuery.isDisableReadAudit()) {
@@ -1651,41 +1654,50 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}, transaction);
}
/**
* Insert the bean.
*/
@Override
public void insert(Object bean) {
insert(bean, null);
persister.insert(checkEntityBean(bean), null, null);
}
@Override
public void insert(Object bean, @Nullable InsertOptions insertOptions) {
persister.insert(checkEntityBean(bean), insertOptions, null);
}
/**
* Insert the bean with a transaction.
*/
@Override
public void insert(Object bean, @Nullable Transaction transaction) {
persister.insert(checkEntityBean(bean), transaction);
persister.insert(checkEntityBean(bean), null, transaction);
}
@Override
public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) {
persister.insert(checkEntityBean(bean), insertOptions, transaction);
}
/**
* Insert all beans in the collection.
*/
@Override
public void insertAll(Collection<?> beans) {
insertAll(beans, null);
insertAll(beans, null, null);
}
@Override
public void insertAll(Collection<?> beans, InsertOptions options) {
insertAll(beans, options, null);
}
/**
* Insert all beans in the collection with a transaction.
*/
@Override
public void insertAll(@Nullable Collection<?> beans, @Nullable Transaction transaction) {
insertAll(beans, null, transaction);
}
@Override
public void insertAll(@Nullable Collection<?> beans, InsertOptions options, @Nullable Transaction transaction) {
if (beans == null || beans.isEmpty()) {
return;
}
executeInTrans((txn) -> {
txn.checkBatchEscalationOnCollection();
for (Object bean : beans) {
persister.insert(checkEntityBean(bean), txn);
persister.insert(checkEntityBean(bean), options, txn);
}
return 0;
}, transaction);
@@ -35,7 +35,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private final OrmQueryEngine queryEngine;
private final SpiQuery<T> query;
private final BeanFindController finder;
private final Boolean readOnly;
private LoadContext loadContext;
private PersistenceContext persistenceContext;
private HashQuery cacheKey;
@@ -52,7 +51,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
this.finder = beanDescriptor.beanFinder();
this.queryEngine = queryEngine;
this.query = query;
this.readOnly = query.isReadOnly();
this.persistenceContext = query.persistenceContext();
}
@@ -135,13 +133,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return loadContext.secondaryQueriesMinBatchSize();
}
/**
* Return the Normal, sharedInstance, ReadOnly state of this query.
*/
public Boolean isReadOnly() {
return readOnly;
}
/**
* Return the BeanDescriptor for the associated bean.
*/
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.core;
import io.ebean.InsertOptions;
import io.ebean.ValuePair;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.EntityBean;
@@ -124,6 +125,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Many-to-many intersection table changes that are held for later batch processing.
*/
private List<SaveMany> saveMany;
private InsertOptions insertOptions;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
@@ -380,6 +382,14 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return intercept.dirtyPropertyNames();
}
public boolean isChangedProperty(int propertyIndex) {
if (dirtyProperties == null) {
return intercept.isChangedProperty(propertyIndex);
} else {
return dirtyProperties[propertyIndex];
}
}
/**
* Return the dirty properties on this request.
*/
@@ -865,8 +875,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
setNotifyCache();
boolean isChangeLog = beanDescriptor.isChangeLog();
if (type == Type.UPDATE && (isChangeLog || notifyCache || docStoreMode == DocStoreMode.UPDATE)) {
// get the dirty properties for update notification to the doc store
if (type == Type.UPDATE) {
// get the dirty properties for notify cache & orphanRemoval of vanilla collection detection
dirtyProperties = intercept.dirtyProperties();
}
if (isChangeLog) {
@@ -1408,4 +1418,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private void setGeneratedId() {
beanDescriptor.setGeneratedId(entityBean, transaction);
}
public void setInsertOptions(InsertOptions insertOptions) {
this.insertOptions = insertOptions;
}
public InsertOptions insertOptions() {
return insertOptions;
}
}
@@ -1,11 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.CallableSql;
import io.ebean.MergeOptions;
import io.ebean.Query;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.Update;
import io.avaje.lang.Nullable;
import io.ebean.*;
import io.ebean.bean.EntityBean;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiSqlUpdate;
@@ -31,9 +27,9 @@ public interface Persister {
void update(EntityBean entityBean, Transaction t);
/**
* Force an Insert using the given bean.
* Perform an Insert using the given bean.
*/
void insert(EntityBean entityBean, Transaction t);
void insert(EntityBean entityBean, @Nullable InsertOptions insertOptions, @Nullable Transaction t);
/**
* Insert or update the bean depending on its state.
@@ -44,7 +40,6 @@ public interface Persister {
* Delete a bean given it's type and id value.
* <p>
* This will also cascade delete one level of children.
* </p>
*/
int delete(Class<?> beanType, Object id, Transaction transaction, boolean permanent);
@@ -1,12 +1,9 @@
package io.ebeaninternal.server.deploy;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.query.CQueryCollectionAdd;
@@ -34,7 +31,7 @@ public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
* For Map's this needs to take the mapKey.
* </p>
*/
BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey);
BeanCollectionAdd collectionAdd(Object bc, String mapKey);
/**
* Create an empty collection of the correct type without a parent bean.
@@ -1,16 +1,12 @@
package io.ebeaninternal.server.deploy;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanList;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -23,19 +19,11 @@ public class BeanListHelp<T> extends BaseCollectionHelp<T> {
super(many);
}
BeanListHelp() {
super();
}
@Override
public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
if (bc instanceof BeanList<?>) {
BeanList<?> bl = (BeanList<?>) bc;
if (bl.actualList() == null) {
bl.setActualList(new ArrayList<>());
}
return bl;
return bl.collectionAdd();
} else {
throw new RuntimeException("Unhandled type " + bc);
}
@@ -67,20 +55,20 @@ public class BeanListHelp<T> extends BaseCollectionHelp<T> {
return beanList;
}
@SuppressWarnings("unchecked")
@Override
public final void refresh(BeanCollection<?> bc, EntityBean parentBean) {
BeanList<?> newBeanList = (BeanList<?>) bc;
BeanList<T> newBeanList = (BeanList<T>) bc;
List<?> currentList = (List<?>) many.getValue(parentBean);
newBeanList.setModifyListening(many.modifyListenMode());
if (currentList == null) {
// the currentList is null? Not really expecting this...
many.setValue(parentBean, newBeanList);
} else if (currentList instanceof BeanList<?>) {
} else if (currentList instanceof BeanList) {
// normally this case, replace just the underlying list
BeanList<?> currentBeanList = (BeanList<?>) currentList;
currentBeanList.setActualList(newBeanList.actualList());
currentBeanList.setModifyListening(many.modifyListenMode());
BeanList<T> currentBeanList = (BeanList<T>) currentList;
currentBeanList.refresh(many.modifyListenMode(), newBeanList);
} else {
// replace the entire list with the BeanList
@@ -1,17 +1,13 @@
package io.ebeaninternal.server.deploy;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanMap;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -37,20 +33,14 @@ public class BeanMapHelp<T> extends BaseCollectionHelp<T> {
@Override
@SuppressWarnings("unchecked")
public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
if (mapKey == null) {
mapKey = many.mapKey();
}
BeanProperty beanProp = targetDescriptor.beanProperty(mapKey);
if (bc instanceof BeanMap<?, ?>) {
BeanMap<Object, Object> bm = (BeanMap<Object, Object>) bc;
Map<Object, Object> actualMap = bm.actualMap();
if (actualMap == null) {
actualMap = new LinkedHashMap<>();
bm.setActualMap(actualMap);
}
return new Adder(beanProp, actualMap);
return new Adder(beanProp, bm.collectionAdd());
} else {
throw new RuntimeException("Unhandled type " + bc);
}
@@ -126,8 +116,7 @@ public class BeanMapHelp<T> extends BaseCollectionHelp<T> {
} else if (current instanceof BeanMap<?, ?>) {
// normally this case, replace just the underlying list
BeanMap<?, ?> currentBeanMap = (BeanMap<?, ?>) current;
currentBeanMap.setActualMap(newBeanMap.actualMap());
currentBeanMap.setModifyListening(many.modifyListenMode());
currentBeanMap.refresh(many.modifyListenMode(), newBeanMap);
} else {
// replace the entire set
@@ -655,7 +655,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
private BeanCollectionAdd beanCollectionAdd(Object bc) {
return help.getBeanCollectionAdd(bc, null);
return help.collectionAdd(bc, null);
}
public Object parentId(EntityBean parentBean) {
@@ -1,17 +1,13 @@
package io.ebeaninternal.server.deploy;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanSet;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
@@ -26,21 +22,11 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
super(many);
}
/**
* For a query that returns a set.
*/
BeanSetHelp() {
super();
}
@Override
public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
if (bc instanceof BeanSet<?>) {
BeanSet<?> beanSet = (BeanSet<?>) bc;
if (beanSet.actualSet() == null) {
beanSet.setActualSet(new LinkedHashSet<>());
}
return beanSet;
return beanSet.collectionAdd();
} else {
throw new RuntimeException("Unhandled type " + bc);
}
@@ -72,9 +58,10 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
return beanSet;
}
@SuppressWarnings("unchecked")
@Override
public final void refresh(BeanCollection<?> bc, EntityBean parentBean) {
BeanSet<?> newBeanSet = (BeanSet<?>) bc;
BeanSet<T> newBeanSet = (BeanSet<T>) bc;
Set<?> current = (Set<?>) many.getValue(parentBean);
newBeanSet.setModifyListening(many.modifyListenMode());
if (current == null) {
@@ -83,9 +70,8 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
} else if (current instanceof BeanSet<?>) {
// normally this case, replace just the underlying list
BeanSet<?> currentBeanSet = (BeanSet<?>) current;
currentBeanSet.setActualSet(newBeanSet.actualSet());
currentBeanSet.setModifyListening(many.modifyListenMode());
BeanSet<T> currentBeanSet = (BeanSet<T>) current;
currentBeanSet.refresh(many.modifyListenMode(), newBeanSet);
} else {
// replace the entire set
@@ -4,7 +4,6 @@ import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanMap;
import java.util.LinkedHashMap;
import java.util.Map;
final class ElementHelpMap implements ElementHelp {
@@ -15,7 +14,7 @@ final class ElementHelpMap implements ElementHelp {
private static class Collector implements ElementCollector {
private final Map<Object, Object> map = new LinkedHashMap<>();
private final LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
@Override
public void addElement(Object element) {
@@ -4,7 +4,6 @@ import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanSet;
import java.util.LinkedHashSet;
import java.util.Set;
final class ElementHelpSet implements ElementHelp {
@@ -15,7 +14,7 @@ final class ElementHelpSet implements ElementHelp {
private static class Collector implements ElementCollector {
private final Set<Object> set = new LinkedHashSet<>();
private final LinkedHashSet<Object> set = new LinkedHashSet<>();
@Override
public void addElement(Object element) {
@@ -122,7 +122,7 @@ public final class DeployCreateProperties {
return new DeployBeanProperty(desc, propertyType, field.getGenericType());
}
// check for Collection type (list, set or map)
ManyType manyType = determineManyType.getManyType(propertyType);
ManyType manyType = determineManyType.manyType(propertyType);
if (manyType != null) {
// List, Set or Map based object
Class<?> targetType = determineTargetType(field);
@@ -11,14 +11,14 @@ import java.util.Set;
*/
final class DetermineManyType {
ManyType getManyType(Class<?> type) {
ManyType manyType(Class<?> type) {
if (type.equals(List.class)) {
return ManyType.LIST;
}
if (type.equals(Set.class)) {
if (type.equals(Set.class) || type.getCanonicalName().equals("java.util.SequencedSet")) {
return ManyType.SET;
}
if (type.equals(Map.class)) {
if (type.equals(Map.class) || type.getCanonicalName().equals("java.util.SequencedMap")) {
return ManyType.MAP;
}
return null;
@@ -411,7 +411,7 @@ public final class DefaultPersister implements Persister {
if (bean._ebean_getIntercept().isUpdate()) {
update(bean, t);
} else {
insert(bean, t);
insert(bean, null, t);
}
}
@@ -419,12 +419,15 @@ public final class DefaultPersister implements Persister {
* Insert this bean.
*/
@Override
public void insert(EntityBean bean, Transaction t) {
public void insert(EntityBean bean, InsertOptions insertOptions, Transaction t) {
PersistRequestBean<?> req = createRequest(bean, t, PersistRequest.Type.INSERT);
if (req.isSkipReference()) {
// skip insert on reference bean
return;
}
if (insertOptions != null) {
req.setInsertOptions(insertOptions);
}
try {
req.initTransIfRequiredWithBatchCascade();
insert(req);
@@ -328,7 +328,7 @@ final class SaveManyBeans extends SaveManyBase {
}
private boolean isChangedProperty() {
return parentBean._ebean_getIntercept().isChangedProperty(many.propertyIndex());
return request.isChangedProperty(many.propertyIndex());
}
private void removeAssocManyOrphans() {
@@ -1,11 +1,15 @@
package io.ebeaninternal.server.persist.dml;
import java.util.ArrayList;
import java.util.List;
/**
* Helper to support the generation of DML statements.
*/
public final class GenerateDmlRequest {
private final StringBuilder sb = new StringBuilder(100);
private final List<String> columns = new ArrayList<>();
private StringBuilder insertBindBuffer;
private String prefix;
private String prefix2;
@@ -26,7 +30,7 @@ public final class GenerateDmlRequest {
++bindColumnCount;
sb.append(prefix);
sb.append(column);
//sb.append(expr);
columns.add(column);
if (insertMode > 0) {
if (insertMode++ > 1) {
insertBindBuffer.append(',');
@@ -75,4 +79,8 @@ public final class GenerateDmlRequest {
public boolean isUpdate() {
return insertMode == 0;
}
public List<String> columns() {
return columns;
}
}
@@ -69,7 +69,7 @@ public final class InsertHandler extends DmlHandler {
SpiTransaction t = persistRequest.transaction();
// get the appropriate sql
sql = meta.getSql(withId, persistRequest.isPublish());
sql = meta.sql(withId, persistRequest.isPublish(), persistRequest.insertOptions());
PreparedStatement pstmt;
if (persistRequest.isBatched()) {
pstmt = pstmtBatch(t, sql, persistRequest, useGeneratedKeys);
@@ -134,7 +134,8 @@ public final class InsertHandler extends DmlHandler {
if (idValue != null) {
persistRequest.setGeneratedKey(idValue);
}
} else {
} else if (persistRequest.insertOptions() == null) {
// insert on conflict do nothing can not return generated key
throw new PersistenceException("Autoincrement getGeneratedKeys() returned no rows?");
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
import io.ebean.annotation.Platform;
import io.ebean.bean.EntityBean;
import io.ebean.config.dbplatform.DatabasePlatform;
@@ -36,9 +37,11 @@ final class InsertMeta {
private final Bindable shadowFKey;
private final String[] identityDbColumns;
private final Platform platform;
private final InsertMetaOptions options;
InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor<?> desc, Bindable shadowFKey, BindableId id, BindableList all) {
this.platform = dbPlatform.platform();
this.options = InsertMetaPlatform.create(platform, desc, this);
this.discriminator = discriminator(desc);
this.id = id;
this.all = all;
@@ -47,8 +50,8 @@ final class InsertMeta {
String tableName = desc.baseTable();
String draftTableName = desc.draftTable();
this.sqlWithId = genSql(false, tableName, false);
this.sqlDraftWithId = desc.isDraftable() ? genSql(false, draftTableName, true) : sqlWithId;
this.sqlWithId = sql(false, tableName, false);
this.sqlDraftWithId = desc.isDraftable() ? sql(false, draftTableName, true) : sqlWithId;
// only available for single Id property
if (id.isConcatenated()) {
@@ -72,8 +75,8 @@ final class InsertMeta {
this.supportsGetGeneratedKeys = dbPlatform.dbIdentity().isSupportsGetGeneratedKeys();
this.supportsSelectLastInsertedId = desc.supportsSelectLastInsertedId();
}
this.sqlNullId = genSql(true, tableName, false);
this.sqlDraftNullId = desc.isDraftable() ? genSql(true, draftTableName, true) : sqlNullId;
this.sqlNullId = sql(true, tableName, false);
this.sqlDraftNullId = desc.isDraftable() ? sql(true, draftTableName, true) : sqlNullId;
}
}
@@ -137,9 +140,20 @@ final class InsertMeta {
}
/**
* get the sql based whether the id value(s) are null.
* Return the sql for the given options.
*/
public String getSql(boolean withId, boolean publish) {
public String sql(boolean withId, boolean publish, InsertOptions insertOptions) {
if (insertOptions == null) {
return sql(withId, publish);
}
return options.sql(withId, insertOptions);
}
String sqlFor(boolean withId) {
return withId ? sqlWithId : sqlNullId;
}
private String sql(boolean withId, boolean publish) {
if (withId) {
return publish ? sqlWithId : sqlDraftWithId;
} else {
@@ -147,12 +161,18 @@ final class InsertMeta {
}
}
private String genSql(boolean nullId, String table, boolean draftTable) {
private String sql(boolean nullId, String table, boolean draftTable) {
GenerateDmlRequest request = new GenerateDmlRequest();
sql(request, nullId, table, draftTable);
return request.toString();
}
void sql(GenerateDmlRequest request, boolean nullId, String table, boolean draftTable) {
request.setInsertSetMode();
request.append("insert into ").append(table);
if (nullId && noColumnsForInsert(draftTable)) {
return request.append(defaultValues()).toString();
request.append(defaultValues());
return;
}
request.append(" (");
if (!nullId) {
@@ -172,7 +192,6 @@ final class InsertMeta {
request.append(") values (");
request.append(request.insertBindBuffer());
request.append(")");
return request.toString();
}
private String defaultValues() {
@@ -196,5 +215,4 @@ final class InsertMeta {
&& discriminator == null
&& (draftTable ? all.isEmpty() : allExcludeDraftOnly.isEmpty());
}
}
@@ -0,0 +1,14 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
/**
* Generator for insert SQL with options.
*/
interface InsertMetaOptions {
/**
* Generate the SQL for the given insert options.
*/
String sql(boolean withId, InsertOptions insertOptions);
}
@@ -0,0 +1,113 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Postgres specific generation of insert on conflict.
*/
final class InsertMetaOptionsPostgres implements InsertMetaOptions {
private final InsertMeta meta;
private final BeanDescriptor<?> desc;
private final String baseTable;
private final Map<String, String> sqlCache = new ConcurrentHashMap<>();
InsertMetaOptionsPostgres(InsertMeta meta, BeanDescriptor<?> desc) {
this.meta = meta;
this.desc = desc;
this.baseTable = desc.baseTable();
}
@Override
public String sql(boolean withId, InsertOptions options) {
String key = withId + options.key();
return sqlCache.computeIfAbsent(key, k -> generate(withId, options));
}
private String generate(boolean withId, InsertOptions options) {
char type = options.key().charAt(0);
switch (type) {
case 'U':
return generate(withId, false, options);
case 'N':
return generate(withId, true, options);
default:
return meta.sqlFor(withId);
}
}
private String generate(boolean withId, boolean doNothing, InsertOptions options) {
GenerateDmlRequest request = new GenerateDmlRequest();
meta.sql(request, !withId, baseTable, false);
request.append(" on conflict ");
List<String> uniqueColumns = desc.uniqueProps().stream()
.flatMap(Arrays::stream)
.map(BeanProperty::dbColumn)
.collect(Collectors.toList());
String constraintName = options.constraint();
if (constraintName != null) {
request.append("on constraint ").append(constraintName);
} else {
request.append("(");
String cols = options.uniqueColumns();
if (cols != null) {
request.append(cols);
} else {
appendUniqueColumns(uniqueColumns, request);
}
request.append(")");
}
if (doNothing) {
request.append(" do nothing");
return request.toString();
}
request.append(" do update set ");
String updateSet = options.updateSet();
if (updateSet != null) {
request.append(updateSet);
} else {
setColumns(withId, request, uniqueColumns);
}
return request.toString();
}
private void setColumns(boolean withId, GenerateDmlRequest request, List<String> uniqueColumns) {
List<String> columns = request.columns();
columns.removeAll(uniqueColumns);
if (withId) {
BeanProperty idProperty = desc.idProperty();
if (idProperty != null && !idProperty.isEmbedded()) {
columns.remove(idProperty.dbColumn());
}
}
for (int i = 0; i < columns.size(); i++) {
if (i > 0) {
request.append(", ");
}
String col = columns.get(i);
request.append(col).append("=excluded.").append(col);
}
}
private static void appendUniqueColumns(List<String> uniqueColumns, GenerateDmlRequest request) {
if (uniqueColumns.isEmpty()) {
throw new IllegalStateException("Unable to identify unique columns for INSERT ON CONFLICT - Add mapping like @Column(unique=true) or @Index(unique=true)");
}
for (int i = 0; i < uniqueColumns.size(); i++) {
if (i > 0) {
request.append(", ");
}
request.append(uniqueColumns.get(i));
}
}
}
@@ -0,0 +1,29 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
import io.ebean.annotation.Platform;
import io.ebeaninternal.server.deploy.BeanDescriptor;
final class InsertMetaPlatform {
private static final NotSupported NOT_SUPPORTED = new NotSupported();
static InsertMetaOptions create(Platform platform, BeanDescriptor<?> desc, InsertMeta meta) {
switch (platform.base()) {
case POSTGRES:
case YUGABYTE:
case COCKROACH:
return new InsertMetaOptionsPostgres(meta, desc);
default:
return NOT_SUPPORTED;
}
}
static final class NotSupported implements InsertMetaOptions {
@Override
public String sql(boolean withId, InsertOptions insertOptions) {
throw new UnsupportedOperationException("InsertOptions not supported on this database platform");
}
}
}
@@ -186,7 +186,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
this.queryMode = query.mode();
this.loadContextBean = queryMode.isLoadContextBean() || query.getForUpdateLockType() != null;
this.lazyLoadManyProperty = query.lazyLoadMany();
this.readOnly = request.isReadOnly();
this.readOnly = query.isReadOnly();
this.disableLazyLoading = query.isDisableLazyLoading();
this.objectGraphNode = query.parentNode();
this.profilingListener = query.profilingListener();
@@ -305,6 +305,11 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public <A> Optional<A> findSingleAttributeOrEmpty() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public boolean isCountDistinct() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
@@ -51,6 +51,10 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
* Flushes the jdbc batch by default unless explicitly turned off on the transaction.
*/
private <T> void flushJdbcBatchOnQuery(OrmQueryRequest<T> request) {
if (request.query().isUsingFuture()) {
// future queries never invoke a flush
return;
}
SpiTransaction t = request.transaction();
if (t.isFlushOnQuery()) {
// before we perform a query, we need to flush any
@@ -9,8 +9,10 @@ import io.ebeaninternal.api.*;
import io.ebeaninternal.server.dto.DtoBeanDescriptor;
import io.ebeaninternal.server.dto.DtoMappingRequest;
import io.ebeaninternal.server.dto.DtoQueryPlan;
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;
import javax.annotation.Nullable;
import java.sql.Connection;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -86,6 +88,12 @@ public final class DefaultDtoQuery<T> extends AbstractQuery implements SpiDtoQue
return this;
}
@Override
public DtoQuery<T> usingConnection(Connection connection) {
this.transaction = new ExternalJdbcTransaction(connection);
return this;
}
@Override
public DtoQuery<T> usingMaster() {
this.useMaster = true;
@@ -58,6 +58,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
private Type type;
private String label;
private Mode mode = Mode.NORMAL;
private boolean usingFuture;
private Object tenantId;
/**
* Holds query in structured form.
@@ -971,6 +972,16 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
this.mode = mode;
}
@Override
public final void usingFuture() {
this.usingFuture = true;
}
@Override
public final boolean isUsingFuture() {
return usingFuture;
}
@Override
public final boolean isUsageProfiling() {
return usageProfiling;
@@ -1161,7 +1172,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
// add the rawSql statement - if any
if (orderByIsEmpty()) {
if (rawSql != null && rawSql.getSql() != null) {
order(rawSql.getSql().getOrderBy());
orderBy(rawSql.getSql().getOrderBy());
}
}
if (checkPagingOrderBy()) {
@@ -1517,7 +1528,6 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
}
@Override
@SuppressWarnings("unchecked")
public final <A> List<A> findSingleAttributeList() {
return server.findSingleAttributeList(this);
}
@@ -1533,6 +1543,11 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
return !list.isEmpty() ? list.get(0) : null;
}
@Override
public final <A> Optional<A> findSingleAttributeOrEmpty() {
return Optional.ofNullable(findSingleAttribute());
}
@Override
public final T findOne() {
return server.findOne(this);
@@ -7,7 +7,9 @@ import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;
import java.sql.Connection;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@@ -50,6 +52,12 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq
return this;
}
@Override
public SqlQuery usingConnection(Connection connection) {
this.transaction = new ExternalJdbcTransaction(connection);
return this;
}
@Override
public SqlQuery usingMaster() {
this.useMaster = true;
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<name>ebean ddl generation</name>
@@ -28,14 +28,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
@@ -65,7 +65,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,7 +14,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<name>ebean postgis types</name>
@@ -19,14 +19,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
@@ -62,7 +62,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<name>ebean querybean</name>
@@ -17,7 +17,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
@@ -63,14 +63,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
@@ -84,7 +84,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
@@ -32,7 +32,7 @@ public abstract class PBaseString<R, T> extends PBaseComparable<R, T> {
* @param value the equal to bind value
* @return the root query bean instance
*/
@Deprecated
@Deprecated(forRemoval = true)
public final R equalToType(T value) {
expr().eq(_name, value);
return _root;
@@ -46,7 +46,7 @@ public abstract class PBaseString<R, T> extends PBaseComparable<R, T> {
* @param value the equal to bind value
* @return the root query bean instance
*/
@Deprecated
@Deprecated(forRemoval = true)
public final R notEqualToType(T value) {
expr().ne(_name, value);
return _root;
@@ -745,7 +745,7 @@ public abstract class TQRootBean<T, R> {
* Add EXISTS sub-query predicate.
*/
public R exists(Query<?> subQuery) {
query.where().exists(subQuery);
peekExprList().exists(subQuery);
return root;
}
@@ -753,7 +753,7 @@ public abstract class TQRootBean<T, R> {
* Add NOT EXISTS sub-query predicate.
*/
public R notExists(Query<?> subQuery) {
query.where().notExists(subQuery);
peekExprList().notExists(subQuery);
return root;
}
@@ -764,7 +764,7 @@ public abstract class TQRootBean<T, R> {
* @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values.
*/
public final R exists(String sqlSubQuery, Object... bindValues) {
query().where().exists(sqlSubQuery, bindValues);
peekExprList().exists(sqlSubQuery, bindValues);
return root;
}
@@ -775,7 +775,7 @@ public abstract class TQRootBean<T, R> {
* @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values.
*/
public final R notExists(String sqlSubQuery, Object... bindValues) {
query().where().notExists(sqlSubQuery, bindValues);
peekExprList().notExists(sqlSubQuery, bindValues);
return root;
}
@@ -1275,7 +1275,7 @@ public abstract class TQRootBean<T, R> {
/**
* @deprecated migrate to {@link #orderBy()}.
*/
@Deprecated(since = "13.19")
@Deprecated(since = "13.19", forRemoval = true)
public R order() {
return root;
}
@@ -1295,7 +1295,7 @@ public abstract class TQRootBean<T, R> {
/**
* @deprecated migrate to {@link #orderBy(String)}
*/
@Deprecated(since = "13.19")
@Deprecated(since = "13.19", forRemoval = true)
public R order(String orderByClause) {
return orderBy(orderByClause);
}
@@ -1867,6 +1867,28 @@ public abstract class TQRootBean<T, R> {
return query.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
*/
@Nullable
public <A> Optional<A> findSingleAttributeOrEmpty() {
return query.findSingleAttributeOrEmpty();
}
/**
* Execute the query processing the beans one at a time.
* <p>
@@ -1,8 +1,9 @@
package org.example.domain;
import io.ebean.annotation.DbArray;
import jakarta.persistence.*;
import org.example.domain.finder.ContactFinder;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.ZonedDateTime;
@@ -17,6 +18,11 @@ import java.util.Map;
@Table(name = "be_contact")
public class Contact extends BaseModel {
/**
* Convenience Finder for 'active record' style.
*/
public static final ContactFinder find = new ContactFinder();
@DbArray
List<@Size(max=20) String> phoneNumbers = new ArrayList<>();
@@ -0,0 +1,18 @@
package org.example.domain.finder;
import io.ebean.Finder;
import org.example.domain.Contact;
import org.example.domain.query.QContact;
/**
*/
public class ContactFinder extends Finder<Long, Contact> {
public ContactFinder() {
super(Contact.class);
}
public QContact typed() {
return new QContact();
}
}
@@ -1,6 +1,8 @@
package org.querytest;
import org.example.domain.Contact;
import org.example.domain.Customer;
import org.example.domain.query.QCustomer;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
@@ -9,6 +11,8 @@ import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class QCustomerAndOrTest {
@Test
@@ -55,6 +59,30 @@ public class QCustomerAndOrTest {
}
@Test
public void testOrWithExists() {
QCustomer query = Customer.find.typed()
.alias("_cust")
.or()
.name.eq("Superman")
.exists(Contact.find.typed()
.alias("contact")
.firstName.eq("Superman")
.raw("contact.customer_id = _cust.id")
.query()
)
.endOr()
.select(QCustomer.alias().id);
query.findList();
assertThat(query.getGeneratedSql()).isEqualTo(
"select _cust.id from be_customer _cust where (" +
"_cust.name = ? or exists (select 1 from be_contact contact where " +
"contact.first_name = ? and contact.customer_id = _cust.id))"
);
}
private Date fiveDaysAgo() {
LocalDateTime fiveDaysAgo = LocalDate.now().atStartOfDay().minusDays(5);
return new Date(fiveDaysAgo.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
@@ -63,6 +63,25 @@ public class QCustomerTest {
}
@Test
public void copy() {
var origin = new QCustomer()
.setDistinct(true)
.status.equalTo(Customer.Status.BAD);
var copy1 = origin.copy().name.isNotNull();
var q1 = copy1.query();
copy1.findList();
assertThat(q1.getGeneratedSql()).contains("from be_customer t0 where t0.status = ? and t0.name is not null");
var copy2 = origin.copy().version.ge(1L);
var q2 = copy2.query();
copy2.findList();
assertThat(q2.getGeneratedSql()).contains("from be_customer t0 where t0.status = ? and t0.version >= ?");
}
@Test
public void findSingleAttribute() {
@@ -1050,6 +1069,32 @@ public class QCustomerTest {
assertThat(maxDate).isNotNull();
}
@Test
public void findSingleAttributeOrEmpty() {
Customer cust = new Customer();
cust.setName("MaybeIExist yeah");
cust.setStatus(Customer.Status.GOOD);
cust.setRegistered(new Date());
cust.save();
Optional<String> customerName = new QCustomer()
.select(name)
.status.eq(Customer.Status.GOOD)
.name.startsWith("MaybeIExist")
.findSingleAttributeOrEmpty();
assertThat(customerName).isPresent();
Optional<String> customerName2 = new QCustomer()
.select(name)
.status.eq(Customer.Status.GOOD)
.name.eq("NahIDoNotExist")
.findSingleAttributeOrEmpty();
assertThat(customerName2).isEmpty();
}
@Test
public void testFetchByScalarValue() {
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<artifactId>ebean-redis</artifactId>
@@ -22,28 +22,28 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
@@ -57,7 +57,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<artifactId>ebean-spring-txn</artifactId>
@@ -28,7 +28,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
@@ -77,7 +77,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<name>ebean test</name>
@@ -33,20 +33,20 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
@@ -149,14 +149,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
@@ -3,6 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanList;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Product;
import java.util.ArrayList;
import java.util.Comparator;
@@ -13,22 +14,22 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanListTest {
private final Object object1 = new Object();
private final Object object2 = new Object();
private final Object object3 = new Object();
private final Product object1 = new Product(1);
private final Product object2 = new Product(2);
private final Product object3 = new Product(3);
private List<Object> all() {
List<Object> all = new ArrayList<>();
all.add(object1);
all.add(object2);
all.add(object3);
all.add(new Product(1));
all.add(new Product(2));
all.add(new Product(3));
return all;
}
private List<Object> some() {
List<Object> some = new ArrayList<>();
some.add(object2);
some.add(object3);
some.add(new Product(2));
some.add(new Product(3));
return some;
}
@@ -163,14 +164,15 @@ public class BeanListTest {
@Test
public void testRemove_given_beansInAdditions() {
List<Object> all = all();
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.addAll(all());
list.addAll(all);
assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3);
// act
list.remove(object2);
list.remove(object3);
list.remove(all.get(1));
list.remove(all.get(2));
assertThat(list.modifyAdditions()).containsOnly(object1);
assertThat(list.modifyRemovals()).isEmpty();
@@ -179,13 +181,14 @@ public class BeanListTest {
@Test
public void testRemoveAll_given_beansInAdditions() {
List<Object> all = all();
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.addAll(all());
list.addAll(all);
assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3);
// act
list.removeAll(some());
list.removeAll(List.of(all.get(1), all.get(2)));
assertThat(list.modifyAdditions()).containsOnly(object1);
assertThat(list.modifyRemovals()).isEmpty();
@@ -3,7 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanMap;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.EBasic;
import org.tests.model.basic.Product;
import java.util.*;
@@ -14,24 +14,24 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class BeanMapTest {
private final EBasic object1 = new EBasic("o1");
private final EBasic object2 = new EBasic("o2");
private final EBasic object3 = new EBasic("o3");
private final EBasic object4 = new EBasic("o4");
private final EBasic object5 = new EBasic("o5");
private final Product object1 = new Product(1);
private final Product object2 = new Product(2);
private final Product object3 = new Product(3);
private final Product object4 = new Product(4);
private final Product object5 = new Product(5);
private Map<String, Object> all() {
Map<String, Object> all = new LinkedHashMap<>();
all.put("1", object1);
all.put("2", object2);
all.put("3", object3);
all.put("1", new Product(1));
all.put("2", new Product(2));
all.put("3", new Product(3));
return all;
}
private Map<String, Object> some() {
Map<String, Object> all = new LinkedHashMap<>();
all.put("2", object2);
all.put("3", object3);
all.put("2", new Product(2));
all.put("3", new Product(3));
return all;
}
@@ -73,8 +73,8 @@ public class BeanMapTest {
@Test
public void testAdd_given_someAlreadyIn() {
BeanMap<String, Object> map = new BeanMap<>(some());
Map<String, Object> some = new LinkedHashMap<>(Map.of("2", object2, "3", object3));
BeanMap<String, Object> map = new BeanMap<>(some);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
@@ -90,11 +90,13 @@ public class BeanMapTest {
@Test
public void testAddSome_given_someAlreadyIn() {
BeanMap<String, Object> map = new BeanMap<>(some());
Map<String, Object> some = new LinkedHashMap<>(Map.of("2", object2, "3", object3));
BeanMap<String, Object> map = new BeanMap<>(some);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
map.putAll(all());
Map<String, Object> all = Map.of("1", object1, "2", object2, "3", object3);
map.putAll(all);
assertThat(map.modifyAdditions()).containsOnly(object1);
assertThat(map.modifyRemovals()).isEmpty();
@@ -179,7 +181,7 @@ public class BeanMapTest {
@Test
public void testClear_given_someBeansInAdditions() {
BeanMap<String, EBasic> map = newModifyListeningMap();
BeanMap<String, Product> map = newModifyListeningMap();
map.put("2", object2);
map.put("3", object3);
@@ -193,7 +195,7 @@ public class BeanMapTest {
@Test
public void keySet_add_whenModifyListening() {
BeanMap<String, EBasic> map = newModifyListeningMap();
BeanMap<String, Product> map = newModifyListeningMap();
assertThrows(UnsupportedOperationException.class, () -> map.keySet().add("3"));
}
@@ -205,7 +207,7 @@ public class BeanMapTest {
@Test
public void keySet_addAll_whenModifyListening() {
BeanMap<String, EBasic> map = newModifyListeningMap();
BeanMap<String, Product> map = newModifyListeningMap();
assertThrows(UnsupportedOperationException.class, () -> map.keySet().addAll(asList("3", "4")));
}
@@ -317,13 +319,13 @@ public class BeanMapTest {
@Test
public void values_add() {
BeanMap<String, EBasic> map = new BeanMap<>();
BeanMap<String, Product> map = new BeanMap<>();
assertThrows(UnsupportedOperationException.class, () -> map.values().add(object3));
}
@Test
public void values_addAll() {
BeanMap<String, EBasic> map = new BeanMap<>();
BeanMap<String, Product> map = new BeanMap<>();
assertThrows(UnsupportedOperationException.class, () -> map.values().addAll(asList(object3, object5)));
}
@@ -337,8 +339,8 @@ public class BeanMapTest {
@Test
public void entrySet_clear() {
final BeanMap<String, EBasic> map = newModifyListeningMap();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
entries.clear();
assertThat(entries).isEmpty();
@@ -348,8 +350,8 @@ public class BeanMapTest {
@Test
public void entrySet_remove() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
assertThat(map).hasSize(5);
@@ -365,8 +367,8 @@ public class BeanMapTest {
@Test
public void entrySet_remove_whenNotEqualValue() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
assertThat(map).hasSize(5);
@@ -379,11 +381,11 @@ public class BeanMapTest {
@Test
public void entrySet_iterator_remove() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final Iterator<Map.Entry<String, EBasic>> iterator = entries.iterator();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
final Iterator<Map.Entry<String, Product>> iterator = entries.iterator();
while (iterator.hasNext()) {
final Map.Entry<String, EBasic> entry = iterator.next();
final Map.Entry<String, Product> entry = iterator.next();
if (entry.getKey().equals("2") || entry.getKey().equals("5")) {
iterator.remove();
}
@@ -395,8 +397,8 @@ public class BeanMapTest {
@Test
public void entrySet_removeAll() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
entries.removeAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(3);
@@ -406,8 +408,8 @@ public class BeanMapTest {
@Test
public void entrySet_retainAll() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
entries.retainAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(2);
@@ -415,15 +417,15 @@ public class BeanMapTest {
assertThat(map.modifyRemovals()).containsOnly(object2, object3, object5);
}
private BeanMap<String, EBasic> newModifyListeningMap() {
BeanMap<String, EBasic> map = new BeanMap<>();
private BeanMap<String, Product> newModifyListeningMap() {
BeanMap<String, Product> map = new BeanMap<>();
map.put("1", object1);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return map;
}
private BeanMap<String, EBasic> newModifyListeningMap5() {
BeanMap<String, EBasic> map = new BeanMap<>();
private BeanMap<String, Product> newModifyListeningMap5() {
BeanMap<String, Product> map = new BeanMap<>();
map.put("1", object1);
map.put("2", object2);
map.put("3", object3);
@@ -3,6 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanSet;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Product;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -12,22 +13,22 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanSetTest {
Object object1 = new Object();
Object object2 = new Object();
Object object3 = new Object();
Product object1 = new Product(1);
Product object2 = new Product(2);
Product object3 = new Product(3);
private Set<Object> all() {
Set<Object> all = new LinkedHashSet<>();
all.add(object1);
all.add(object2);
all.add(object3);
all.add(new Product(1));
all.add(new Product(2));
all.add(new Product(3));
return all;
}
private Set<Object> some() {
Set<Object> some = new LinkedHashSet<>();
some.add(object2);
some.add(object3);
some.add(new Product(2));
some.add(new Product(3));
return some;
}
@@ -99,7 +100,9 @@ public class BeanSetTest {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
set.addAll(all());
Set<Object> all = Set.of(object1, object2, object3);
set.addAll(all);
assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3);
// act
@@ -115,11 +118,13 @@ public class BeanSetTest {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
set.addAll(all());
Set<Object> all = Set.of(object1, object2, object3);
set.addAll(all);
assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3);
// act
set.removeAll(some());
set.removeAll(Set.of(object2, object3));
assertThat(set.modifyAdditions()).containsOnly(object1);
assertThat(set.modifyRemovals()).isEmpty();
@@ -453,21 +453,41 @@ public class TDSpiServer implements SpiServer {
}
@Override
public void insert(Object bean, InsertOptions insertOptions) {
}
@Override
public void insert(Object bean, Transaction transaction) {
}
@Override
public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) {
}
@Override
public void insertAll(Collection<?> beans) {
}
@Override
public void insertAll(Collection<?> beans, InsertOptions options) {
}
@Override
public void insertAll(Collection<?> beans, Transaction transaction) {
}
@Override
public void insertAll(Collection<?> beans, InsertOptions options, Transaction transaction) {
}
@Override
public int execute(SqlUpdate updSql, Transaction transaction) {
return 0;
@@ -27,14 +27,14 @@ public class TestQueryForUpdate extends BaseTestCase {
try (final Transaction transaction = DB.beginTransaction()) {
query = DB.find(Customer.class)
.forUpdate()
.order().desc("id");
.orderBy().desc("id");
query.findList();
}
if (isSqlServer()) {
assertThat(sqlOf(query)).contains("with (updlock)");
} else if (!isDb2()){
} else {
assertThat(sqlOf(query)).contains("for update");
}
}
@@ -48,21 +48,21 @@ public class TestQueryForUpdate extends BaseTestCase {
query = DB.find(Customer.class)
.forUpdate()
.setMaxRows(3)
.order().desc("id");
.orderBy().desc("id");
query.findList();
}
if (isSqlServer()) {
assertThat(sqlOf(query)).contains("with (updlock)");
} else if (!isOracle() && !isDb2()) {
} else if (!isOracle()) {
// Oracle does not support FOR UPDATE with FETCH
assertThat(sqlOf(query)).contains("for update");
}
}
@Test
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2})
public void testForUpdate_when_alreadyInPCAsReference() {
ResetBasicData.reset();
Order o0 = DB.find(Order.class).orderBy("id").setMaxRows(1).findOne();
@@ -96,7 +96,7 @@ public class TestQueryForUpdate extends BaseTestCase {
}
@Test
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2})
public void testForUpdate_when_alreadyInPCAsReference_usingLock() {
ResetBasicData.reset();
Order o0 = DB.find(Order.class).orderBy("id").setMaxRows(1).findOne();
@@ -131,7 +131,7 @@ public class TestQueryForUpdate extends BaseTestCase {
}
@Test
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2})
public void testForUpdate_when_alreadyInPC() {
EBasic basic = new EBasic("initialValue");
@@ -193,7 +193,7 @@ public class TestQueryForUpdate extends BaseTestCase {
Query<Customer> query = DB.find(Customer.class)
.forUpdateNoWait()
.order().desc("id");
.orderBy().desc("id");
query.findList();
if (isOracle()) {
@@ -219,7 +219,7 @@ public class TestQueryForUpdate extends BaseTestCase {
Query<Customer> query = DB.find(Customer.class)
.forUpdateNoWait()
.setMaxRows(1)
.order().desc("id");
.orderBy().desc("id");
List<Customer> list = query.findList();
Customer first = list.get(0);
@@ -73,9 +73,26 @@ public class TestQueryUsingConnection extends BaseTestCase {
assertThat(count).isEqualTo(otherCount + 1);
assertThat(otherCount).isEqualTo(masterCount);
}
}
@IgnorePlatform({Platform.SQLSERVER, Platform.COCKROACH})
@Test
public void dtoQueryUsingConnection() {
ResetBasicData.reset();
try (Transaction transaction = DB.createTransaction()) {
final CountryDto dto = DB.findDto(CountryDto.class, "select code, name from o_country where code=?")
.usingConnection(transaction.connection())
.setParameter("NZ")
.findOne();
assertThat(dto).isNotNull();
assertEquals("NZ", dto.code);
transaction.rollback();
}
}
public static class CountryDto {
final String code;
final String name;
@@ -0,0 +1,297 @@
package org.tests.insert;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.InsertOptions;
import io.ebean.Transaction;
import io.ebean.annotation.Platform;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.BaseTestCase;
import io.ebean.xtest.ForPlatform;
import org.junit.jupiter.api.Test;
import org.tests.update.EPersonOnline;
import java.util.List;
import static io.ebean.InsertOptions.ON_CONFLICT_NOTHING;
import static io.ebean.InsertOptions.ON_CONFLICT_UPDATE;
import static org.assertj.core.api.Assertions.assertThat;
class TestInsertOnConflict extends BaseTestCase {
InsertOptions onConflictDoUpdateAndGetGeneratedKeys = InsertOptions.builder()
.onConflictUpdate()
.getGeneratedKeys(true)
.build();
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertOnConflictUpdateExplicitTransaction() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a@b.com");
try (Transaction txn = DB.createTransaction()) {
db.insert(bean, ON_CONFLICT_UPDATE, txn);
txn.commit();
}
assertThat(bean.getId()).isNotNull();
var bean2 = newBean("a@b.com");
bean2.setOnlineStatus(false);
try (Transaction txn = DB.createTransaction()) {
db.insert(bean2, ON_CONFLICT_UPDATE, txn);
txn.commit();
}
assertThat(bean2.getId()).isEqualTo(bean.getId());
var sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
List<EPersonOnline> list = db.find(EPersonOnline.class).findList();
assertThat(list).hasSize(1);
assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated());
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertOnConflictUpdate_when_noIdValue() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a@b.com");
db.insert(bean, onConflictDoUpdateAndGetGeneratedKeys);
assertThat(bean.getId()).isNotNull();
var bean2 = newBean("a@b.com");
bean2.setOnlineStatus(false);
db.insert(bean2, onConflictDoUpdateAndGetGeneratedKeys);
assertThat(bean2.getId()).isEqualTo(bean.getId());
var sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
List<EPersonOnline> list = db.find(EPersonOnline.class).findList();
assertThat(list).hasSize(1);
assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated());
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertOnConflictUpdate_when_idValueSupplied() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a@b.com");
bean.setId(40_042L);
db.insert(bean, ON_CONFLICT_UPDATE);
assertThat(bean.getId()).isNotNull();
var bean2 = newBean("a@b.com");
bean2.setId(40_043L); // not expected but can be different
bean2.setOnlineStatus(false);
db.insert(bean2, onConflictDoUpdateAndGetGeneratedKeys);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("insert into e_person_online (id, email, online_status, when_updated) values (?,?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains("insert into e_person_online (id, email, online_status, when_updated) values (?,?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
List<EPersonOnline> list = db.find(EPersonOnline.class).findList();
assertThat(list).hasSize(1);
assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated());
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertAll_onConflictUpdate_when_noIdValue() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var bean3 = newBean("a3@b.com");
db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_UPDATE);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(5);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- bind");
assertThat(sql.get(4)).contains(" -- executeBatch()");
var bean4 = newBean("a1@b.com");
var bean5 = newBean("a5@b.com");
db.insertAll(List.of(bean4, bean5), ON_CONFLICT_UPDATE);
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(4);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertAll_onConflictUpdate_explicitTransaction() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
try (Transaction txn = DB.createTransaction()) {
txn.setBatchSize(3);
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var bean3 = newBean("a3@b.com");
var bean4 = newBean("a4@b.com");
db.insertAll(List.of(bean, bean2, bean3, bean4), ON_CONFLICT_UPDATE, txn);
txn.commit();
}
var sql = LoggedSql.stop();
assertThat(sql).hasSize(8);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- bind");
assertThat(sql.get(4)).contains(" -- executeBatch()");
assertThat(sql.get(5)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(6)).contains(" -- bind");
assertThat(sql.get(7)).contains(" -- executeBatch()");
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(4);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void explicitConstraint() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var bean3 = newBean("a3@b.com");
var ON_CONFLICT_ = InsertOptions.builder()
.onConflictUpdate()
.constraint("uq_e_person_online_email")
.build();
db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(5);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict on constraint uq_e_person_online_email do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- bind");
assertThat(sql.get(4)).contains(" -- executeBatch()");
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(3);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void explicitUniqueColumns() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var bean3 = newBean("a3@b.com");
var ON_CONFLICT_ = InsertOptions.builder()
.onConflictUpdate()
.uniqueColumns(" email ")
.build();
db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(5);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict ( email ) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- bind");
assertThat(sql.get(4)).contains(" -- executeBatch()");
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(3);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void explicitUpdateSet() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var ON_CONFLICT_ = InsertOptions.builder()
.onConflictUpdate()
.updateSet("when_updated=excluded.when_updated, online_status=true")
.build();
db.insertAll(List.of(bean, bean2), ON_CONFLICT_);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(4);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set when_updated=excluded.when_updated, online_status=true");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- executeBatch()");
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(2);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertOnConflictNothing_when_noIdValue() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a@b.com");
db.insert(bean, ON_CONFLICT_NOTHING);
assertThat(bean.getId()).isNotNull();
var bean2 = newBean("a@b.com");
bean2.setOnlineStatus(false);
db.insert(bean2, ON_CONFLICT_NOTHING);
assertThat(bean2.getId()).isNull();
var sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do nothing");
assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do nothing");
List<EPersonOnline> list = db.find(EPersonOnline.class).findList();
assertThat(list).hasSize(1);
assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean.getWhenUpdated());
}
private static EPersonOnline newBean(String email) {
EPersonOnline bean = new EPersonOnline();
bean.setEmail(email);
bean.setOnlineStatus(true);
return bean;
}
}
@@ -39,6 +39,13 @@ public class Product implements Serializable {
@Version
Timestamp updtime;
public Product(Integer id) {
this.id = id;
}
public Product() {
}
/**
* Return id.
*/
@@ -28,6 +28,11 @@ public class OmBeanListChild extends Model {
public Long getId() {
return id;
}
public OmBeanListChild setId(Long id) {
this.id = id;
return this;
}
}
@@ -2,10 +2,14 @@ package org.tests.model.orphanremoval;
import io.ebean.Model;
import io.ebean.annotation.WhenCreated;
import io.ebean.annotation.WhenModified;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Version;
import java.time.Instant;
import java.util.List;
import static jakarta.persistence.CascadeType.ALL;
@@ -19,6 +23,13 @@ public class OmBeanListParent extends Model {
@Version
private long version;
private String name;
@WhenCreated
private Instant whenCreated;
@WhenModified
private Instant whenModified;
@OneToMany(cascade = ALL, mappedBy = "parent", orphanRemoval = true)
private List<OmBeanListChild> children;
@@ -35,6 +46,42 @@ public class OmBeanListParent extends Model {
this.children.clear();
this.children.addAll(children);
}
public void setChildren2(List<OmBeanListChild> children) {
this.children = children;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
public Instant getWhenModified() {
return whenModified;
}
public void setWhenModified(Instant whenModified) {
this.whenModified = whenModified;
}
public Instant getWhenCreated() {
return whenCreated;
}
public void setWhenCreated(Instant whenCreated) {
this.whenCreated = whenCreated;
}
}
@@ -1,17 +1,20 @@
package org.tests.model.orphanremoval;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
import org.junit.jupiter.api.Test;
import java.util.List;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class TestOrphanRemovalOverwrite {
class TestOrphanRemovalOverwrite {
@Test
public void testOverwritingMapping() {
void testOverwritingMapping() {
OmBeanListParent parent = new OmBeanListParent();
parent.save();
@@ -29,8 +32,45 @@ public class TestOrphanRemovalOverwrite {
// Refreshing here generates new objects for the associated children that are referred to by the parent.
parent.refresh();
assertNotNull(childList.get(0).getId());
assertEquals(childList.get(0).getId(), parent.getChildren().get(0).getId());
OmBeanListChild child = childList.get(0);
assertNotNull(child.getId());
OmBeanListChild refreshedChild = parent.getChildren().get(0);
assertEquals(child.getId(), refreshedChild.getId());
assertEquals(child, refreshedChild);
assertEquals(childList, parent.getChildren());
}
@Test
void clearAddAll() {
OmBeanListChild c1 = new OmBeanListChild("c1");
OmBeanListChild c2 = new OmBeanListChild("c2");
OmBeanListParent parent = new OmBeanListParent();
parent.getChildren().add(c1);
parent.getChildren().add(c2);
parent.save();
OmBeanListChild c3 = new OmBeanListChild("c3");
c3.setId(c1.getId());
OmBeanListParent p1 = DB.find(OmBeanListParent.class, parent.getId());
List<OmBeanListChild> children = p1.getChildren();
children.clear();
children.add(c3);
LoggedSql.start();
DB.save(p1);
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(7);
assertThat(sql.get(0)).contains("delete from om_bean_list_child where id=?");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- executeBatch()");
assertThat(sql.get(4)).contains(" insert into om_bean_list_child");
assertThat(sql.get(5)).contains(" -- bind");
assertThat(sql.get(6)).contains(" -- executeBatch()");
}
}
@@ -0,0 +1,114 @@
package org.tests.o2m;
import io.ebean.DB;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Test;
import org.tests.model.orphanremoval.OmBeanListChild;
import org.tests.model.orphanremoval.OmBeanListParent;
import java.time.Instant;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import static org.assertj.core.api.Assertions.assertThat;
class OneToManyListMarkAsDirtyTest extends BaseTestCase {
@Test
void usingNewListWithNonDirtyParent_expect_orphanDeleted_works() {
// setup
var parent = new OmBeanListParent();
var a_b = new OmBeanListChild("b");
parent.getChildren().add(a_b);
DB.save(parent);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
// act
var secondParent = DB.find(OmBeanListParent.class, parent.getId());
secondParent.setChildren2(new ArrayList<>()); // <!-- HERE - setting a new ArrayList rather than using clear()
var a_c = new OmBeanListChild("b");
secondParent.getChildren().add(a_c);
// working here as secondParent itself is not dirty
DB.save(secondParent);
var refreshedParent = DB.find(OmBeanListParent.class, parent.getId());
assertThat(refreshedParent.getChildren().size()).isEqualTo(1);
assertThat(refreshedParent.getChildren().get(0).getId()).isEqualTo(a_c.getId());
// Q: is this expected? A: Rob Bygrave - Yes it is expected
assertThat(refreshedParent.getVersion()).isEqualTo(parent.getVersion());
assertThat(refreshedParent.getWhenModified()).isEqualTo(parent.getWhenModified());
}
@Test
void usingNewListWithParentDirty_expect_orphanDeleted_fails2() {
// setup
var parent = new OmBeanListParent();
var a_b = new OmBeanListChild("b");
parent.getChildren().add(a_b);
DB.save(parent);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
// act
var secondParent = DB.find(OmBeanListParent.class, parent.getId());
secondParent.setChildren2(new ArrayList<>()); // <!-- HERE: using new ArrayList with orphanRemoval
var a_c = new OmBeanListChild("c");
secondParent.getChildren().add(a_c);
// force version increase
DB.markAsDirty(secondParent);
DB.save(secondParent);
var refreshedParent = DB.find(OmBeanListParent.class, parent.getId());
assertThat(refreshedParent.getChildren().size()).isEqualTo(1);
assertThat(refreshedParent.getChildren().get(0).getId()).isEqualTo(a_c.getId());
assertThat(refreshedParent.getVersion()).isGreaterThan(parent.getVersion());
assertThat(refreshedParent.getWhenModified()).isAfter(parent.getWhenModified());
}
@Test
void usingClearWithParentDirty_expect_orphanDeleted_works() {
// setup
var parent = new OmBeanListParent();
var a_b = new OmBeanListChild("b");
parent.getChildren().add(a_b);
DB.save(parent);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
// act
var secondParent = DB.find(OmBeanListParent.class, parent.getId());
secondParent.getChildren().clear(); // <!-- HERE: Using clear() with orphanRemoval
var a_c = new OmBeanListChild("c");
secondParent.getChildren().add(a_c);
// force version increase
DB.markAsDirty(secondParent);
DB.save(secondParent);
var refreshedParent = DB.find(OmBeanListParent.class, parent.getId());
assertThat(refreshedParent.getChildren().size()).isEqualTo(1);
assertThat(refreshedParent.getChildren().get(0).getId()).isEqualTo(a_c.getId());
assertThat(refreshedParent.getVersion()).isGreaterThan(parent.getVersion());
assertThat(refreshedParent.getWhenModified()).isAfter(parent.getWhenModified());
}
@Test
void usingNewListWithParentDirty_expect_orphanDeleted_fails() {
// setup
var parent = new OmBeanListParent();
var a_b = new OmBeanListChild("b");
parent.getChildren().add(a_b);
DB.save(parent);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
// act
var secondParent = DB.find(OmBeanListParent.class, parent.getId());
secondParent.setChildren2(new ArrayList<>()); // <!-- HERE: new ArrayList with orphanRemoval
var a_c = new OmBeanListChild("c");
secondParent.getChildren().add(a_c);
secondParent.setWhenCreated(Instant.now());
DB.save(secondParent);
var refreshedParent = DB.find(OmBeanListParent.class, parent.getId());
assertThat(refreshedParent.getChildren().size()).isEqualTo(1);
assertThat(refreshedParent.getChildren().get(0).getId()).isEqualTo(a_c.getId());
assertThat(refreshedParent.getVersion()).isGreaterThan(parent.getVersion());
assertThat(refreshedParent.getWhenModified()).isAfter(parent.getWhenModified());
}
}
@@ -6,11 +6,87 @@ import org.junit.jupiter.api.Test;
import org.tests.model.basic.EBasic;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.assertj.core.api.Assertions.assertThat;
class TestFindFutureRowCount extends BaseTestCase {
@Test
void futureCount_doesNotTriggerFlush() throws ExecutionException, InterruptedException {
try (Transaction transaction = DB.beginTransaction()) {
transaction.setBatchMode(true);
transaction.setBatchSize(100);
EBasic basic = new EBasic("count_batched");
DB.save(basic);
FutureRowCount<EBasic> futureCount = DB.find(EBasic.class)
.where().eq("name", "count_batched")
.findFutureCount();
assertThat(futureCount.get()).isEqualTo(0);
transaction.flush();
FutureRowCount<EBasic> futureCountAfter = DB.find(EBasic.class)
.where().eq("name", "count_batched")
.findFutureCount();
assertThat(futureCountAfter.get()).isEqualTo(1);
}
}
@Test
void futureList_doesNotTriggerFlush() throws ExecutionException, InterruptedException {
try (Transaction transaction = DB.beginTransaction()) {
transaction.setBatchMode(true);
transaction.setBatchSize(100);
EBasic basic = new EBasic("list_batched");
DB.save(basic);
FutureList<EBasic> futureList = DB.find(EBasic.class)
.where().eq("name", "list_batched")
.findFutureList();
assertThat(futureList.get()).isEmpty();
transaction.flush();
FutureList<EBasic> futureListAfter = DB.find(EBasic.class)
.where().eq("name", "list_batched")
.findFutureList();
assertThat(futureListAfter.get()).hasSize(1);
}
}
@Test
void futureIds_doesNotTriggerFlush() throws ExecutionException, InterruptedException {
try (Transaction transaction = DB.beginTransaction()) {
transaction.setBatchMode(true);
transaction.setBatchSize(100);
EBasic basic = new EBasic("ids_batched");
DB.save(basic);
FutureIds<EBasic> futureIds = DB.find(EBasic.class)
.where().eq("name", "ids_batched")
.findFutureIds();
assertThat(futureIds.get()).isEmpty();
transaction.flush();
FutureIds<EBasic> futureIdsAfter = DB.find(EBasic.class)
.where().eq("name", "ids_batched")
.findFutureIds();
assertThat(futureIdsAfter.get()).hasSize(1);
}
}
@Test
void count_when_inTransaction() throws Exception {
try (Transaction transaction = DB.beginTransaction()) {
@@ -355,6 +355,24 @@ public class TestQueryFilterMany extends BaseTestCase {
assertSql(sql.get(1)).contains(" and lower(t0.title) like");
}
@Test
public void testFetchAndFilterMany() {
ResetBasicData.reset();
LoggedSql.start();
DB.find(Customer.class)
.fetch("contacts")
.filterMany("contacts.notes").istartsWith("title", "foo")
.findList();
List<String> sql = LoggedSql.stop();
assertThat(sql.size()).isEqualTo(2);
assertSql(sql.get(0)).contains(" from o_customer t0 left join contact t1 on t1.customer_id = t0.id");
platformAssertIn(sql.get(1), " from contact_note t0 where (t0.contact_id)");
assertSql(sql.get(1)).contains(" and lower(t0.title) like");
}
@Test
public void testFilterManyUsingExpression() {
@@ -11,6 +11,7 @@ import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
import jakarta.persistence.PersistenceException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -269,23 +270,25 @@ public class TestQueryFindPagedList extends BaseTestCase {
@Test
void test_forUpdate() {
ResetBasicData.reset();
if (!isDb2()) {
ResetBasicData.reset();
try (Transaction txn = DB.beginTransaction()) {
PagedList<Order> pagedList = DB.find(Order.class).forUpdate().setMaxRows(2).findPagedList();
try (Transaction txn = DB.beginTransaction()) {
PagedList<Order> pagedList = DB.find(Order.class).forUpdate().setMaxRows(2).findPagedList();
LoggedSql.start();
int totalCount = pagedList.getTotalCount();
assertThat(totalCount).isGreaterThan(2);
LoggedSql.start();
int totalCount = pagedList.getTotalCount();
assertThat(totalCount).isGreaterThan(2);
List<Order> list = pagedList.getList();
assertThat(list).hasSize(2);
List<Order> list = pagedList.getList();
assertThat(list).hasSize(2);
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("select count(*) from o_order t0;");
if (isH2() || isPostgresCompatible()) {
assertThat(sql.get(1)).contains(" limit 2 for update;");
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("select count(*) from o_order t0;");
if (isH2() || isPostgresCompatible()) {
assertThat(sql.get(1)).contains(" limit 2 for update;");
}
}
}
}
@@ -20,6 +20,7 @@ import org.tests.o2m.OmBasicParent;
import java.sql.Date;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
@@ -139,6 +140,32 @@ class TestQuerySingleAttribute extends BaseTestCase {
assertThat(name).isNotNull();
}
@Test
void findSingleAttributeOrEmpty() {
ResetBasicData.reset();
Query<Customer> query = DB.find(Customer.class)
.select("name")
.where().eq("name", "Rob").query();
Optional<String> name = query.findSingleAttributeOrEmpty();
assertThat(sqlOf(query)).contains("select t0.name from o_customer t0");
assertThat(name).isPresent().contains("Rob");
}
@Test
void findSingleAttributeOrEmpty_when_empty() {
ResetBasicData.reset();
Query<Customer> query = DB.find(Customer.class)
.select("name")
.where().eq("name", "I_Do_Not_ExistO!").query();
Optional<String> name = query.findSingleAttributeOrEmpty();
assertThat(name).isEmpty();
}
@Test
void findSingleAttributeList_with_join_column() {
ResetBasicData.reset();
@@ -265,6 +265,33 @@ class SqlQueryTests extends BaseTestCase {
assertThat(custDtos).isNotEmpty();
}
@Test
void queryUsingConnection() throws SQLException {
ResetBasicData.reset();
boolean h2 = isH2();
String sql = h2 ? "select id, name||session_id(), status from o_customer where name is not null"
: "select id, name, status from o_customer where name is not null";
try (Transaction txn = DB.createTransaction()) {
String h2SessionId = h2 ? h2SessionId(txn) : null;
AtomicInteger counter = new AtomicInteger();
DB.sqlQuery(sql)
.usingConnection(txn.connection())
.mapTo(CUST_MAPPER)
.findEach(custDto -> {
counter.incrementAndGet();
assertThat(custDto.name).isNotNull();
if (h2) {
assertThat(custDto.name).endsWith(h2SessionId);
}
});
assertThat(counter.get()).isGreaterThan(0);
}
}
@Test
void queryUsingTransaction() throws SQLException {
ResetBasicData.reset();
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</parent>
<name>kotlin querybean generator</name>
@@ -21,7 +21,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
@@ -35,7 +35,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
@@ -56,14 +56,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<scope>test</scope>
</dependency>
@@ -94,6 +94,13 @@ class KotlinLangAdapter implements LangAdapter {
writer.append(" * Private constructor for FetchGroup building.").eol();
writer.append(" */").eol();
writer.append(" private constructor(fetchGroupQuery: Query<%s>) : super(fetchGroupQuery)", shortName).eol();
writer.eol();
writer.append(" /** Return a copy of the query. */").eol();
writer.append(" fun copy() : Q%s {", shortName).eol();
writer.append(" return Q%s(query().copy())", shortName).eol();
writer.append(" }").eol();
writer.eol();
}
@Override
+14 -14
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -15,67 +15,67 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hsqldb</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlanywhere</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -15,7 +15,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -15,7 +15,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
@@ -1,6 +1,7 @@
package io.ebean.platform.db2;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.*;
@@ -53,4 +54,9 @@ public abstract class BaseDB2Platform extends DatabasePlatform {
return new DB2SequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
}
@Override
protected String withForUpdate(String sql, Query.LockWait lockWait, Query.LockType lockType) {
// NOWAIT and SKIP LOCKED not supported with Db2
return sql + " for update";
}
}
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -15,7 +15,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
<!-- Provided scope so that the H2HistoryTrigger can live in Ebean core
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -15,7 +15,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -15,7 +15,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.25.1-jakarta</version>
<version>13.26.1-jakarta</version>
</dependency>
</dependencies>

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