Compare commits

..
42 Commits
Author SHA1 Message Date
Rob Bygrave 100d53fded #3461 - Add also() for query beans that takes QueryBuilder 2024-08-27 23:10:38 +12:00
Rob BygraveandGitHub f8964707d9 #3463 - Ignore queryBean.select(null) calls (#3464) 2024-08-27 21:01:51 +12:00
Rob BygraveandGitHub 86c7de2de8 ebean-test: Modify postgis platform to support both net.postgis and og.postgis (#3449)
* ebean-test: Modify postgis platform to support both net.postgis and org.postgis

Use Class.forName() to test existence of net.postgis.jdbc.DriverWrapperLW and if available use that driver rather than the older org.postgis.DriverWrapperLW one.

Also support explicitly specifying the driver to use via test property - ebean.test.postgis.driver

* Update test for net postgis to use a separate container

* Remove NetPostgisSetup as PostgisSetup handles both cases now
2024-08-27 20:44:03 +12:00
Michael BenzandGitHub 53b4d94933 Update README.md fixing broken Foconis logo (#3457)
Updated the Foconis AG logo with the new Foconis Analytics URL which was broken because of a deep link to a Yoomla logo that is no longer available.
2024-08-27 20:40:36 +12:00
Rob Bygrave b0ec23e53c Update tests to ensure ResetBasicData.reset() was called early 2024-08-04 18:59:46 +12:00
Rob Bygrave 20caae532c Adjust ResetBasicData with more output 2024-08-04 18:15:22 +12:00
Rob Bygrave 4369481c1a Adjust ResetBasicData with more output 2024-08-04 18:11:36 +12:00
Rob Bygrave c94426b6b4 Update tests config for ebean-net-postgis-types 2024-07-22 22:01:42 +12:00
Rob Bygrave b2ff32e88c Update tests config for ebean-net-postgis-types 2024-07-22 21:47:48 +12:00
Rob Bygrave 3bd8dfa7bf Update tests config for ebean-net-postgis-types 2024-07-22 20:12:27 +12:00
Rob Bygrave 0e7abdd137 Version 14.5.0 2024-07-22 08:33:34 +12:00
Ryszard TrojnackiandGitHub 9135f2cd3e New Postgis types / Java modules (#3448)
* Added modules `ebean-net-postgis-types`, `ebean-net-postgis` for new `net.postgis` geometry types without support for GeoLatte.

* Added missing `uses` entry in `module-info.java` for `io.ebean.api`.
2024-07-22 07:31:30 +12:00
Rob Bygrave 6785d5703e Update tests for DB2 forUpdate() 2024-07-17 08:01:26 +12:00
Rob Bygrave 0a95ac7fa0 Update tests for DB2 forUpdate() 2024-07-16 22:18:29 +12:00
Rob BygraveandGitHub bbd5636689 #3444 - DBName with hyphens creates invalid code (#3447) 2024-07-16 20:22:08 +12:00
Noemi PramlandGitHub 9f0eaef3d8 Db2 forupdate fix (#3446)
* ADD failing test for db2

* ADD: possible fix
2024-07-16 19:37:20 +12:00
Rob Bygrave a5d2a574d3 #3433 - Put test back from desc -> description as desc as a column name isn't widely supported by other databases 2024-07-08 21:54:35 +12:00
Rob BygraveandGitHub 6e7adbb8e4 #3433 - Entity property called desc used with query order by desc generates bad sql (#3438) 2024-07-04 00:05:01 +12:00
Rob BygraveandGitHub 8913bead89 [ebean-migration] #3434 - Connection is closed error running migration with MySql and Hikari (#3437) 2024-07-02 23:47:14 +12:00
Rob BygraveandGitHub cdf1cd8487 [ebean-redis] Fix for #3432 empty value for natural keys (#3436) 2024-07-02 22:06:57 +12:00
Rob BygraveandGitHub 2582c4b653 Bump to ebean-agent 14.5.0/15.5.0 with ASM 9.7 for JDK 22 and 23 support (#3435) 2024-07-02 18:08:08 +12:00
Rob Bygrave a9e3e2ea43 Version 14.4.0 2024-06-26 22:33:04 +12:00
Rob Bygrave 834ce971b3 Bump ebean-agent, no functional change 2024-06-26 22:19:58 +12:00
Rob BygraveandGitHub 5c27f89677 Change includeLabelInSql to default to true (#3431)
To disable this use ebean.includeLabelInSql=false or set this via
`DatabaseBuilder.includeLabelInSql(false)``
2024-06-26 20:32:19 +12:00
Rob BygraveandGitHub bfbbb7e7a7 Bump ebean-datasource dependency to 9.0 with automatic Lambda mode detection (#3430)
* Bump ebean-datasource dependency to 9.0 with automatic Lambda mode detection

ebean-datasource 9.0 automatically detects when it is running in AWS Lambda
and will set validateOnHeartbeat to false. In Lambda we don't want to validate
connections in background threads that can suspend in Lambda.
2024-06-26 20:27:49 +12:00
Rob BygraveandGitHub a8947c7d2f Bump ebean-migration dependency to 14.1.0 with fastMode enabled by default (#3429)
ebean-migration 14.1.0 defaults fastMode to true. This means that it first
runs a fast check that all migrations have been run. If that does not succeed
for any reason it runs the normal migration process.
2024-06-26 20:15:21 +12:00
Rob BygraveandGitHub d5401efb08 #3423 JtaTransactionManager keeps reference to already closed scope (and Transaction/connection ThreadLocal) when JtaTxnListener#afterCompletion is called by a different thread (#3424)
Bugfix for "Long/slow transaction reaper" in Wildfire that can close/rollback a transaction in a different thread. Fix is to use active flag to inactivate the transaction and detect that case in JtaTransactionManager.getCurrentTransaction()
2024-06-21 07:57:31 +12:00
Rob Bygrave 925ceaf441 Update build actions/setup-java@v4 2024-06-14 21:47:43 +12:00
Rob Bygrave c5fea13ca5 Update build actions/setup-java@v4 2024-06-14 21:42:06 +12:00
Rob Bygrave 6fa3759786 Add 23 to build 2024-06-14 21:34:54 +12:00
Rob Bygrave 1c5288b6ec Version 14.3.1 2024-06-10 19:26:40 +12:00
Rob Bygrave b485fb7440 Bump ebean-agent to 14.3.1 2024-06-10 19:02:41 +12:00
Rob BygraveandGitHub b07f909dd3 Bump parent pom to 4.2 with EA changes to annotation processing (#3417)
* Bump parent pom to 4.2 with EA changes to annotation processing

Annotation processing needs to be explicitly on in JDK 24 EA builds

* Bump bytebuddy version in ebean-test and use experimental=true for EA builds

* Use parent pom 4.3 with bytebuddy.experimental=true by default for surefire
2024-06-10 18:36:40 +12:00
Rob BygraveandGitHub fa92e7a4b7 Merge pull request #3416 from ebean-orm/feature/querybean-gen-suppresswarnings
Change QueryBean to use protected constructors, suppress warning on generated query beans
2024-06-10 12:39:53 +12:00
Rob Bygrave 1823ef288a Change QueryBean to use protected constructors, suppress warning on generated query beans 2024-06-07 22:48:04 +12:00
Rob BygraveandGitHub 7b2128b02f Merge pull request #3415 from ebean-orm/feature/3412
#3412 Use existing/parent transaction with fetch query on ToMany paths
2024-06-07 22:31:24 +12:00
Rob Bygrave 3c84a902e8 Followup for #3412 - Refactor DefaultBeanLoader move query creation logic into LoadBeanRequest
Move the query creation, using transaction and configure into LoadBeanRequest. This code then looks more like that of LoadManyRequest.
2024-06-07 22:26:48 +12:00
Rob Bygrave 3ce95c34cb #3412 Use existing/parent transaction with fetch query on ToMany paths
Fix such that LoadManyRequest uses the parent requests transaction rather
than obtain a new read only transaction to perform the fetch query.
2024-06-07 22:16:02 +12:00
Rob BygraveandGitHub d817dcb549 Merge pull request #3413 from ebean-orm/feature/fix-logging-naturalKey
Fix logging message for naturalKey MPUT to include the natural key properties
2024-05-31 21:57:24 +12:00
Rob BygraveandGitHub 5f218e1fdb Merge pull request #3409 from kaiyaok2/fix_NIO
Fixed non-idempotent tests in `TestCacheSerialization`
2024-05-31 21:56:33 +12:00
Rob Bygrave 59daf5689a Fix logging message for naturalKey MPUT to include the natural key properties 2024-05-31 21:52:38 +12:00
kaiyaok2 44efed5cd9 fixed non-idempotent tests 2024-05-26 23:57:12 +08:00
572 changed files with 27684 additions and 1128 deletions
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'zulu'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'adopt'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'zulu'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -16,18 +16,18 @@ jobs:
strategy:
fail-fast: false
matrix:
java_version: [GA,EA]
java_version: [GA,EA,23]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: oracle-actions/setup-java@v1
with:
website: jdk.java.net
release: ${{ matrix.java_version }}
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'adopt'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -17,14 +17,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'adopt'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'zulu'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'adopt'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'zulu'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'adopt'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -17,14 +17,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'adopt'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'adopt'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+3 -3
View File
@@ -20,14 +20,14 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java_version }}
distribution: 'adopt'
- name: Maven cache
uses: actions/cache@v3
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
+1 -1
View File
@@ -56,7 +56,7 @@ Work at the highest level of abstraction and drop down levels as needed.
<tr>
<td align="center" valign="middle">
<a href="https://www.foconis.de/" target="_blank">
<img width="222px" src="https://www.foconis.de/templates/yootheme/cache/foconis_logo_322-709da1de.png">
<img width="222px" src="https://group.foconis.com/download/ci/logo/png-72dpi/logo-quer/foconis-analytics-quer.png">
</a>
</td>
<td align="center" valign="middle">
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+85
View File
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
<name>ebean-net-postgis</name>
<description>ebean-net-postgis composite</description>
<artifactId>ebean-net-postgis</artifactId>
<properties>
<postgis.jdbc.version>2023.1.0</postgis.jdbc.version>
<postgres.jdbc.version>42.7.2</postgres.jdbc.version>
</properties>
<dependencies>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-datasource</artifactId>
<version>${ebean-datasource.version}</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>${ebean-migration.version}</version>
</dependency>
<!-- Technically optional but most expected to use query beans -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-net-postgis-types</artifactId>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgres.jdbc.version}</version>
<exclusions>
<!-- exclude unnecessary checker framework -->
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
<version>${postgis.jdbc.version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,7 @@
package io.ebean.postgis.assembly;
/**
* Nothing interesting here - required placeholder for javadoc.
*/
public class Assembly {
}
@@ -0,0 +1,10 @@
module io.ebean.postgis {
requires transitive io.ebean.api;
requires transitive io.ebean.core;
requires transitive io.ebean.datasource;
requires transitive io.ebean.querybean;
requires transitive io.ebean.platform.postgres;
// requires transitive io.ebean.postgis.types;
}
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+2 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
</parent>
<artifactId>composites</artifactId>
@@ -24,6 +24,7 @@
<module>ebean-oracle</module>
<module>ebean-postgres</module>
<module>ebean-postgis</module>
<module>ebean-net-postgis</module>
<!-- <module>sqlanywhere</module>-->
<module>ebean-sqlite</module>
<module>ebean-sqlserver</module>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
</parent>
<name>ebean api</name>
@@ -155,4 +155,10 @@ public abstract class BeanFinder<I,T> {
return db().findNative(type, nativeSql);
}
/**
* Creates a query using the ORM query language.
*/
protected Query<T> query(String ormQuery) {
return db().createQuery(type, ormQuery);
}
}
+52
View File
@@ -741,6 +741,21 @@ public final class DB {
return getDefault().createUpdate(beanType, ormUpdate);
}
/**
* Create a named query.
* <p>
* For RawSql the named query is expected to be in ebean.xml.
*
* @param beanType The type of entity bean
* @param namedQuery The name of the query
* @param <T> The type of entity bean
* @return The query
*/
public static <T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery) {
return getDefault().createNamedQuery(beanType, namedQuery);
}
/**
* Create a query for a type of entity bean.
* <p>
@@ -760,6 +775,43 @@ public final class DB {
return getDefault().createQuery(beanType);
}
/**
* Parse the Ebean query language statement returning the query which can then
* be modified (add expressions, change order by clause, change maxRows, change
* fetch and select paths etc).
* <p>
* <h3>Example</h3>
* <pre>{@code
*
* // Find order additionally fetching the customer, details and details.product name.
*
* String eql = "fetch customer fetch details fetch details.product (name) where id = :orderId ";
*
* Query<Order> query = DB.createQuery(Order.class, eql);
* query.setParameter("orderId", 2);
*
* Order order = query.findOne();
*
* // This is the same as:
*
* Order order = DB.find(Order.class)
* .fetch("customer")
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findOne();
*
* }</pre>
*
* @param beanType The type of bean to fetch
* @param eql The Ebean query
* @param <T> The type of the entity bean
* @return The query with expressions defined as per the parsed query statement
*/
public static <T> Query<T> createQuery(Class<T> beanType, String eql) {
return getDefault().createQuery(beanType, eql);
}
/**
* Create a query for a type of entity bean.
* <p>
@@ -242,6 +242,18 @@ public interface Database {
*/
<T> UpdateQuery<T> update(Class<T> beanType);
/**
* Create a named query.
* <p>
* For RawSql the named query is expected to be in ebean.xml.
*
* @param beanType The type of entity bean
* @param namedQuery The name of the query
* @param <T> The type of entity bean
* @return The query
*/
<T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery);
/**
* Create a query for an entity bean and synonym for {@link #find(Class)}.
*
@@ -249,6 +261,41 @@ public interface Database {
*/
<T> Query<T> createQuery(Class<T> beanType);
/**
* Parse the Ebean query language statement returning the query which can then
* be modified (add expressions, change order by clause, change maxRows, change
* fetch and select paths etc).
* <p>
* <h3>Example</h3>
* <pre>{@code
*
* // Find order additionally fetching the customer, details and details.product name.
*
* String ormQuery = "fetch customer fetch details fetch details.product (name) where id = :orderId ";
*
* Query<Order> query = DB.createQuery(Order.class, ormQuery);
* query.setParameter("orderId", 2);
*
* Order order = query.findOne();
*
* // This is the same as:
*
* Order order = DB.find(Order.class)
* .fetch("customer")
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findOne();
*
* }</pre>
*
* @param beanType The type of bean to fetch
* @param ormQuery The Ebean ORM query
* @param <T> The type of the entity bean
* @return The query with expressions defined as per the parsed query statement
*/
<T> Query<T> createQuery(Class<T> beanType, String ormQuery);
/**
* Create a query for a type of entity bean.
* <p>
@@ -412,6 +459,18 @@ public interface Database {
*/
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
/**
* Create a named Query for DTO beans.
* <p>
* DTO beans are just normal bean like classes with public constructor(s) and setters.
* They do not need to be registered with DB before use.
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param namedQuery The name of the query
* @param <T> The type of the DTO bean.
*/
<T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery);
/**
* Look to execute a native sql query that does not return beans but instead
* returns SqlRow or direct access to ResultSet.
@@ -1326,6 +1385,109 @@ public interface Database {
*/
ScriptRunner script();
/**
* Return the Document store.
*/
DocumentStore docStore();
/**
* Publish a single bean given its type and id returning the resulting live bean.
* <p>
* The values are published from the draft to the live bean.
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean
* @param transaction the transaction the publish process should use (can be null)
*/
@Nullable
<T> T publish(Class<T> beanType, Object id, Transaction transaction);
/**
* Publish a single bean given its type and id returning the resulting live bean.
* This will use the current transaction or create one if required.
* <p>
* The values are published from the draft to the live bean.
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean
*/
@Nullable
<T> T publish(Class<T> beanType, Object id);
/**
* Publish the beans that match the query returning the resulting published beans.
* <p>
* The values are published from the draft beans to the live beans.
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
* @param transaction the transaction the publish process should use (can be null)
*/
<T> List<T> publish(Query<T> query, Transaction transaction);
/**
* Publish the beans that match the query returning the resulting published beans.
* This will use the current transaction or create one if required.
* <p>
* The values are published from the draft beans to the live beans.
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
*/
<T> List<T> publish(Query<T> query);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean to restore
* @param transaction the transaction the restore process should use (can be null)
*/
@Nullable
<T> T draftRestore(Class<T> beanType, Object id, Transaction transaction);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean to restore
*/
@Nullable
<T> T draftRestore(Class<T> beanType, Object id);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
* @param transaction the transaction the restore process should use (can be null)
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
*/
<T> List<T> draftRestore(Query<T> query);
/**
* Returns the set of properties/paths that are unknown (do not map to known properties or paths).
* <p>
@@ -13,6 +13,8 @@ import io.ebean.event.*;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import jakarta.persistence.EnumType;
import javax.sql.DataSource;
@@ -689,6 +691,38 @@ public interface DatabaseBuilder {
@Deprecated
DatabaseBuilder setChangeLogAsync(boolean changeLogAsync);
/**
* Set the ReadAuditLogger to use. If not set the default implementation is used
* which logs the read events in JSON format to a standard named SLF4J logger
* (which can be configured in say logback to log to a separate log file).
*/
default DatabaseBuilder readAuditLogger(ReadAuditLogger readAuditLogger) {
return setReadAuditLogger(readAuditLogger);
}
/**
* @deprecated migrate to {@link #readAuditLogger(ReadAuditLogger)}.
*/
@Deprecated
DatabaseBuilder setReadAuditLogger(ReadAuditLogger readAuditLogger);
/**
* Set the ReadAuditPrepare to use.
* <p>
* It is expected that an implementation is used that read user context information
* (user id, user ip address etc) and sets it on the ReadEvent bean before it is sent
* to the ReadAuditLogger.
*/
default DatabaseBuilder readAuditPrepare(ReadAuditPrepare readAuditPrepare) {
return setReadAuditPrepare(readAuditPrepare);
}
/**
* @deprecated migrate to {@link #readAuditPrepare(ReadAuditPrepare)}.
*/
@Deprecated
DatabaseBuilder setReadAuditPrepare(ReadAuditPrepare readAuditPrepare);
/**
* Set the configuration for profiling.
*/
@@ -971,6 +1005,16 @@ public interface DatabaseBuilder {
@Deprecated
DatabaseBuilder setAllQuotedIdentifiers(boolean allQuotedIdentifiers);
/**
* Set to true if this Database is Document store only instance (has no JDBC DB).
*/
DatabaseBuilder setDocStoreOnly(boolean docStoreOnly);
/**
* Set the configuration for the ElasticSearch integration.
*/
DatabaseBuilder setDocStoreConfig(DocStoreConfig docStoreConfig);
/**
* Set the constraint naming convention used in DDL generation.
*/
@@ -2413,6 +2457,16 @@ public interface DatabaseBuilder {
*/
boolean isChangeLogAsync();
/**
* Return the ReadAuditLogger to use.
*/
ReadAuditLogger getReadAuditLogger();
/**
* Return the ReadAuditPrepare to use.
*/
ReadAuditPrepare getReadAuditPrepare();
/**
* Return the tenancy catalog provider.
*/
@@ -2551,6 +2605,16 @@ public interface DatabaseBuilder {
*/
boolean isAllQuotedIdentifiers();
/**
* Return true if this Database is a Document store only instance (has no JDBC DB).
*/
boolean isDocStoreOnly();
/**
* Return the configuration for the ElasticSearch integration.
*/
DocStoreConfig getDocStoreConfig();
/**
* Return the constraint naming convention used in DDL generation.
*/
@@ -0,0 +1,94 @@
package io.ebean;
/**
* Bean holding the details to update the document store.
*/
public final class DocStoreQueueEntry {
/**
* Action to either update or delete a document from the index.
*/
public enum Action {
/**
* Action is to update a document in the doc store.
*/
INDEX(1),
/**
* Action is to delete a document from the doc store..
*/
DELETE(2),
/**
* An update is required based on a change to a nested/embedded object at a given path.
*/
NESTED(3);
int value;
Action(int value) {
this.value = value;
}
/**
* Return the value associated with this action type.
*/
public int getValue() {
return value;
}
}
private final Action type;
private final String queueId;
private final String path;
private final Object beanId;
/**
* Construct for an INDEX or DELETE action.
*/
public DocStoreQueueEntry(Action type, String queueId, Object beanId) {
this(type, queueId, null, beanId);
}
/**
* Construct for an NESTED/embedded path invalidation action.
*/
public DocStoreQueueEntry(Action type, String queueId, String path, Object beanId) {
this.type = type;
this.queueId = queueId;
this.path = path;
this.beanId = beanId;
}
/**
* Return the event type.
*/
public Action getType() {
return type;
}
/**
* Return the associate queueId.
*/
public String getQueueId() {
return queueId;
}
/**
* Return the path if this is a nested update.
*/
public String getPath() {
return path;
}
/**
* Return the bean id (which matches the document id).
*/
public Object getBeanId() {
return beanId;
}
}
@@ -0,0 +1,312 @@
package io.ebean;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import io.ebean.docstore.DocQueryContext;
import io.ebean.docstore.RawDoc;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Document storage operations.
*/
@NonNullApi
public interface DocumentStore {
/**
* Update the associated document store using the result of the query.
* <p>
* This will execute the query against the database creating a document for each
* bean graph and sending this to the document store.
* </p>
* <p>
* Note that the select and fetch paths of the query is set for you to match the
* document structure needed based on <code>@DocStore</code> and <code>@DocStoreEmbedded</code>
* so what this query requires is the predicates only.
* </p>
* <p>
* This query will be executed using findEach so it is safe to use a query
* that will fetch a lot of beans. The default bulkBatchSize is used.
* </p>
*
* @param query The query that selects object to send to the document store.
*/
<T> void indexByQuery(Query<T> query);
/**
* Update the associated document store index using the result of the query additionally specifying a
* bulkBatchSize to use for sending the messages to ElasticSearch.
*
* @param query The query that selects object to send to the document store.
* @param bulkBatchSize The batch size to use when bulk sending to the document store.
*/
<T> void indexByQuery(Query<T> query, int bulkBatchSize);
/**
* Update the document store for all beans of this type.
* <p>
* This is the same as indexByQuery where the query has no predicates and so fetches all rows.
* </p>
*/
void indexAll(Class<?> beanType);
/**
* Return the bean by fetching it's content from the document store.
* If the document is not found null is returned.
* <p>
* Typically this is called indirectly by findOne() on the query.
* </p>
* <pre>{@code
*
* Customer customer =
* database.find(Customer.class)
* .setUseDocStore(true)
* .setId(42)
* .findOne();
*
* }</pre>
*/
@Nullable
<T> T find(DocQueryContext<T> request);
/**
* Execute the find list query. This request is prepared to execute secondary queries.
* <p>
* Typically this is called indirectly by findList() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* List<Customer> newCustomers =
* database.find(Customer.class)
* .setUseDocStore(true)
* .where().eq("status, Customer.Status.NEW)
* .findList();
*
* }</pre>
*/
<T> List<T> findList(DocQueryContext<T> request);
/**
* Execute the query against the document store returning the paged list.
* <p>
* The query should have <code>firstRow</code> or <code>maxRows</code> set prior to calling this method.
* </p>
* <p>
* Typically this is called indirectly by findPagedList() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* PagedList<Customer> newCustomers =
* database.find(Customer.class)
* .setUseDocStore(true)
* .where().eq("status, Customer.Status.NEW)
* .setMaxRows(50)
* .findPagedList();
*
* }</pre>
*/
<T> PagedList<T> findPagedList(DocQueryContext<T> request);
/**
* Execute the query against the document store with the expectation of a large set of results
* that are processed in a scrolling resultSet fashion.
* <p>
* For example, with the ElasticSearch doc store this uses SCROLL.
* </p>
* <p>
* Typically this is called indirectly by findEach() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* database.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEach((Order order) -> {
* // process the bean ...
* });
*
* }</pre>
*/
<T> void findEach(DocQueryContext<T> query, Consumer<T> consumer);
/**
* Execute the query against the document store with the expectation of a large set of results
* that are processed in a scrolling resultSet fashion.
* <p>
* Unlike findEach() this provides the opportunity to stop iterating through the large query.
* </p>
* <p>
* For example, with the ElasticSearch doc store this uses SCROLL.
* </p>
* <p>
* Typically this is called indirectly by findEachWhile() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* database.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEachWhile(new Predicate<Order>() {
* @Override
* public void accept(Order bean) {
* // process the bean
*
* // return true to continue, false to stop
* // boolean shouldContinue = ...
* return shouldContinue;
* }
* });
*
* }</pre>
*/
<T> void findEachWhile(DocQueryContext<T> query, Predicate<T> consumer);
/**
* Find each processing raw documents.
*
* @param indexNameType The full index name and type
* @param rawQuery The query to execute
* @param consumer Consumer to process each document
*/
void findEach(String indexNameType, String rawQuery, Consumer<RawDoc> consumer);
/**
* Find each processing raw documents stopping when the predicate returns false.
*
* @param indexNameType The full index name and type
* @param rawQuery The query to execute
* @param consumer Consumer to process each document until false is returned
*/
void findEachWhile(String indexNameType, String rawQuery, Predicate<RawDoc> consumer);
/**
* Process the queue entries sending updates to the document store or queuing them for later processing.
*/
long process(List<DocStoreQueueEntry> queueEntries) throws IOException;
/**
* Drop the index from the document store (similar to DDL drop table).
* <pre>{@code
*
* DocumentStore documentStore = database.docStore();
*
* documentStore.dropIndex("product_copy");
*
* }</pre>
*/
void dropIndex(String indexName);
/**
* Create an index given a mapping file as a resource in the classPath (similar to DDL create table).
* <pre>{@code
*
* DocumentStore documentStore = database.docStore();
*
* // uses product_copy.mapping.json resource
* // ... to define mappings for the index
*
* documentStore.createIndex("product_copy", null);
*
* }</pre>
*
* @param indexName the name of the new index
* @param alias the alias of the index
*/
void createIndex(String indexName, String alias);
/**
* Modify the settings on an index.
* <p>
* For example, this can be used be used to set elasticSearch refresh_interval
* on an index before a bulk update.
* </p>
* <pre>{@code
*
* // refresh_interval -1 ... disable refresh while bulk loading
*
* Map<String,Object> settings = new LinkedHashMap<>();
* settings.put("refresh_interval", "-1");
*
* documentStore.indexSettings("product", settings);
*
* }</pre>
* <pre>{@code
*
* // refresh_interval 1s ... restore after bulk loading
*
* Map<String,Object> settings = new LinkedHashMap<>();
* settings.put("refresh_interval", "1s");
*
* documentStore.indexSettings("product", settings);
*
* }</pre>
*
* @param indexName the name of the index to update settings on
* @param settings the settings to set on the index
*/
void indexSettings(String indexName, Map<String, Object> settings);
/**
* Copy the index to a new index.
* <p>
* This copy process does not use the database but instead will copy from the source index to a destination index.
* </p>
* <pre>{@code
*
* long copyCount = documentStore.copyIndex(Product.class, "product_copy");
*
* }</pre>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex);
/**
* Copy entries from an index to a new index but limiting to documents that have been
* modified since the sinceEpochMillis time.
* <p>
* To support this the document needs to have a <code>@WhenModified</code> property.
* </p>
* <pre>{@code
*
* long copyCount = documentStore.copyIndex(Product.class, "product_copy", sinceMillis);
*
* }</pre>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex, long sinceEpochMillis);
/**
* Copy from a source index to a new index taking only the documents
* matching the given query.
* <pre>{@code
*
* // predicates to select the source documents to copy
* Query<Product> query = database.find(Product.class)
* .where()
* .ge("whenModified", new Timestamp(since))
* .ge("name", "A")
* .lt("name", "D")
* .query();
*
* // copy from the source index to "product_copy" index
* long copyCount = documentStore.copyIndex(query, "product_copy", 1000);
*
* }</pre>
*
* @param query The query to select the source documents to copy
* @param newIndex The target index to copy the documents to
* @param bulkBatchSize The ElasticSearch bulk batch size, if 0 uses the default.
* @return The number of documents copied to the new index.
*/
long copyIndex(Query<?> query, String newIndex, int bulkBatchSize);
}
@@ -1,5 +1,7 @@
package io.ebean;
import io.ebean.search.*;
import java.util.Collection;
import java.util.Map;
@@ -623,6 +625,31 @@ public interface ExpressionFactory {
*/
Expression raw(String raw);
/**
* Create a Text Match expression (currently doc store/Elastic only).
*/
Expression textMatch(String propertyName, String search, Match options);
/**
* Create a Text Multi match expression (currently doc store/Elastic only).
*/
Expression textMultiMatch(String query, MultiMatch options);
/**
* Create a text simple query expression (currently doc store/Elastic only).
*/
Expression textSimple(String search, TextSimple options);
/**
* Create a text query string expression (currently doc store/Elastic only).
*/
Expression textQueryString(String search, TextQueryString options);
/**
* Create a text common terms expression (currently doc store/Elastic only).
*/
Expression textCommonTerms(String search, TextCommonTerms options);
/**
* And - join two expressions with a logical and.
*/
@@ -666,4 +693,12 @@ public interface ExpressionFactory {
*/
<T> Junction<T> junction(Junction.Type type, Query<T> query, ExpressionList<T> parent);
/**
* Add the expressions to the given expression list.
*
* @param where The expression list to add the expressions to
* @param expressions The expressions that are parsed
* @param params Bind parameters to match ? or ?1 bind positions.
*/
<T> void where(ExpressionList<T> where, String expressions, Object[] params);
}
@@ -2,6 +2,7 @@ package io.ebean;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import io.ebean.search.*;
import jakarta.persistence.NonUniqueResultException;
import java.sql.Connection;
@@ -105,6 +106,11 @@ public interface ExpressionList<T> {
*/
Query<T> asOf(Timestamp asOf);
/**
* Execute the query against the draft set of tables.
*/
Query<T> asDraft();
/**
* Convert the query to a DTO bean query.
* <p>
@@ -507,6 +513,28 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> filterMany(String manyProperty);
/**
* @deprecated for removal - migrate to {@link #filterManyRaw(String, String, Object...)}.
* <p>
* Add filter expressions to the many property.
*
* <pre>{@code
*
* DB.find(Customer.class)
* .where()
* .eq("name", "Rob")
* .filterMany("orders", "status = ?", Status.NEW)
* .findList();
*
* }</pre>
*
* @param manyProperty The many property
* @param expressions Filter expressions with and, or and ? or ?1 type bind parameters
* @param params Bind parameters used in the expressions
*/
@Deprecated(forRemoval = true)
ExpressionList<T> filterMany(String manyProperty, String expressions, Object... params);
/**
* Add filter expressions for the many path. The expressions can include SQL functions if
* desired and the property names are translated to column names.
@@ -558,6 +586,19 @@ public interface ExpressionList<T> {
*/
Query<T> setDistinct(boolean distinct);
/**
* Set the index(es) to search for a document store which uses partitions.
* <p>
* For example, when executing a query against ElasticSearch with daily indexes we can
* explicitly specify the indexes to search against.
* </p>
*
* @param indexName The index or indexes to search against
* @return This query
* @see Query#setDocIndexName(String)
*/
Query<T> setDocIndexName(String indexName);
/**
* Set the first row to fetch.
*
@@ -641,6 +682,14 @@ public interface ExpressionList<T> {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set to true if this query should execute against the doc store.
* <p>
* When setting this you may also consider disabling lazy loading.
* </p>
*/
Query<T> setUseDocStore(boolean useDocsStore);
/**
* Set true if you want to disable lazy loading.
* <p>
@@ -649,6 +698,16 @@ public interface ExpressionList<T> {
*/
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
/**
* Disable read auditing for this query.
* <p>
* This is intended to be used when the query is not a user initiated query and instead
* part of the internal processing in an application to load a cache or document store etc.
* In these cases we don't want the query to be part of read auditing.
* </p>
*/
Query<T> setDisableReadAuditing();
/**
* Set a label on the query (to help identify query execution statistics).
*/
@@ -668,6 +727,14 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> where();
/**
* Add the expressions to this expression list.
*
* @param expressions The expressions that are parsed and added to this expression list
* @param params Bind parameters to match ? or ?1 bind positions.
*/
ExpressionList<T> where(String expressions, Object... params);
/**
* Path exists - for the given path in a JSON document.
* <pre>{@code
@@ -1562,6 +1629,47 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> rawOrEmpty(String raw, Collection<?> values);
/**
* Add a match expression.
*
* @param propertyName The property name for the match
* @param search The search value
*/
ExpressionList<T> match(String propertyName, String search);
/**
* Add a match expression with options.
*
* @param propertyName The property name for the match
* @param search The search value
*/
ExpressionList<T> match(String propertyName, String search, Match options);
/**
* Add a multi-match expression.
*/
ExpressionList<T> multiMatch(String search, String... properties);
/**
* Add a multi-match expression using options.
*/
ExpressionList<T> multiMatch(String search, MultiMatch options);
/**
* Add a simple query string expression.
*/
ExpressionList<T> textSimple(String search, TextSimple options);
/**
* Add a query string expression.
*/
ExpressionList<T> textQueryString(String search, TextQueryString options);
/**
* Add common terms expression.
*/
ExpressionList<T> textCommonTerms(String search, TextCommonTerms options);
/**
* And - join two expressions with a logical and.
*/
@@ -1704,6 +1812,42 @@ public interface ExpressionList<T> {
*/
Junction<T> disjunction();
/**
* Start a list of expressions that will be joined by MUST.
* <p>
* This automatically makes the query a useDocStore(true) query that
* will execute against the document store (ElasticSearch etc).
* </p>
* <p>
* This is logically similar to and().
* </p>
*/
Junction<T> must();
/**
* Start a list of expressions that will be joined by SHOULD.
* <p>
* This automatically makes the query a useDocStore(true) query that
* will execute against the document store (ElasticSearch etc).
* </p>
* <p>
* This is logically similar to or().
* </p>
*/
Junction<T> should();
/**
* Start a list of expressions that will be joined by MUST NOT.
* <p>
* This automatically makes the query a useDocStore(true) query that
* will execute against the document store (ElasticSearch etc).
* </p>
* <p>
* This is logically similar to not().
* </p>
*/
Junction<T> mustNot();
/**
* End a junction returning the parent expression list.
* <p>
@@ -213,4 +213,11 @@ public class Finder<I, T> {
return db().findNative(type, nativeSql);
}
/**
* Creates a query using the ORM query language.
*/
public Query<T> query(String ormQuery) {
return db().createQuery(type, ormQuery);
}
}
+67 -29
View File
@@ -186,15 +186,15 @@ public class OrderBy<T> implements Serializable {
if (list.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
var append = new StringAppend();
for (int i = 0; i < list.size(); i++) {
Property property = list.get(i);
if (i > 0) {
sb.append(", ");
append.append(", ");
}
sb.append(property.toStringFormat());
property.toStringFormat(append);
}
return sb.toString();
return append.toString();
}
@Override
@@ -231,6 +231,55 @@ public class OrderBy<T> implements Serializable {
return this;
}
/**
* Append the order by clause.
*/
public interface Append {
/**
* Append a property expression.
*/
Append property(String property);
/**
* Append a literal.
*/
Append append(String literal);
/**
* Parse and append an expression.
*/
Append parse(String expression);
}
private static final class StringAppend implements Append {
private final StringBuilder builder = new StringBuilder();
@Override
public String toString() {
return builder.toString();
}
@Override
public Append property(String property) {
builder.append(property);
return this;
}
@Override
public Append append(String literal) {
builder.append(literal);
return this;
}
@Override
public Append parse(String raw) {
builder.append(raw);
return this;
}
}
/**
* A property and its ascending descending order.
*/
@@ -309,36 +358,25 @@ public class OrderBy<T> implements Serializable {
@Override
public String toString() {
return toStringFormat();
return property;
}
public String toStringFormat() {
if (nulls == null && collation == null) {
if (ascending) {
return property;
public void toStringFormat(Append append) {
if (collation != null) {
if (collation.contains("${}")) {
// this is a complex collation, e.g. DB2 - we must replace the property
append.parse(collation.replace("${}", property));
} else {
return property + " desc";
append.property(property).append(" collate ").append(collation);
}
} else {
StringBuilder sb = new StringBuilder();
if (collation != null) {
if (collation.contains("${}")) {
// this is a complex collation, e.g. DB2 - we must replace the property
sb.append(collation.replace("${}", property));
} else {
sb.append(property);
sb.append(" collate ").append(collation);
}
} else {
sb.append(property);
}
if (!ascending) {
sb.append(' ').append("desc");
}
if (nulls != null) {
sb.append(' ').append(nulls).append(' ').append(highLow);
}
return sb.toString();
append.property(property);
}
if (!ascending) {
append.append(" desc");
}
if (nulls != null) {
append.append(" ").append(nulls).append(" ").append(highLow);
}
}
@@ -367,6 +367,23 @@ public interface Query<T> extends CancelableQuery, QueryBuilder<Query<T>, T> {
*/
ExpressionList<T> where();
/**
* Add Full text search expressions for Document store queries.
* <p>
* This is currently ElasticSearch only and provides the full text
* expressions such as Match and Multi-Match.
* <p>
* This automatically makes this query a "Doc Store" query and will execute
* against the document store (ElasticSearch).
* <p>
* Expressions added here are added to the "query" section of an ElasticSearch
* query rather than the "filter" section.
* <p>
* Expressions added to the where() are added to the "filter" section of an
* ElasticSearch query.
*/
ExpressionList<T> text();
/**
* This applies a filter on the 'many' property list rather than the root
* level objects.
@@ -483,6 +500,11 @@ public interface Query<T> extends CancelableQuery, QueryBuilder<Query<T>, T> {
*/
LockType getForUpdateLockType();
/**
* Returns the inherit type. This is normally the same as getBeanType() returns as long as no other type is set.
*/
Class<? extends T> getInheritType();
/**
* Return the type of query being executed.
*/
@@ -20,6 +20,7 @@ import java.util.stream.Stream;
* @param <SELF> The type of the builder
* @param <T> The entity bean type
*/
//public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends QueryBuilderProjection<SELF, T> {
public interface QueryBuilder<SELF, T> extends QueryBuilderProjection<SELF, T> {
/**
@@ -27,6 +28,15 @@ public interface QueryBuilder<SELF, T> extends QueryBuilderProjection<SELF, T> {
*/
SELF alias(String alias);
/**
* Apply changes to the query using a function.
* <p>
* This can be used to apply generic features to queries.
*
* @param apply The changes to apply to the query
*/
SELF also(Consumer<QueryBuilder<?, ?>> apply);
/**
* Apply changes to the query conditional on the supplied predicate.
* <p>
@@ -47,6 +57,11 @@ public interface QueryBuilder<SELF, T> extends QueryBuilderProjection<SELF, T> {
*/
SELF asOf(Timestamp asOf);
/**
* Execute the query against the draft set of tables.
*/
SELF asDraft();
/**
* Convert the query to a DTO bean query.
* <p>
@@ -216,7 +231,44 @@ public interface QueryBuilder<SELF, T> extends QueryBuilderProjection<SELF, T> {
*/
SELF setHint(String hint);
/**
/**
* Set the index(es) to search for a document store which uses partitions.
* <p>
* For example, when executing a query against ElasticSearch with daily indexes we can
* explicitly specify the indexes to search against.
* </p>
* <pre>{@code
*
* // explicitly specify the indexes to search
* query.setDocIndexName("logstash-2016.11.5,logstash-2016.11.6")
*
* // search today's index
* query.setDocIndexName("$today")
*
* // search the last 3 days
* query.setDocIndexName("$last-3")
*
* }</pre>
* <p>
* If the indexName is specified with ${daily} e.g. "logstash-${daily}" ... then we can use
* $today and $last-x as the search docIndexName like the examples below.
* </p>
* <pre>{@code
*
* // search today's index
* query.setDocIndexName("$today")
*
* // search the last 3 days
* query.setDocIndexName("$last-3")
*
* }</pre>
*
* @param indexName The index or indexes to search against
* @return This query
*/
SELF setDocIndexName(String indexName);
/**
* Execute the query including soft deleted rows.
* <p>
* This means that Ebean will not add any predicates to the query for filtering out
@@ -226,6 +278,15 @@ public interface QueryBuilder<SELF, T> extends QueryBuilderProjection<SELF, T> {
*/
SELF setIncludeSoftDeletes();
/**
* Disable read auditing for this query.
* <p>
* This is intended to be used when the query is not a user initiated query and instead
* part of the internal processing in an application to load a cache or document store etc.
* In these cases we don't want the query to be part of read auditing.
*/
SELF setDisableReadAuditing();
/**
* Set true if you want to disable lazy loading.
* <p>
@@ -238,6 +299,20 @@ public interface QueryBuilder<SELF, T> extends QueryBuilderProjection<SELF, T> {
*/
SELF setDistinct(boolean distinct);
/**
* Restrict the query to only return subtypes of the given inherit type.
* <pre>{@code
*
* List<Animal> animals =
* new QAnimal()
* .name.startsWith("Fluffy")
* .setInheritType(Cat.class)
* .findList();
*
* }</pre>
*/
SELF setInheritType(Class<? extends T> type);
/**
* Set the first row to return for this query.
*
@@ -306,6 +381,13 @@ public interface QueryBuilder<SELF, T> extends QueryBuilderProjection<SELF, T> {
*/
SELF setMapKey(String mapKey);
/**
* Set to true if this query should execute against the doc store.
* <p>
* When setting this you may also consider disabling lazy loading.
*/
SELF setUseDocStore(boolean useDocStore);
/**
* When set to true when you want the returned beans to be read only.
*/
@@ -6,6 +6,7 @@ package io.ebean;
* @param <SELF> The builder type
* @param <T> The entity bean type
*/
//public interface QueryBuilderProjection<SELF extends QueryBuilderProjection<SELF, T>, T> {
public interface QueryBuilderProjection<SELF, T> {
/**
@@ -1,7 +1,9 @@
package io.ebean;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.DocStoreConfig;
import jakarta.persistence.PersistenceException;
import java.sql.Connection;
@@ -209,6 +211,29 @@ public interface Transaction extends AutoCloseable {
*/
boolean isActive();
/**
* Set the behavior for document store updates on this transaction.
* <p>
* For example, set the mode to DocStoreEvent.IGNORE for this transaction and
* then any changes via this transaction are not sent to the doc store. This
* would be used when doing large bulk inserts into the database and we want
* to control how that is sent to the document store.
* </p>
*/
void setDocStoreMode(DocStoreMode mode);
/**
* Set the batch size to use for sending messages to the document store.
* <p>
* You might set this if you know the changes in this transaction result in especially large or
* especially small payloads and want to adjust the batch size to match.
* </p>
* <p>
* Setting this overrides the default of {@link DocStoreConfig#getBulkBatchSize()}
* </p>
*/
void setDocStoreBatchSize(int batchSize);
/**
* Explicitly turn off or on the cascading nature of save() and delete(). This
* gives the developer exact control over what beans are saved and deleted
@@ -16,6 +16,8 @@ import io.ebean.event.*;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetricNamingMatch;
import io.ebean.util.StringHelper;
import jakarta.persistence.EnumType;
@@ -127,7 +129,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
* When true then include a sql comment in generated SELECT queries with the query
* label or profile location label.
*/
private boolean includeLabelInSql;
private boolean includeLabelInSql = true;
/**
* Interesting classes such as entities, embedded, ScalarTypes,
@@ -141,6 +143,16 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
*/
private List<String> packages = new ArrayList<>();
/**
* Configuration for the ElasticSearch integration.
*/
private DocStoreConfig docStoreConfig = new DocStoreConfig();
/**
* Set to true when the Database only uses Document store.
*/
private boolean docStoreOnly;
/**
* This is used to populate @WhoCreated, @WhoModified and
* support other audit features (who executed a query etc).
@@ -415,6 +427,8 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
private ChangeLogListener changeLogListener;
private ChangeLogRegister changeLogRegister;
private boolean changeLogAsync = true;
private ReadAuditLogger readAuditLogger;
private ReadAuditPrepare readAuditPrepare;
private EncryptKeyManager encryptKeyManager;
private EncryptDeployManager encryptDeployManager;
private Encryptor encryptor;
@@ -988,6 +1002,28 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
return this;
}
@Override
public ReadAuditLogger getReadAuditLogger() {
return readAuditLogger;
}
@Override
public DatabaseConfig setReadAuditLogger(ReadAuditLogger readAuditLogger) {
this.readAuditLogger = readAuditLogger;
return this;
}
@Override
public ReadAuditPrepare getReadAuditPrepare() {
return readAuditPrepare;
}
@Override
public DatabaseConfig setReadAuditPrepare(ReadAuditPrepare readAuditPrepare) {
this.readAuditPrepare = readAuditPrepare;
return this;
}
@Override
public ProfilingConfig getProfilingConfig() {
return profilingConfig;
@@ -1262,6 +1298,28 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
}
}
@Override
public boolean isDocStoreOnly() {
return docStoreOnly;
}
@Override
public DatabaseConfig setDocStoreOnly(boolean docStoreOnly) {
this.docStoreOnly = docStoreOnly;
return this;
}
@Override
public DocStoreConfig getDocStoreConfig() {
return docStoreConfig;
}
@Override
public DatabaseConfig setDocStoreConfig(DocStoreConfig docStoreConfig) {
this.docStoreConfig = docStoreConfig;
return this;
}
@Override
public DbConstraintNaming getConstraintNaming() {
return platformConfig.getConstraintNaming();
@@ -2034,6 +2092,13 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
readOnlyDataSourceConfig.loadSettings(p.properties, name + "-ro");
}
/**
* This is broken out to allow overridden behaviour.
*/
protected void loadDocStoreSettings(PropertiesWrapper p) {
docStoreConfig.loadSettings(p);
}
/**
* This is broken out to allow overridden behaviour.
*/
@@ -2065,6 +2130,11 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
}
loadDataSourceSettings(p);
if (docStoreConfig == null) {
docStoreConfig = new DocStoreConfig();
}
loadDocStoreSettings(p);
defaultServer = p.getBoolean("defaultServer", defaultServer);
readOnlyDatabase = p.getBoolean("readOnlyDatabase", readOnlyDatabase);
autoPersistUpdates = p.getBoolean("autoPersistUpdates", autoPersistUpdates);
@@ -2081,6 +2151,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
queryPlanCapturePeriodSecs = p.getLong("queryPlan.capturePeriodSecs", queryPlanCapturePeriodSecs);
queryPlanCaptureMaxTimeMillis = p.getLong("queryPlan.captureMaxTimeMillis", queryPlanCaptureMaxTimeMillis);
queryPlanCaptureMaxCount = p.getInt("queryPlan.captureMaxCount", queryPlanCaptureMaxCount);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
localOnlyL2Cache = p.getBoolean("localOnlyL2Cache", localOnlyL2Cache);
enabledL2Regions = p.get("enabledL2Regions", enabledL2Regions);
@@ -0,0 +1,320 @@
package io.ebean.config;
import io.ebean.Transaction;
import io.ebean.annotation.DocStoreMode;
/**
* Configuration for the Document store integration (e.g. ElasticSearch).
*/
public class DocStoreConfig {
/**
* True when the Document store integration is active/on.
*/
protected boolean active;
/**
* Set to true means Ebean will generate mapping files on startup.
*/
protected boolean generateMapping;
/**
* When true the Document store should drop and re-create document indexes.
*/
protected boolean dropCreate;
/**
* When true the Document store should create any document indexes that don't already exist.
*/
protected boolean create;
/**
* The URL of the Document store. For example: http://localhost:9200.
*/
protected String url;
/**
* Credential that be used for authentication to document store.
*/
protected String username;
/**
* Password credential that be used for authentication to document store.
*/
protected String password;
/**
* Set to true such that the client allows connections to invalid/self signed SSL certificates.
*/
protected boolean allowAllCertificates;
/**
* The default mode used by indexes.
*/
protected DocStoreMode persist = DocStoreMode.UPDATE;
/**
* The default batch size to use for the Bulk API calls.
*/
protected int bulkBatchSize = 1000;
/**
* Resource path for the Document store mapping files.
*/
protected String mappingPath;
/**
* Suffix used for mapping files.
*/
protected String mappingSuffix;
/**
* Location of resources that mapping files are generated into.
*/
protected String pathToResources = "src/main/resources";
/**
* Return true if the Document store (ElasticSearch) integration is active.
*/
public boolean isActive() {
String systemValue = System.getProperty("ebean.docstore.active");
if (systemValue != null) {
return Boolean.parseBoolean(systemValue);
}
return active;
}
/**
* Set to true to make the Document store (ElasticSearch) integration active.
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* Return the URL to the Document store.
*/
public String getUrl() {
String systemValue = System.getProperty("ebean.docstore.url");
if (systemValue != null) {
return systemValue;
}
return url;
}
/**
* Return the user credential for connecting to the document store.
*/
public String getUsername() {
return username;
}
/**
* Set the user credential for connecting to the document store.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Return the password credential for connecting to the document store.
*/
public String getPassword() {
return password;
}
/**
* Set the password credential for connecting to the document store.
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Set the URL to the Document store.
* <p>
* For a local ElasticSearch server this would be: http://localhost:9200
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Return true if Ebean should generate mapping files on server startup.
*/
public boolean isGenerateMapping() {
String systemValue = System.getProperty("ebean.docstore.generateMapping");
if (systemValue != null) {
return Boolean.parseBoolean(systemValue);
}
return generateMapping;
}
/**
* Set to true if Ebean should generate mapping files on server startup.
*/
public void setGenerateMapping(boolean generateMapping) {
this.generateMapping = generateMapping;
}
/**
* Return true if the document store should recreate mapped indexes.
*/
public boolean isDropCreate() {
String systemValue = System.getProperty("ebean.docstore.dropCreate");
if (systemValue != null) {
return Boolean.parseBoolean(systemValue);
}
return dropCreate;
}
/**
* Set to true if the document store should recreate mapped indexes.
*/
public void setDropCreate(boolean dropCreate) {
this.dropCreate = dropCreate;
}
/**
* Create true if the document store should create mapped indexes that don't yet exist.
* This is only used if dropCreate is false.
*/
public boolean isCreate() {
String systemValue = System.getProperty("ebean.docstore.create");
if (systemValue != null) {
return Boolean.parseBoolean(systemValue);
}
return create;
}
/**
* Set to true if the document store should create mapped indexes that don't yet exist.
* This is only used if dropCreate is false.
*/
public void setCreate(boolean create) {
this.create = create;
}
/**
* Return true if the client allows connections to invalid/self signed SSL certificates.
*/
public boolean isAllowAllCertificates() {
return allowAllCertificates;
}
/**
* Set to true such that the client allows connections to invalid/self signed SSL certificates.
*/
public void setAllowAllCertificates(boolean allowAllCertificates) {
this.allowAllCertificates = allowAllCertificates;
}
/**
* Return the default batch size to use for calls to the Bulk API.
*/
public int getBulkBatchSize() {
return bulkBatchSize;
}
/**
* Set the default batch size to use for calls to the Bulk API.
* <p>
* The batch size can be set on a transaction via {@link Transaction#setDocStoreBatchSize(int)}.
* </p>
*/
public void setBulkBatchSize(int bulkBatchSize) {
this.bulkBatchSize = bulkBatchSize;
}
/**
* Return the mapping path.
*/
public String getMappingPath() {
return mappingPath;
}
/**
* Set the mapping path.
*/
public void setMappingPath(String mappingPath) {
this.mappingPath = mappingPath;
}
/**
* Return the mapping suffix.
*/
public String getMappingSuffix() {
return mappingSuffix;
}
/**
* Set the mapping suffix.
*/
public void setMappingSuffix(String mappingSuffix) {
this.mappingSuffix = mappingSuffix;
}
/**
* Return the relative file system path to resources when generating mapping files.
*/
public String getPathToResources() {
return pathToResources;
}
/**
* Set the relative file system path to resources when generating mapping files.
*/
public void setPathToResources(String pathToResources) {
this.pathToResources = pathToResources;
}
/**
* Return the default behavior for when Insert, Update and Delete events occur on beans that have an associated
* Document store.
*/
public DocStoreMode getPersist() {
return persist;
}
/**
* Set the default behavior for when Insert, Update and Delete events occur on beans that have an associated
* Document store.
* <ul>
* <li>DocStoreEvent.UPDATE - build and send message to Bulk API</li>
* <li>DocStoreEvent.QUEUE - add an entry with the index type and id only into a queue for later processing</li>
* <li>DocStoreEvent.IGNORE - ignore. Most likely used when some scheduled batch job handles updating the index</li>
* </ul>
* <p>
* You might choose to use QUEUE if that particular index data is updating very frequently or the cost of indexing
* is expensive. Setting it to QUEUE can mean many changes can be batched together potentially coalescing multiple
* updates for an index entry into a single update.
* </p>
* <p>
* You might choose to use IGNORE when you have your own external process for updating the indexes. In this case
* you don't want Ebean to do anything when the data changes.
* </p>
*/
public void setPersist(DocStoreMode persist) {
this.persist = persist;
}
/**
* Load settings specified in properties files.
*/
public void loadSettings(PropertiesWrapper properties) {
active = properties.getBoolean("docstore.active", active);
url = properties.get("docstore.url", url);
username = properties.get("docstore.username", url);
password = properties.get("docstore.password", url);
persist = properties.getEnum(DocStoreMode.class, "docstore.persist", persist);
bulkBatchSize = properties.getInt("docstore.bulkBatchSize", bulkBatchSize);
generateMapping = properties.getBoolean("docstore.generateMapping", generateMapping);
dropCreate = properties.getBoolean("docstore.dropCreate", dropCreate);
create = properties.getBoolean("docstore.create", create);
allowAllCertificates = properties.getBoolean("docstore.allowAllCertificates", allowAllCertificates);
mappingPath = properties.get("docstore.mappingPath", mappingPath);
mappingSuffix = properties.get("docstore.mappingSuffix", mappingSuffix);
pathToResources = properties.get("docstore.pathToResources", pathToResources);
}
}
@@ -0,0 +1,7 @@
package io.ebean.docstore;
/**
* Document Mapping for a bean marker interface.
*/
public interface DocMapping {
}
@@ -0,0 +1,7 @@
package io.ebean.docstore;
/**
* Document query request context marker interface.
*/
public interface DocQueryContext<T> {
}
@@ -0,0 +1,7 @@
package io.ebean.docstore;
/**
* Document update context marker interface.
*/
public interface DocUpdateContext {
}
@@ -0,0 +1,102 @@
package io.ebean.docstore;
import java.util.Map;
/**
* Raw document.
*/
public class RawDoc {
private Map<String, Object> source;
private String id;
private double score;
private String index;
private String type;
/**
* Construct the document with all the meta data.
*/
public RawDoc(Map<String, Object> source, String id, double score, String index, String type) {
this.source = source;
this.id = id;
this.score = score;
this.index = index;
this.type = type;
}
/**
* Construct empty (typically for JSON marshalling).
*/
public RawDoc() {
}
/**
* Return the source document as a Map.
*/
public Map<String, Object> getSource() {
return source;
}
/**
* Return the Id value.
*/
public String getId() {
return id;
}
/**
* Return the score.
*/
public double getScore() {
return score;
}
/**
* Return the index name.
*/
public String getIndex() {
return index;
}
/**
* Return the index type.
*/
public String getType() {
return type;
}
/**
* Set the source document.
*/
public void setSource(Map<String, Object> source) {
this.source = source;
}
/**
* Set the id value.
*/
public void setId(String id) {
this.id = id;
}
/**
* Set the score.
*/
public void setScore(double score) {
this.score = score;
}
/**
* Set the index name.
*/
public void setIndex(String index) {
this.index = index;
}
/**
* Set the index type.
*/
public void setType(String type) {
this.type = type;
}
}
@@ -0,0 +1,37 @@
package io.ebean.event.readaudit;
/**
* Log that the query was executed
*/
public interface ReadAuditLogger {
/**
* Called when a new query plan is created.
* <p>
* The query plan has the full sql and logging the query plan separately means that each of
* the bean and many read events can log the query plan key and not the full sql (reducing the
* bulk size of the read audit logs).
* </p>
*/
void queryPlan(ReadAuditQueryPlan queryPlan);
/**
* Audit a find bean query that returned a bean.
* <p>
* Finds that did not return a bean are excluded.
* </p>
*/
void auditBean(ReadEvent readBean);
/**
* Audit a find many query that returned some beans.
* <p>
* Finds that did not return any beans are excluded.
* </p>
* <p>
* For large queries executed via findEach() etc the ids are collected in batches
* and logged. Hence the ids list has a maximum size of the batch size.
* </p>
*/
void auditMany(ReadEvent readMany);
}
@@ -0,0 +1,23 @@
package io.ebean.event.readaudit;
/**
* Set user context information into the read event prior to it being logged.
*/
public interface ReadAuditPrepare {
/**
* Prepare the read event by setting any user context information into the read event such as the
* application user id and ip address.
* <p>
* This method is called prior to the read event being sent to the ReadAuditLogger.
* </p>
* <p>
* Note that for findFutureList() queries prepare() is called early in the foreground thread
* prior to the query executing and at that point the ReadEvent bean only has the bean type
* and no other details (which are populated later when the query is executed in the background
* thread).
* </p>
*/
void prepare(ReadEvent readEvent);
}
@@ -0,0 +1,97 @@
package io.ebean.event.readaudit;
/**
* A SQL query and associated keys.
* <p>
* This is logged as a separate event so that the
* </p>
*/
public class ReadAuditQueryPlan {
String beanType;
String queryKey;
String sql;
/**
* Construct given the beanType, queryKey and sql.
*/
public ReadAuditQueryPlan(String beanType, String queryKey, String sql) {
this.beanType = beanType;
this.queryKey = queryKey;
this.sql = sql;
}
/**
* Construct for JSON tools.
*/
public ReadAuditQueryPlan() {
}
@Override
public String toString() {
return "beanType:" + beanType + " queryKey:" + queryKey + " sql:" + sql;
}
/**
* Return the bean type.
*/
public String getBeanType() {
return beanType;
}
/**
* Set the bean type.
*/
public void setBeanType(String beanType) {
this.beanType = beanType;
}
/**
* Return the query key (relative to the bean type).
*/
public String getQueryKey() {
return queryKey;
}
/**
* Set the query key.
*/
public void setQueryKey(String queryKey) {
this.queryKey = queryKey;
}
/**
* Return the sql statement.
*/
public String getSql() {
return sql;
}
/**
* Set the sql statement.
*/
public void setSql(String sql) {
this.sql = sql;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ReadAuditQueryPlan that = (ReadAuditQueryPlan) o;
if (!beanType.equals(that.beanType)) return false;
if (!queryKey.equals(that.queryKey)) return false;
return sql.equals(that.sql);
}
@Override
public int hashCode() {
int result = beanType.hashCode();
result = 92821 * result + queryKey.hashCode();
result = 92821 * result + sql.hashCode();
return result;
}
}
@@ -0,0 +1,261 @@
package io.ebean.event.readaudit;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Read event sent to the ReadEventLogger.
* <p>
* This is a flattened in that it contains either a read bean or list of beans. It is flattened
* in this way to simplify logging and processing and simply means that it either contains an
* id or a list of ids.
* </p>
*/
public class ReadEvent {
/**
* User defined 'source' such as the application name.
*/
protected String source;
/**
* Application user id expected to be optionally populated by ChangeLogPrepare.
*/
protected String userId;
/**
* Application user ip address expected to be optionally populated by ChangeLogPrepare.
*/
protected String userIpAddress;
/**
* Arbitrary user context information expected to be optionally populated by ChangeLogPrepare.
*/
protected Map<String, String> userContext;
/**
* The time the bean change was created.
*/
protected long eventTime;
/**
* The type of the bean(s) read.
*/
protected String beanType;
/**
* The query key (relative to the bean type).
*/
protected String queryKey;
/**
* The bind log when the query was executed.
*/
protected String bindLog;
/**
* The id of the bean read.
*/
protected Object id;
/**
* The ids of the beans read.
*/
protected List<Object> ids;
/**
* Common constructor for single bean and multi-bean read events.
*/
protected ReadEvent(String beanType, String queryKey, String bindLog) {
this.beanType = beanType;
this.queryKey = queryKey;
this.bindLog = bindLog;
this.eventTime = System.currentTimeMillis();
}
/**
* Construct for a single bean read.
*/
public ReadEvent(String beanType, String queryKey, String bindLog, Object id) {
this(beanType, queryKey, bindLog);
this.id = id;
}
/**
* Construct for many beans read.
*/
public ReadEvent(String beanType, String queryKey, String bindLog, List<Object> ids) {
this(beanType, queryKey, bindLog);
this.ids = ids;
}
/**
* Construct for many future list query.
*/
public ReadEvent(String beanType) {
this.beanType = beanType;
this.eventTime = System.currentTimeMillis();
}
/**
* Constructor for JSON tools.
*/
public ReadEvent() {
}
/**
* Return a code that identifies the source of the change (like the name of the application).
*/
public String getSource() {
return source;
}
/**
* Set the source of the change (like the name of the application).
*/
public void setSource(String source) {
this.source = source;
}
/**
* Return the application user Id.
*/
public String getUserId() {
return userId;
}
/**
* Set the application user Id.
* <p>
* This can be set by the ChangeLogListener in the prepare() method which is called
* in the foreground thread.
* </p>
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* Return the application users ip address.
*/
public String getUserIpAddress() {
return userIpAddress;
}
/**
* Set the application users ip address.
* <p>
* This can be set by the ChangeLogListener in the prepare() method which is called
* in the foreground thread.
* </p>
*/
public void setUserIpAddress(String userIpAddress) {
this.userIpAddress = userIpAddress;
}
/**
* Return a user context value - anything you set yourself in ChangeLogListener prepare().
*/
public Map<String, String> getUserContext() {
if (userContext == null) {
userContext = new LinkedHashMap<>();
}
return userContext;
}
/**
* Set a user context value (anything you like).
* <p>
* This can be set by the ChangeLogListener in the prepare() method which is called
* in the foreground thread.
* </p>
*/
public void setUserContext(Map<String, String> userContext) {
this.userContext = userContext;
}
/**
* Return the type of bean read.
*/
public String getBeanType() {
return beanType;
}
/**
* Set the type of bean read.
*/
public void setBeanType(String beanType) {
this.beanType = beanType;
}
/**
* Return the query key (relative to the bean type).
*/
public String getQueryKey() {
return queryKey;
}
/**
* Set the query key (relative to the bean type).
*/
public void setQueryKey(String queryKey) {
this.queryKey = queryKey;
}
/**
* Return the bind log used when executing the query.
*/
public String getBindLog() {
return bindLog;
}
/**
* Set the bind log used when executing the query.
*/
public void setBindLog(String bindLog) {
this.bindLog = bindLog;
}
/**
* Return the event date time.
*/
public long getEventTime() {
return eventTime;
}
/**
* Set the event date time.
*/
public void setEventTime(long eventTime) {
this.eventTime = eventTime;
}
/**
* Return the id of the bean read.
*/
public Object getId() {
return id;
}
/**
* Set the id of the bean read.
*/
public void setId(Object id) {
this.id = id;
}
/**
* Return the ids of the beans read.
*/
public List<Object> getIds() {
return ids;
}
/**
* Set the ids of the beans read.
*/
public void setIds(List<Object> ids) {
this.ids = ids;
}
}
@@ -0,0 +1,8 @@
/**
* Provides Auditing of read events including queries and L2 cache.
* <p>
* Provides a built support for supplied an audit of all the 'read events' for beans annotated
* with <code>@ReadAudit</code>
* </p>
*/
package io.ebean.event.readaudit;
@@ -0,0 +1,72 @@
package io.ebean.plugin;
import io.ebean.FetchPath;
import io.ebean.Query;
import io.ebean.docstore.DocUpdateContext;
import java.io.IOException;
/**
* Doc store functions for a specific entity bean type.
*
* @param <T> The type of entity bean
*/
public interface BeanDocType<T> {
/**
* Return the doc store index type for this bean type.
*/
String indexType();
/**
* Return the doc store index name for this bean type.
*/
String indexName();
/**
* Apply the appropriate fetch path to the query such that the query returns beans matching
* the document store structure with the expected embedded properties.
*/
void applyPath(Query<T> spiQuery);
/**
* Return the FetchPath for the embedded document.
*/
FetchPath embedded(String path);
/**
* For embedded 'many' properties we need a FetchPath relative to the root which is used to
* build and replace the embedded list.
*/
FetchPath embeddedManyRoot(String path);
/**
* Return a 'raw' property mapped for the given property.
* If none exists the given property is returned.
*/
String rawProperty(String property);
/**
* Store the bean in the doc store index.
* <p>
* This somewhat assumes the bean is fetched with appropriate path properties
* to match the expected document structure.
*/
void index(Object idValue, T bean, DocUpdateContext txn) throws IOException;
/**
* Add a delete by Id to the doc store.
*/
void deleteById(Object idValue, DocUpdateContext txn) throws IOException;
/**
* Add a embedded document update to the doc store.
*
* @param idValue the Id value of the bean holding the embedded document
* @param embeddedProperty the embedded property
* @param embeddedRawContent the content of the embedded document in JSON form
* @param txn the doc store transaction to add the update to
*/
void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocUpdateContext txn) throws IOException;
}
@@ -2,12 +2,15 @@ package io.ebean.plugin;
import io.ebean.Query;
import io.ebean.config.dbplatform.IdType;
import io.ebean.docstore.DocMapping;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
import io.ebean.event.BeanQueryAdapter;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
/**
* Information and methods on BeanDescriptors made available to plugins.
@@ -142,4 +145,73 @@ public interface BeanType<T> {
*/
IdType idType();
/**
* Return true if this bean type has doc store backing.
*/
boolean isDocStoreMapped();
/**
* Return the DocumentMapping for this bean type.
* <p>
* This is the document structure and mapping options for how this bean type is mapped
* for the document store.
* </p>
*/
DocMapping docMapping();
/**
* Return the doc store queueId for this bean type.
*/
String docStoreQueueId();
/**
* Return the doc store support for this bean type.\
*/
BeanDocType<T> docStore();
/**
* Add the discriminator value to the query if needed.
*/
void addInheritanceWhere(Query<?> query);
/**
* Return the root bean type for an inheritance hierarchy.
*/
BeanType<?> root();
/**
* Return true if this bean type has an inheritance hierarchy.
*/
boolean hasInheritance();
/**
* Return true if this object is the root level object in its entity
* inheritance.
*/
boolean isInheritanceRoot();
/**
* Returns all direct children of this beantype
*/
List<BeanType<?>> inheritanceChildren();
/**
* Returns the parent in inheritance hierarchy
*/
BeanType<?> inheritanceParent();
/**
* Visit all children recursively
*/
void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor);
/**
* Return the discriminator column.
*/
String discColumn();
/**
* Create a bean given the discriminator value.
*/
T createBeanUsingDisc(Object discValue);
}
@@ -38,6 +38,11 @@ public interface SpiServer extends Database {
*/
List<? extends BeanType<?>> beanTypes(String baseTableName);
/**
* Return the bean type for a given doc store queueId.
*/
BeanType<?> beanTypeForQueueId(String queueId);
/**
* Return a BeanLoader.
*/
@@ -0,0 +1,98 @@
package io.ebean.search;
/**
* Options for the text match and multi match expressions.
*/
public abstract class AbstractMatch {
protected boolean operatorAnd;
protected String analyzer;
protected double boost;
protected String minShouldMatch;
protected int maxExpansions;
protected String zeroTerms;
protected double cutoffFrequency;
protected String fuzziness;
protected int prefixLength;
protected String rewrite;
/**
* Return true if using the AND operator otherwise using the OR operator.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the boost.
*/
public double getBoost() {
return boost;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the zero terms option.
*/
public String getZeroTerms() {
return zeroTerms;
}
/**
* Return the cutoff frequency.
*/
public double getCutoffFrequency() {
return cutoffFrequency;
}
/**
* Return the max expansions.
*/
public int getMaxExpansions() {
return maxExpansions;
}
/**
* Return the analyzer.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the fuzziness.
*/
public String getFuzziness() {
return fuzziness;
}
/**
* Return the prefix length.
*/
public int getPrefixLength() {
return prefixLength;
}
/**
* Return the rewrite option.
*/
public String getRewrite() {
return rewrite;
}
}
@@ -0,0 +1,117 @@
package io.ebean.search;
/**
* Options for the text match expression.
*/
public class Match extends AbstractMatch {
protected boolean phrase;
protected boolean phrasePrefix;
public Match() {
}
/**
* Set this to be a "Phrase" type expression.
*/
public Match phrase() {
phrase = true;
return this;
}
/**
* Set this to be a "Phrase Prefix" type expression.
*/
public Match phrasePrefix() {
phrasePrefix = true;
return this;
}
/**
* Use the AND operator (rather than OR).
*/
public Match opAnd() {
operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public Match opOr() {
operatorAnd = false;
return this;
}
/**
* Set the zero terms.
*/
public Match zeroTerms(String zeroTerms) {
this.zeroTerms = zeroTerms;
return this;
}
/**
* Set the cutoff frequency.
*/
public Match cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set the max expansions (for phrase prefix only).
*/
public Match maxExpansions(int maxExpansions) {
this.maxExpansions = maxExpansions;
return this;
}
/**
* Set the Analyzer to use for this expression.
*/
public Match analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the boost.
*/
public Match boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the rewrite to use.
*/
public Match minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the rewrite to use.
*/
public Match rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return true if this is a phrase query.
*/
public boolean isPhrase() {
return phrase;
}
/**
* Return true if this is a phrase prefix query.
*/
public boolean isPhrasePrefix() {
return phrasePrefix;
}
}
@@ -0,0 +1,148 @@
package io.ebean.search;
/**
* Options for the text match expression.
*/
public class MultiMatch extends AbstractMatch {
/**
* The MultiMatch type.
*/
public enum Type {
BEST_FIELDS,
MOST_FIELDS,
CROSS_FIELDS,
PHRASE,
PHRASE_PREFIX
}
protected final String[] fields;
protected Type type = Type.BEST_FIELDS;
protected double tieBreaker;
/**
* Create with the given fields.
*/
public static MultiMatch fields(String... fields) {
return new MultiMatch(fields);
}
/**
* Construct with a set of fields.
*/
public MultiMatch(String... fields) {
this.fields = fields;
}
/**
* Set the type of query.
*/
public MultiMatch type(Type type) {
this.type = type;
return this;
}
/**
* Set the tieBreaker to use.
*/
public MultiMatch tieBreaker(double tieBreaker) {
this.tieBreaker = tieBreaker;
return this;
}
/**
* Use the AND operator (rather than OR).
*/
public MultiMatch opAnd() {
operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public MultiMatch opOr() {
operatorAnd = false;
return this;
}
/**
* Set the minimum should match value.
*/
public MultiMatch minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the boost.
*/
public MultiMatch boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the zero terms.
*/
public MultiMatch zeroTerms(String zeroTerms) {
this.zeroTerms = zeroTerms;
return this;
}
/**
* Set the cutoff frequency.
*/
public MultiMatch cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set the max expansions (for phrase prefix only).
*/
public MultiMatch maxExpansions(int maxExpansions) {
this.maxExpansions = maxExpansions;
return this;
}
/**
* Set the Analyzer to use for this expression.
*/
public MultiMatch analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the rewrite to use.
*/
public MultiMatch rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return the type.
*/
public Type getType() {
return type;
}
/**
* Return the fields to search.
*/
public String[] getFields() {
return fields;
}
/**
* Return the tie breaker.
*/
public double getTieBreaker() {
return tieBreaker;
}
}
@@ -0,0 +1,139 @@
package io.ebean.search;
/**
* Text common terms query.
* <p>
* This maps to an ElasticSearch "common terms query".
* </p>
* <pre>{@code
*
* TextCommonTerms options = new TextCommonTerms()
* .cutoffFrequency(0.001)
* .minShouldMatch("50%")
* .lowFreqOperatorAnd(true)
* .highFreqOperatorAnd(true);
*
* List<Customer> customers = database.find(Customer.class)
* .text()
* .textCommonTerms("the brown", options)
* .findList();
*
* }</pre>
* <pre>{@code
*
* // ElasticSearch expression
*
* "common": {
* "body": {
* "query": "the brown",
* "cutoff_frequency": 0.001,
* "low_freq_operator": "and",
* "high_freq_operator": "and",
* "minimum_should_match": "50%"
* }
* }
*
* }</pre>
*/
public class TextCommonTerms {
protected double cutoffFrequency;
protected boolean lowFreqOperatorAnd;
protected boolean highFreqOperatorAnd;
protected String minShouldMatch;
protected String minShouldMatchLowFreq;
protected String minShouldMatchHighFreq;
/**
* Set the cutoff frequency.
*/
public TextCommonTerms cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set to true if low frequency terms should use AND operator.
*/
public TextCommonTerms lowFreqOperatorAnd(boolean opAnd) {
this.lowFreqOperatorAnd = opAnd;
return this;
}
/**
* Set to true if high frequency terms should use AND operator.
*/
public TextCommonTerms highFreqOperatorAnd(boolean opAnd) {
this.highFreqOperatorAnd = opAnd;
return this;
}
/**
* Set the minimum should match.
*/
public TextCommonTerms minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the minimum should match for low frequency terms.
*/
public TextCommonTerms minShouldMatchLowFreq(String minShouldMatchLowFreq) {
this.minShouldMatchLowFreq = minShouldMatchLowFreq;
return this;
}
/**
* Set the minimum should match for high frequency terms.
*/
public TextCommonTerms minShouldMatchHighFreq(String minShouldMatchHighFreq) {
this.minShouldMatchHighFreq = minShouldMatchHighFreq;
return this;
}
/**
* Return true if low freq should use the AND operator.
*/
public boolean isLowFreqOperatorAnd() {
return lowFreqOperatorAnd;
}
/**
* Return true if high freq should use the AND operator.
*/
public boolean isHighFreqOperatorAnd() {
return highFreqOperatorAnd;
}
/**
* Return the cutoff frequency.
*/
public double getCutoffFrequency() {
return cutoffFrequency;
}
/**
* Return the minimum to match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the minimum to match for high frequency.
*/
public String getMinShouldMatchHighFreq() {
return minShouldMatchHighFreq;
}
/**
* Return the minimum to match for low frequency.
*/
public String getMinShouldMatchLowFreq() {
return minShouldMatchLowFreq;
}
}
@@ -0,0 +1,398 @@
package io.ebean.search;
/**
* Text query string options.
* <p>
* This maps to an ElasticSearch "query string query".
* </p>
* <pre>{@code
*
* TextQueryString options = new TextQueryString()
* .analyzeWildcard(true)
* .fields("name")
* .lenient(true)
* .opAnd();
*
* List<Customer> customers = database.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
* <pre>{@code
*
* // just use default options
* TextQueryString options = new TextQueryString();
*
* List<Customer> customers = database.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*/
public class TextQueryString {
public static final int DEFAULT_FUZZY_MAX_EXPANSIONS = 50;
protected final String[] fields;
/**
* Only used when multiple fields set.
*/
protected boolean useDisMax = true;
/**
* Only used when multiple fields set.
*/
protected double tieBreaker;
protected String defaultField;
protected boolean operatorAnd;
protected String analyzer;
protected boolean allowLeadingWildcard = true;
protected boolean lowercaseExpandedTerms = true;
protected int fuzzyMaxExpansions = DEFAULT_FUZZY_MAX_EXPANSIONS;
protected String fuzziness;
protected int fuzzyPrefixLength;
protected double phraseSlop;
protected double boost;
protected boolean analyzeWildcard;
protected boolean autoGeneratePhraseQueries;
protected String minShouldMatch;
protected boolean lenient;
protected String locale;
protected String timeZone;
protected String rewrite;
/**
* Create with given fields.
*/
public static TextQueryString fields(String... fields) {
return new TextQueryString(fields);
}
/**
* Construct with the fields to use.
*/
public TextQueryString(String... fields) {
this.fields = fields;
}
/**
* Use the AND operator (rather than OR).
*/
public TextQueryString opAnd() {
this.operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public TextQueryString opOr() {
this.operatorAnd = false;
return this;
}
/**
* Set the locale.
*/
public TextQueryString locale(String locale) {
this.locale = locale;
return this;
}
/**
* Set lenient mode.
*/
public TextQueryString lenient(boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* Set the minimum should match.
*/
public TextQueryString minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the analyzer.
*/
public TextQueryString analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set useDisMax option (when multiple fields only).
*/
public TextQueryString useDisMax(boolean useDisMax) {
this.useDisMax = useDisMax;
return this;
}
/**
* Set tieBreaker option (when multiple fields only).
*/
public TextQueryString tieBreaker(double tieBreaker) {
this.tieBreaker = tieBreaker;
return this;
}
/**
* Set the default field.
*/
public TextQueryString defaultField(String defaultField) {
this.defaultField = defaultField;
return this;
}
/**
* Set allow leading wildcard mode.
*/
public TextQueryString allowLeadingWildcard(boolean allowLeadingWildcard) {
this.allowLeadingWildcard = allowLeadingWildcard;
return this;
}
/**
* Set lowercase expanded terms mode.
*/
public TextQueryString lowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
this.lowercaseExpandedTerms = lowercaseExpandedTerms;
return this;
}
/**
* Set fuzzy max expansions.
*/
public TextQueryString fuzzyMaxExpansions(int fuzzyMaxExpansions) {
this.fuzzyMaxExpansions = fuzzyMaxExpansions;
return this;
}
/**
* Set fuzziness.
*/
public TextQueryString fuzziness(String fuzziness) {
this.fuzziness = fuzziness;
return this;
}
/**
* Set the fuzzy prefix length.
*/
public TextQueryString fuzzyPrefixLength(int fuzzyPrefixLength) {
this.fuzzyPrefixLength = fuzzyPrefixLength;
return this;
}
/**
* Set the phrase slop.
*/
public TextQueryString phraseSlop(double phraseSlop) {
this.phraseSlop = phraseSlop;
return this;
}
/**
* Set the boost.
*/
public TextQueryString boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the analyze wildcard mode.
*/
public TextQueryString analyzeWildcard(boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Set the auto generate phrase queries mode.
*/
public TextQueryString autoGeneratePhraseQueries(boolean autoGeneratePhraseQueries) {
this.autoGeneratePhraseQueries = autoGeneratePhraseQueries;
return this;
}
/**
* Set the time zone.
*/
public TextQueryString timeZone(String timeZone) {
this.timeZone = timeZone;
return this;
}
/**
* Set the rewrite option.
*/
public TextQueryString rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return the rewrite option.
*/
public String getRewrite() {
return rewrite;
}
/**
* Return the fields.
*/
public String[] getFields() {
return fields;
}
/**
* Return true if AND is the default operator.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the analyzer.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the locale.
*/
public String getLocale() {
return locale;
}
/**
* Return lenient mode.
*/
public boolean isLenient() {
return lenient;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the useDixMax mode.
*/
public boolean isUseDisMax() {
return useDisMax;
}
/**
* Return the tie breaker.
*/
public double getTieBreaker() {
return tieBreaker;
}
/**
* Return the default field.
*/
public String getDefaultField() {
return defaultField;
}
/**
* Return the allow leading wildcard mode.
*/
public boolean isAllowLeadingWildcard() {
return allowLeadingWildcard;
}
/**
* Return the lowercase expanded terms mode.
*/
public boolean isLowercaseExpandedTerms() {
return lowercaseExpandedTerms;
}
/**
* Return the fuzzy max expansions.
*/
public int getFuzzyMaxExpansions() {
return fuzzyMaxExpansions;
}
/**
* Return the fuzziness.
*/
public String getFuzziness() {
return fuzziness;
}
/**
* Return the fuzzy prefix length.
*/
public int getFuzzyPrefixLength() {
return fuzzyPrefixLength;
}
/**
* Return the phrase slop.
*/
public double getPhraseSlop() {
return phraseSlop;
}
/**
* Return the analyze wildcard mode.
*/
public boolean isAnalyzeWildcard() {
return analyzeWildcard;
}
/**
* Return the boost.
*/
public double getBoost() {
return boost;
}
/**
* Return the auto generate phase queries mode.
*/
public boolean isAutoGeneratePhraseQueries() {
return autoGeneratePhraseQueries;
}
/**
* Return the time zone.
*/
public String getTimeZone() {
return timeZone;
}
}
@@ -0,0 +1,193 @@
package io.ebean.search;
/**
* Simple text query options.
* <p>
* This maps to an ElasticSearch "simple text query".
* </p>
* <pre>{@code
*
* TextSimple options = new TextSimple()
* .analyzeWildcard(true)
* .fields("name")
* .lenient(true)
* .opAnd();
*
* List<Customer> customers = database.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*/
public class TextSimple {
protected String[] fields;
protected boolean operatorAnd;
protected String analyzer;
protected String flags;
protected boolean lowercaseExpandedTerms = true;
protected boolean analyzeWildcard;
protected String locale;
protected boolean lenient;
protected String minShouldMatch;
/**
* Construct
*/
public TextSimple() {
}
/**
* Set the fields.
*/
public TextSimple fields(String... fields) {
this.fields = fields;
return this;
}
/**
* Use AND as the default operator.
*/
public TextSimple opAnd() {
this.operatorAnd = true;
return this;
}
/**
* Use OR as the default operator.
*/
public TextSimple opOr() {
this.operatorAnd = false;
return this;
}
/**
* Set the analyzer
*/
public TextSimple analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the flags.
*/
public TextSimple flags(String flags) {
this.flags = flags;
return this;
}
/**
* Set the false to not use lowercase expanded terms.
*/
public TextSimple lowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
this.lowercaseExpandedTerms = lowercaseExpandedTerms;
return this;
}
/**
* Set to true to use analyze wildcard.
*/
public TextSimple analyzeWildcard(boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Set the locale.
*/
public TextSimple locale(String locale) {
this.locale = locale;
return this;
}
/**
* Set the lenient mode.
*/
public TextSimple lenient(boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* Set the minimum should match.
*/
public TextSimple minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Return lenient mode.
*/
public boolean isLenient() {
return lenient;
}
/**
* Return true to analyse wildcard.
*/
public boolean isAnalyzeWildcard() {
return analyzeWildcard;
}
/**
* Return lowercase expanded terms mode.
*/
public boolean isLowercaseExpandedTerms() {
return lowercaseExpandedTerms;
}
/**
* Return true if the default operator should be AND.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the analyzer to use.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the fields.
*/
public String[] getFields() {
return fields;
}
/**
* Return the locale.
*/
public String getLocale() {
return locale;
}
/**
* Return the flags.
*/
public String getFlags() {
return flags;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
}
@@ -0,0 +1,4 @@
/**
* Provides text search expressions like Match, TextQueryString etc.
*/
package io.ebean.search;
+4
View File
@@ -8,6 +8,7 @@ module io.ebean.api {
uses io.ebean.service.SpiRawSqlService;
uses io.ebean.service.SpiProfileLocationFactory;
uses io.ebean.service.SpiFetchGroupService;
uses io.ebean.service.SpiJsonService;
requires transitive java.sql;
requires transitive io.avaje.config;
@@ -30,10 +31,13 @@ module io.ebean.api {
exports io.ebean.config;
exports io.ebean.config.dbplatform;
exports io.ebean.event;
exports io.ebean.event.readaudit;
exports io.ebean.event.changelog;
exports io.ebean.common;
exports io.ebean.docstore;
exports io.ebean.plugin;
exports io.ebean.metric;
exports io.ebean.search;
exports io.ebean.service;
exports io.ebean.text;
exports io.ebean.text.json;
@@ -76,7 +76,7 @@ class DatabaseConfigTest {
props.setProperty("skipDataSourceCheck", "true");
props.setProperty("readOnlyDatabase", "true");
props.setProperty("lengthCheck", "ON");
props.setProperty("includeLabelInSql", "true");
props.setProperty("includeLabelInSql", "false");
props.setProperty("queryPlan.enable", "true");
props.setProperty("queryPlan.thresholdMicros", "10000");
@@ -97,7 +97,7 @@ class DatabaseConfigTest {
assertTrue(settings.isLoadModuleInfo());
assertTrue(settings.skipDataSourceCheck());
assertTrue(settings.readOnlyDatabase());
assertTrue(settings.isIncludeLabelInSql());
assertFalse(settings.isIncludeLabelInSql());
assertThat(settings.getLengthCheck()).isEqualTo(LengthCheck.ON);
assertTrue(settings.isIdGeneratorAutomatic());
@@ -183,7 +183,7 @@ class DatabaseConfigTest {
assertEquals(10000L, config.getQueryPlanCaptureMaxTimeMillis());
assertEquals(10, config.getQueryPlanCaptureMaxCount());
assertThat(config.getLengthCheck()).isEqualTo(LengthCheck.OFF);
assertFalse(config.isIncludeLabelInSql());
assertTrue(config.isIncludeLabelInSql());
config.setLoadModuleInfo(false);
assertFalse(config.isAutoLoadModuleInfo());
+26 -26
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<!-- platforms -->
@@ -193,79 +193,79 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
+39 -6
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.3.0</version>
<version>14.5.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,7 +22,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
@@ -46,7 +46,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.13.1</version>
</dependency>
<!--
@@ -147,21 +159,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>14.3.0</version>
<version>14.5.0</version>
<scope>test</scope>
</dependency>
@@ -199,6 +211,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Dnet.bytebuddy.experimental=true</argLine>
<useSystemClassLoader>false</useSystemClassLoader>
<trimStackTrace>false</trimStackTrace>
<failIfNoTests>false</failIfNoTests>
@@ -222,6 +235,26 @@
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<doctitle>Ebean 12</doctitle>
<overview>src/main/java/io/ebean/overview.html</overview>
<excludePackageNames>io.ebeaninternal.*:io.ebeanservice:io.ebean.common:io.ebean.bean:io.ebean.service:io.ebean.metric:io.ebean.util:io.ebean.config.properties:io.ebean.config.dbplatform</excludePackageNames>
<linksource>true</linksource>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -84,10 +84,17 @@ public final class LoadBeanRequest extends LoadRequest {
return idList;
}
public SpiQuery<?> createQuery(SpiEbeanServer server) {
final SpiQuery<?> query = server.createQuery(beanType());
query.usingTransaction(transaction);
configureQuery(query);
return query;
}
/**
* Configure the query for lazy loading execution.
*/
public void configureQuery(SpiQuery<?> query) {
private void configureQuery(SpiQuery<?> query) {
query.setMode(Mode.LAZYLOAD_BEAN);
query.setPersistenceContext(loadBuffer.persistenceContext());
query.setLoadDescription(mode(), description());
@@ -51,4 +51,8 @@ public interface LoadContext {
*/
void register(String path, BeanPropertyAssocMany<?> many, BeanCollection<?> bc);
/**
* Use soft-references for streaming queries, so unreachable entries can be garbage collected.
*/
void useReferences(boolean useReferences);
}
@@ -43,4 +43,5 @@ public interface LoadManyBuffer {
void configureQuery(SpiQuery<?> query);
boolean isUseDocStore();
}
@@ -94,6 +94,7 @@ public final class LoadManyRequest extends LoadRequest {
public SpiQuery<?> createQuery(SpiEbeanServer server) {
BeanPropertyAssocMany<?> many = many();
SpiQuery<?> query = many.newQuery(server);
query.usingTransaction(transaction);
String orderBy = many.lazyFetchOrderBy();
if (orderBy != null) {
query.orderBy(orderBy);
@@ -106,7 +107,7 @@ public final class LoadManyRequest extends LoadRequest {
}
query.setLazyLoadForParents(many);
final var pc = loadContext.persistenceContext();
many.addWhereParentIdIn(query, parentIdList(server, many, pc));
many.addWhereParentIdIn(query, parentIdList(server, many, pc), loadContext.isUseDocStore());
query.setPersistenceContext(pc);
query.setLoadDescription(lazy ? "lazy" : "query", description());
if (lazy) {
@@ -4,6 +4,8 @@ import io.avaje.lang.Nullable;
import io.ebean.*;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.CallOrigin;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetricVisitor;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiQuery.Type;
@@ -104,6 +106,11 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectio
*/
BeanDescriptor<?> descriptorById(String className);
/**
* Return BeanDescriptor using it's unique doc store queueId.
*/
BeanDescriptor<?> descriptorByQueueId(String queueId);
/**
* Return BeanDescriptors mapped to this table.
*/
@@ -188,6 +195,17 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectio
*/
boolean isSupportedType(java.lang.reflect.Type genericType);
/**
* Return the ReadAuditLogger to use for logging all read audit events.
*/
ReadAuditLogger readAuditLogger();
/**
* Return the ReadAuditPrepare used to populate the read audit events with
* user context information (user id, user ip address etc).
*/
ReadAuditPrepare readAuditPrepare();
/**
* Return the DataTimeZone to use when reading/writing timestamps via JDBC.
*/
@@ -3,6 +3,9 @@ package io.ebeaninternal.api;
import io.ebean.Expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.expression.DocQueryContext;
import java.io.IOException;
/**
@@ -18,6 +21,16 @@ public interface SpiExpression extends Expression {
*/
void simplify();
/**
* Write the expression as an elastic search expression.
*/
void writeDocQuery(DocQueryContext context) throws IOException;
/**
* Return the nested path for this expression.
*/
String nestedPath(BeanDescriptor<?> desc);
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
@@ -2,6 +2,7 @@ package io.ebeaninternal.api;
import io.ebean.ExpressionList;
import io.ebean.Junction;
import io.ebeaninternal.server.expression.DocQueryContext;
import java.io.IOException;
import java.util.List;
@@ -31,6 +32,11 @@ public interface SpiExpressionList<T> extends ExpressionList<T>, SpiExpression {
*/
boolean isEmpty();
/**
* Write the top level where expressions taking into account possible extra idEquals expression.
*/
void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException;
/**
* Apply firstRow maxRows limits on the filterMany query.
*/
@@ -1,10 +1,17 @@
package io.ebeaninternal.api;
import io.ebean.Junction;
import io.ebeaninternal.server.expression.DocQueryContext;
import java.io.IOException;
/**
* SPI methods for Junction.
*/
public interface SpiJunction<T> extends Junction<T> {
/**
* Write the Junction taking into account it is implied.
*/
void writeDocQueryJunction(DocQueryContext context) throws IOException;
}
@@ -11,6 +11,7 @@ import io.ebean.Query;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.plugin.BeanType;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
@@ -169,6 +170,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
SOFT_DELETED(false),
/**
* Query runs against draft tables.
*/
DRAFT(false),
/**
* Query runs against current data (normal).
*/
@@ -291,6 +297,17 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
SpiRawSql rawSql();
/**
* Return true if this query should be executed against the doc store.
*/
boolean isUseDocStore();
/**
* For doc store query return the document index name to search against.
* This is for partitioned indexes (like daily logstash indexes etc).
*/
String getDocIndexName();
/**
* Return the PersistenceContextScope that this query should use.
* <p>
@@ -365,6 +382,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
boolean isAsOfQuery();
/**
* Return true if this is a 'As Draft' query.
*/
boolean isAsDraft();
/**
* Return true if this query includes soft deleted rows.
*/
@@ -498,6 +520,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
NaturalKeyBindParam naturalKeyBindParam();
/**
* Prepare the query for docstore execution with nested paths.
*/
void prepareDocNested();
/**
* Set the query to be a delete query.
*/
@@ -712,6 +739,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
SpiExpressionList<T> havingExpressions();
/**
* Return the text expressions.
*/
SpiExpressionList<T> textExpression();
/**
* Returns true if either firstRow or maxRows has been set.
*/
@@ -778,6 +810,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
boolean tuneFetchProperties(OrmQueryDetail detail);
/**
* If this is a RawSql based entity set the default RawSql if not set.
*/
void setDefaultRawSqlIfRequired();
/**
* Set to true if this query has been tuned by autoTune.
*/
@@ -855,6 +892,21 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
int bufferFetchSizeHint();
/**
* Return true if read auditing is disabled on this query.
*/
boolean isDisableReadAudit();
/**
* Set the readEvent for future queries (as prepared in foreground thread).
*/
void setFutureFetchAudit(ReadEvent event);
/**
* Read the readEvent for future queries (null otherwise).
*/
ReadEvent futureFetchAudit();
/**
* Return the base table to use if user defined on the query.
*/
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import jakarta.persistence.PersistenceException;
import java.sql.Connection;
@@ -102,6 +103,19 @@ public interface SpiTransaction extends Transaction {
*/
Boolean isUpdateAllLoadedProperties();
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
* Returning 0 implies to use the system wide default batch size.
*/
DocStoreMode docStoreMode();
/**
* Return the batch size to us for ElasticSearch Bulk API calls
* as a result of this transaction.
*/
int getDocStoreBatchSize();
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
@@ -273,6 +287,11 @@ public interface SpiTransaction extends Transaction {
*/
void sendChangeLog(ChangeSet changeSet);
/**
* Return a document store transaction.
*/
DocStoreTransaction docStoreTransaction();
/**
* Set the current Tenant Id.
*/
@@ -338,4 +357,8 @@ public interface SpiTransaction extends Transaction {
*/
void postRollback(Throwable cause);
/**
* Set the transaction to be inactive via external transaction manager.
*/
void deactivateExternal();
}
@@ -2,12 +2,14 @@ package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import jakarta.persistence.PersistenceException;
import java.sql.Connection;
@@ -110,6 +112,32 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.tenantId();
}
@Override
public DocStoreTransaction docStoreTransaction() {
return transaction.docStoreTransaction();
}
@Override
public DocStoreMode docStoreMode() {
return transaction.docStoreMode();
}
@Override
public void setDocStoreMode(DocStoreMode mode) {
transaction.setDocStoreMode(mode);
}
@Override
public int getDocStoreBatchSize() {
return transaction.getDocStoreBatchSize();
}
@Override
public void setDocStoreBatchSize(int batchSize) {
transaction.setDocStoreBatchSize(batchSize);
}
@Override
public boolean isLogSql() {
return transaction.isLogSql();
@@ -419,4 +447,9 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
public void postRollback(Throwable cause) {
transaction.postRollback(cause);
}
@Override
public void deactivateExternal() {
transaction.deactivateExternal();
}
}
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.transaction.DeleteByIdMap;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import java.io.Serializable;
import java.util.ArrayList;
@@ -124,6 +125,18 @@ public final class TransactionEvent implements Serializable {
return changeSet;
}
/**
* Add any relevant PersistRequestBean's to DocStoreUpdates for later processing.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
List<PersistRequestBean<?>> requests = listenerNotify();
if (requests != null) {
for (PersistRequestBean<?> persistRequestBean : requests) {
persistRequestBean.addDocStoreUpdates(docStoreUpdates);
}
}
}
/**
* Return the CacheChangeSet that we add cache notification messages to.
* <p>
@@ -15,6 +15,7 @@ public final class CachedBeanData implements Externalizable {
private long whenCreated;
private long version;
private String discValue;
private Map<String, Object> data;
/**
* The sharable bean is effectively transient (near cache only).
@@ -24,9 +25,10 @@ public final class CachedBeanData implements Externalizable {
/**
* Construct from a loaded bean.
*/
public CachedBeanData(Object sharableBean, Map<String, Object> data, long version) {
public CachedBeanData(Object sharableBean, String discValue, Map<String, Object> data, long version) {
this.whenCreated = System.currentTimeMillis();
this.sharableBean = sharableBean;
this.discValue = discValue;
this.data = data;
this.version = version;
}
@@ -41,6 +43,11 @@ public final class CachedBeanData implements Externalizable {
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(version);
out.writeLong(whenCreated);
boolean hasDisc = discValue != null;
out.writeBoolean(hasDisc);
if (hasDisc) {
out.writeUTF(discValue);
}
out.writeInt(data.size());
for (Map.Entry<String, Object> entry : data.entrySet()) {
out.writeUTF(entry.getKey());
@@ -52,6 +59,9 @@ public final class CachedBeanData implements Externalizable {
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
version = in.readLong();
whenCreated = in.readLong();
if (in.readBoolean()) {
discValue = in.readUTF();
}
data = new LinkedHashMap<>();
int count = in.readInt();
for (int i = 0; i < count; i++) {
@@ -75,7 +85,7 @@ public final class CachedBeanData implements Externalizable {
Map<String, Object> copy = new HashMap<>();
copy.putAll(data);
copy.putAll(changes);
return new CachedBeanData(null, copy, version);
return new CachedBeanData(null, discValue, copy, version);
}
/**
@@ -92,6 +102,13 @@ public final class CachedBeanData implements Externalizable {
return version;
}
/**
* Return the raw discriminator value.
*/
public String getDiscValue() {
return discValue;
}
/**
* Return a sharable (immutable read only) bean. Near cache only use.
*/
@@ -41,7 +41,7 @@ public final class CachedBeanDataFromBean {
long version = desc.getVersion(bean);
EntityBean sharableBean = createSharableBean(desc, bean, ebi);
return new CachedBeanData(sharableBean, data, version);
return new CachedBeanData(sharableBean, desc.discValue(), data, version);
}
private static EntityBean createSharableBean(BeanDescriptor<?> desc, EntityBean bean, EntityBeanIntercept beanEbi) {
@@ -14,6 +14,9 @@ public final class CachedBeanDataToBean {
// any future lazy loading skips L2 bean cache
ebi.setLoadedFromCache(true);
BeanProperty idProperty = desc.idProperty();
if (desc.inheritInfo() != null) {
desc = desc.inheritInfo().readType(bean.getClass()).desc();
}
if (idProperty != null) {
// load the id property
loadProperty(bean, cacheBeanData, ebi, idProperty, context);
@@ -0,0 +1,54 @@
package io.ebeaninternal.server.cache;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Bean Id value plus discriminator type.
* <p>
* Put into L2 cache such that we know the type of a bean with inheritance.
*/
public final class CachedBeanId implements Externalizable {
private String discValue;
private Object id;
public CachedBeanId(String discValue, Object id) {
this.discValue = discValue;
this.id = id;
}
/**
* Construct from serialisation.
*/
public CachedBeanId() {
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(discValue);
out.writeObject(id);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
discValue = in.readUTF();
id = in.readObject();
}
@Override
public String toString() {
return discValue + ":" + id;
}
public String getDiscValue() {
return discValue;
}
public Object getId() {
return id;
}
}
@@ -126,9 +126,7 @@ final class DefaultBeanLoader {
if (loadRequest.checkEmpty()) {
throw new RuntimeException("Nothing in batch?");
}
final SpiQuery<?> query = server.createQuery(loadRequest.beanType());
query.usingTransaction(loadRequest.transaction());
loadRequest.configureQuery(query);
final SpiQuery<?> query = loadRequest.createQuery(server);
loadRequest.postLoad(executeQuery(loadRequest, query));
}
@@ -178,11 +176,12 @@ final class DefaultBeanLoader {
desc.contextPut(pc, id, bean);
ebi.setPersistenceContext(pc);
}
boolean draft = desc.isDraftInstance(bean);
if (embeddedOwnerIndex == -1) {
if (desc.lazyLoadMany(ebi)) {
return;
}
if (Mode.LAZYLOAD_BEAN == mode && desc.isBeanCaching()) {
if (!draft && Mode.LAZYLOAD_BEAN == mode && desc.isBeanCaching()) {
// lazy loading and the bean cache is active
if (desc.cacheBeanLoad(bean, ebi, id, pc)) {
return;
@@ -191,7 +190,9 @@ final class DefaultBeanLoader {
}
SpiQuery<?> query = server.createQuery(desc.type());
query.setLazyLoadProperty(ebi.lazyLoadProperty());
if (mode == SpiQuery.Mode.LAZYLOAD_BEAN && desc.isSoftDelete()) {
if (draft) {
query.asDraft();
} else if (mode == SpiQuery.Mode.LAZYLOAD_BEAN && desc.isSoftDelete()) {
query.setIncludeSoftDeletes();
}
if (embeddedOwnerIndex > -1) {
@@ -92,12 +92,16 @@ public final class DefaultContainer implements SpiContainer {
BootupClasses bootupClasses = bootupClasses(config);
boolean online = true;
TenantMode tenantMode = config.getTenantMode();
if (TenantMode.DB != tenantMode) {
setDataSource(config);
if (!tenantMode.isDynamicDataSource()) {
// check the autoCommit and Transaction Isolation
online = checkDataSource(config);
if (config.isDocStoreOnly()) {
config.databasePlatform(new DatabasePlatform());
} else {
TenantMode tenantMode = config.getTenantMode();
if (TenantMode.DB != tenantMode) {
setDataSource(config);
if (!tenantMode.isDynamicDataSource()) {
// check the autoCommit and Transaction Isolation
online = checkDataSource(config);
}
}
}
@@ -13,6 +13,8 @@ import io.ebean.config.*;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.BeanPersistController;
import io.ebean.event.ShutdownManager;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.*;
import io.ebean.migration.auto.AutoMigrationRunner;
import io.ebean.plugin.BeanType;
@@ -28,9 +30,11 @@ import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.dto.DtoBeanDescriptor;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.el.ElFilter;
import io.ebeaninternal.server.grammer.EqlParser;
import io.ebeaninternal.server.query.*;
import io.ebeaninternal.server.querydefn.*;
import io.ebeaninternal.server.rawsql.SpiRawSql;
@@ -39,6 +43,7 @@ import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.util.ParamTypeHelper;
import io.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import io.ebeanservice.docstore.api.DocStoreIntegration;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.NonUniqueResultException;
@@ -88,6 +93,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final DtoBeanManager dtoBeanManager;
private final BeanDescriptorManager descriptorManager;
private final AutoTuneService autoTuneService;
private final ReadAuditPrepare readAuditPrepare;
private final ReadAuditLogger readAuditLogger;
private final CQueryEngine cqueryEngine;
private final List<Plugin> serverPlugins;
private final SpiDdlGenerator ddlGenerator;
@@ -97,6 +104,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final DefaultBeanLoader beanLoader;
private final EncryptKeyManager encryptKeyManager;
private final SpiJsonContext jsonContext;
private final DocumentStore documentStore;
private final MetaInfoManager metaInfoManager;
private final CurrentTenantProvider currentTenantProvider;
private final SpiLogManager logManager;
@@ -138,12 +146,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.relationalQueryEngine = config.createRelationalQueryEngine();
this.dtoQueryEngine = config.createDtoQueryEngine();
this.autoTuneService = config.createAutoTuneService(this);
this.readAuditPrepare = config.getReadAuditPrepare();
this.readAuditLogger = config.getReadAuditLogger();
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
this.dataTimeZone = config.getDataTimeZone();
this.clockService = config.getClockService();
this.transactionManager = config.createTransactionManager(this);
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor());
this.documentStore = docStoreComponents.documentStore();
this.queryPlanManager = config.initQueryPlanManager(transactionManager);
this.metaInfoManager = new DefaultMetaInfoManager(this, this.config.getMetricNaming());
this.serverPlugins = config.getPlugins();
@@ -177,7 +189,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Execute all the plugins with an online flag indicating the DB is up or not.
*/
public void executePlugins(boolean online) {
ddlGenerator.execute(online);
if (!config.isDocStoreOnly()) {
ddlGenerator.execute(online);
}
for (Plugin plugin : serverPlugins) {
plugin.online(online);
}
@@ -269,6 +283,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return transactionManager.readOnlyDataSource();
}
@Override
public ReadAuditPrepare readAuditPrepare() {
return readAuditPrepare;
}
@Override
public ReadAuditLogger readAuditLogger() {
return readAuditLogger;
}
/**
* Run any initialisation required before registering with the ClusterManager.
*/
@@ -594,7 +618,25 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return (T) existing;
}
}
return (T) desc.contextRef(pc, null, false, id);
InheritInfo inheritInfo = desc.inheritInfo();
if (inheritInfo == null || inheritInfo.isConcrete()) {
return (T) desc.contextRef(pc, null, false, id);
}
return referenceFindOne(type, id, desc);
}
private <T> T referenceFindOne(Class<T> type, Object id, BeanDescriptor<?> desc) {
BeanProperty idProp = desc.idProperty();
if (idProp == null) {
throw new PersistenceException("No ID properties for this type? " + desc);
}
// we actually need to do a query because we don't know the type without the discriminator
// value, just select the id property and discriminator column (auto added)
T bean = find(type).select(idProp.name()).setId(id).findOne();
if (bean == null) {
throw new EntityNotFoundException("Could not find reference bean " + id + " for " + desc);
}
return bean;
}
@Override
@@ -796,6 +838,29 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return query;
}
@Override
public <T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery) {
BeanDescriptor<T> desc = desc(beanType);
String named = desc.namedQuery(namedQuery);
if (named != null) {
return createQuery(beanType, named);
}
SpiRawSql rawSql = desc.namedRawSql(namedQuery);
if (rawSql != null) {
DefaultOrmQuery<T> query = createQuery(beanType);
query.setRawSql(rawSql);
return query;
}
throw new PersistenceException("No named query called " + namedQuery + " for bean:" + beanType.getName());
}
@Override
public <T> DefaultOrmQuery<T> createQuery(Class<T> beanType, String eql) {
DefaultOrmQuery<T> query = createQuery(beanType);
EqlParser.parse(eql, query);
return query;
}
@Override
public <T> DefaultOrmQuery<T> createQuery(Class<T> beanType) {
return new DefaultOrmQuery<>(desc(beanType), this, expressionFactory);
@@ -812,6 +877,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultDtoQuery<>(this, descriptor, sql.trim());
}
@Override
public <T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.descriptor(dtoType);
String sql = descriptor.namedRawSql(namedQuery);
if (sql == null) {
throw new PersistenceException("No named query called " + namedQuery + " for bean:" + dtoType.getName());
}
return new DefaultDtoQuery<>(this, descriptor, sql);
}
@Override
public <T> DtoQuery<T> findDto(Class<T> dtoType, SpiQuery<?> ormQuery) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.descriptor(dtoType);
@@ -875,6 +950,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
transaction = currentServerTransaction();
}
if (!query.isRawSql()) {
query.setDefaultRawSqlIfRequired();
if (!query.isAutoTunable() || !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
@@ -957,6 +1033,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
SpiOrmQueryRequest<T> request = buildQueryRequest(query);
request.prepareQuery();
if (request.isUseDocStore()) {
return docStore().find(request);
}
try {
request.initTransIfRequired();
return (T) request.findId();
@@ -1225,6 +1304,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
spiQuery.usingFuture();
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
if (!spiQuery.isDisableReadAudit()) {
BeanDescriptor<T> desc = descriptorManager.descriptor(spiQuery.getBeanType());
desc.readAuditFutureList(spiQuery);
}
// Create a new transaction solely to execute the findList() at some future time
boolean createdTransaction = false;
SpiTransaction transaction = query.transaction();
@@ -1247,6 +1330,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (maxRows == 0) {
throw new PersistenceException("maxRows must be specified for findPagedList() query");
}
if (query.isUseDocStore()) {
return docStore().findPagedList(createQueryRequest(Type.LIST, query));
}
return new LimitOffsetPagedList<>(this, query);
}
@@ -1274,6 +1360,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> void findEach(SpiQuery<T> query, Consumer<T> consumer) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query);
if (request.isUseDocStore()) {
docStore().findEach(request, consumer);
return;
}
request.initTransIfRequired();
request.findEach(consumer);
// no try finally - findEach guarantee's cleanup of the transaction if required
@@ -1282,6 +1372,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> void findEach(SpiQuery<T> query, int batch, Consumer<List<T>> consumer) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query);
// if (request.isUseDocStore()) {
// docStore().findEach(request, consumer);
// return;
// }
request.initTransIfRequired();
request.findEach(batch, consumer);
// no try finally - findEach guarantee's cleanup of the transaction if required
@@ -1290,6 +1384,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> void findEachWhile(SpiQuery<T> query, Predicate<T> consumer) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query);
if (request.isUseDocStore()) {
docStore().findEachWhile(request, consumer);
return;
}
request.initTransIfRequired();
request.findEachWhile(consumer);
// no try finally - findEachWhile guarantee's cleanup of the transaction if required
@@ -1324,6 +1422,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (result != null) {
return (List<T>) result;
}
if (request.isUseDocStore()) {
return docStore().findList(request);
}
try {
request.initTransIfRequired();
return request.findList();
@@ -1592,6 +1693,54 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}, transaction);
}
@Override
public <T> List<T> publish(Query<T> query, @Nullable Transaction transaction) {
return executeInTrans((txn) -> persister.publish(query, txn), transaction);
}
@Nullable
@Override
public <T> T publish(Class<T> beanType, Object id) {
return publish(beanType, id, null);
}
@Override
public <T> List<T> publish(Query<T> query) {
return publish(query, null);
}
@Nullable
@Override
public <T> T publish(Class<T> beanType, Object id, @Nullable Transaction transaction) {
Query<T> query = find(beanType).setId(id);
List<T> liveBeans = publish(query, transaction);
return (liveBeans.size() == 1) ? liveBeans.get(0) : null;
}
@Override
public <T> List<T> draftRestore(Query<T> query, @Nullable Transaction transaction) {
return executeInTrans((txn) -> persister.draftRestore(query, txn), transaction);
}
@Nullable
@Override
public <T> T draftRestore(Class<T> beanType, Object id, @Nullable Transaction transaction) {
Query<T> query = find(beanType).setId(id);
List<T> beans = draftRestore(query, transaction);
return (beans.size() == 1) ? beans.get(0) : null;
}
@Nullable
@Override
public <T> T draftRestore(Class<T> beanType, Object id) {
return draftRestore(beanType, id, null);
}
@Override
public <T> List<T> draftRestore(Query<T> query) {
return draftRestore(query, null);
}
private EntityBean checkEntityBean(Object bean) {
return (EntityBean) Objects.requireNonNull(bean);
}
@@ -1900,6 +2049,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return descriptorManager.beanTypes(tableName);
}
@Override
public BeanType<?> beanTypeForQueueId(String queueId) {
return descriptorByQueueId(queueId);
}
@Override
public BeanDescriptor<?> descriptorByQueueId(String queueId) {
return descriptorManager.descriptorByQueueId(queueId);
}
/**
* Return the SPI bean types for the given bean class.
*/
@@ -2006,6 +2165,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return callStackFactory.createCallOrigin();
}
@Override
public DocumentStore docStore() {
return documentStore;
}
@Override
public JsonContext json() {
// immutable thread safe so return shared instance
@@ -0,0 +1,69 @@
package io.ebeaninternal.server.core;
import io.ebeaninternal.api.CoreLog;
import io.ebeaninternal.server.dto.DtoNamedQueries;
import io.ebeaninternal.xmapping.api.XmapDto;
import io.ebeaninternal.xmapping.api.XmapEbean;
import io.ebeaninternal.xmapping.api.XmapRawSql;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.System.Logger.Level.ERROR;
/**
* Reads the Xml deployment information.
*/
final class InternalConfigXmlMap {
private final List<XmapEbean> xmlEbeanList;
private final ClassLoader classLoader;
private final Map<Class<?>, DtoNamedQueries> dtoNamedQueries = new HashMap<>();
InternalConfigXmlMap(List<XmapEbean> xmlEbeanList, ClassLoader classLoader) {
this.xmlEbeanList = xmlEbeanList;
this.classLoader = classLoader;
initDtoMapping();
}
void initDtoMapping() {
if (xmlEbeanList != null) {
for (XmapEbean mapping : xmlEbeanList) {
List<XmapDto> dtoList = mapping.getDto();
for (XmapDto dto : dtoList) {
readDtoMapping(dto);
}
}
}
}
/**
* Return the XML deployment information for entity beans.
*/
List<XmapEbean> xmlDeployment() {
return xmlEbeanList;
}
/**
* Return the named queries for Dto beans.
*/
Map<Class<?>, DtoNamedQueries> readDtoMapping() {
return dtoNamedQueries;
}
private void readDtoMapping(XmapDto dto) {
Class<?> dtoClass;
try {
dtoClass = Class.forName(dto.getClazz(), false, classLoader);
} catch (Exception e) {
CoreLog.internal.log(ERROR, "Could not load dto bean class " + dto.getClazz() + " for ebean xml entry");
return;
}
DtoNamedQueries namedQueries = dtoNamedQueries.computeIfAbsent(dtoClass, aClass -> new DtoNamedQueries());
for (XmapRawSql sql : dto.getRawSql()) {
namedQueries.addRawSql(sql.getName(), sql.getQuery());
}
}
}
@@ -11,6 +11,8 @@ import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.*;
@@ -27,6 +29,7 @@ import io.ebeaninternal.server.core.timezone.*;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import io.ebeaninternal.server.deploy.parse.DeployInherit;
import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
@@ -39,10 +42,18 @@ import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.persist.platform.PostgresMultiValueBind;
import io.ebeaninternal.server.query.*;
import io.ebeaninternal.server.readaudit.DefaultReadAuditLogger;
import io.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
import io.ebeaninternal.server.json.DJsonContext;
import io.ebeaninternal.server.transaction.*;
import io.ebeaninternal.server.type.DefaultTypeManager;
import io.ebeaninternal.server.type.TypeManager;
import io.ebeaninternal.xmapping.api.XmapEbean;
import io.ebeaninternal.xmapping.api.XmapService;
import io.ebeanservice.docstore.api.DocStoreFactory;
import io.ebeanservice.docstore.api.DocStoreIntegration;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
import java.util.*;
@@ -61,6 +72,7 @@ public final class InternalConfiguration {
private final DatabaseBuilder.Settings config;
private final BootupClasses bootupClasses;
private final DatabasePlatform databasePlatform;
private final DeployInherit deployInherit;
private final TypeManager typeManager;
private final DtoBeanManager dtoBeanManager;
private final ClockService clockService;
@@ -77,6 +89,7 @@ public final class InternalConfiguration {
private final ExpressionFactory expressionFactory;
private final SpiBackgroundExecutor backgroundExecutor;
private final JsonFactory jsonFactory;
private final DocStoreFactory docStoreFactory;
private final List<Plugin> plugins = new ArrayList<>();
private final MultiValueBind multiValueBind;
private final SpiLogManager logManager;
@@ -93,6 +106,7 @@ public final class InternalConfiguration {
this.clockService = new ClockService(config.settings().getClock());
this.tableModState = new TableModState();
this.logManager = initLogManager();
this.docStoreFactory = initDocStoreFactory(service(DocStoreFactory.class));
this.jsonFactory = config.getJsonFactory();
this.clusterManager = clusterManager;
this.backgroundExecutor = backgroundExecutor;
@@ -101,24 +115,32 @@ public final class InternalConfiguration {
this.expressionFactory = initExpressionFactory(config);
this.typeManager = new DefaultTypeManager(config, bootupClasses);
this.multiValueBind = createMultiValueBind(databasePlatform.platform());
this.deployInherit = new DeployInherit(bootupClasses);
this.deployCreateProperties = new DeployCreateProperties(typeManager);
this.deployUtil = new DeployUtil(typeManager, config);
this.serverCachePlugin = initServerCachePlugin();
this.cacheManager = initCacheManager();
this.dtoBeanManager = new DtoBeanManager(typeManager);
final InternalConfigXmlMap xmlMap = initExternalMapping();
this.dtoBeanManager = new DtoBeanManager(typeManager, xmlMap.readDtoMapping());
this.beanDescriptorManager = new BeanDescriptorManager(this);
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy(xmlMap.xmlDeployment());
Map<String, String> draftTableMap = beanDescriptorManager.draftTableMap();
beanDescriptorManager.scheduleBackgroundTrim();
this.dataTimeZone = initDataTimeZone();
this.binder = getBinder(typeManager, databasePlatform, dataTimeZone);
this.cQueryEngine = new CQueryEngine(config, databasePlatform, binder, asOfTableMapping);
this.cQueryEngine = new CQueryEngine(config, databasePlatform, binder, asOfTableMapping, draftTableMap);
}
public boolean isJacksonCorePresent() {
return jacksonCorePresent;
}
private InternalConfigXmlMap initExternalMapping() {
final List<XmapEbean> xmEbeans = readExternalMapping();
return new InternalConfigXmlMap(xmEbeans, config.getClassLoadConfig().getClassLoader());
}
private <S> S service(Class<S> cls) {
S service = config.getServiceObject(cls);
if (service != null) {
@@ -128,6 +150,14 @@ public final class InternalConfiguration {
}
}
private List<XmapEbean> readExternalMapping() {
final XmapService xmapService = service(XmapService.class);
if (xmapService == null) {
return Collections.emptyList();
}
return xmapService.read(config.getClassLoadConfig().getClassLoader(), config.getMappingLocations());
}
private SpiLogManager initLogManager() {
// allow plugin - i.e. capture executed SQL for testing/asserts
SpiLoggerFactory loggerFactory = service(SpiLoggerFactory.class);
@@ -148,6 +178,17 @@ public final class InternalConfiguration {
return new DefaultExpressionFactory(config.isExpressionEqualsWithNullAsNoop(), nativeIlike);
}
private DocStoreFactory initDocStoreFactory(DocStoreFactory service) {
return service == null ? new NoneDocStoreFactory() : service;
}
/**
* Return the doc store factory.
*/
public DocStoreFactory getDocStoreFactory() {
return docStoreFactory;
}
ClockService getClockService() {
return clockService;
}
@@ -204,6 +245,22 @@ public final class InternalConfiguration {
return plugin((listener != null) ? listener : jacksonCorePresent ? new DefaultChangeLogListener() : null);
}
/**
* Return the ReadAuditLogger implementation to use.
*/
ReadAuditLogger getReadAuditLogger() {
ReadAuditLogger found = bootupClasses.getReadAuditLogger();
return plugin(found != null ? found : jacksonCorePresent ? new DefaultReadAuditLogger() : null);
}
/**
* Return the ReadAuditPrepare implementation to use.
*/
ReadAuditPrepare getReadAuditPrepare() {
ReadAuditPrepare found = bootupClasses.getReadAuditPrepare();
return plugin(found != null ? found : new DefaultReadAuditPrepare());
}
/**
* For 'As Of' queries return the number of bind variables per predicate.
*/
@@ -290,6 +347,10 @@ public final class InternalConfiguration {
return beanDescriptorManager;
}
public DeployInherit getDeployInherit() {
return deployInherit;
}
public DeployCreateProperties getDeployCreateProperties() {
return deployCreateProperties;
}
@@ -311,19 +372,29 @@ public final class InternalConfiguration {
return new GeneratedPropertyFactory(offlineMode, config, bootupClasses.getIdGenerators());
}
/**
* Create the DocStoreIntegration components for the given server.
*/
DocStoreIntegration createDocStoreIntegration(SpiServer server) {
return plugin(docStoreFactory.create(server));
}
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
TransactionManager createTransactionManager(SpiServer server) {
TransactionManager createTransactionManager(SpiServer server, DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionScopeManager scopeManager = createTransactionScopeManager();
boolean notifyL2CacheInForeground = cacheManager.isLocalL2Caching() || config.isNotifyL2CacheInForeground();
TransactionManagerOptions options =
new TransactionManagerOptions(server, notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
beanDescriptorManager, dataSource(), profileHandler(), logManager,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
if (config.isDocStoreOnly()) {
return new DocStoreTransactionManager(options);
}
return new TransactionManager(options);
}
@@ -114,6 +114,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return server.databasePlatform().escapeLikeString(value);
}
@Override
public void executeSecondaryQueries(boolean forEach) {
// disable lazy loading leaves loadContext null
if (loadContext != null) {
@@ -147,6 +148,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return loadContext;
}
@Override
public boolean isUseDocStore() {
return query.isUseDocStore();
}
/**
* Run BeanQueryAdapter preQuery() if needed.
*/
@@ -219,6 +225,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
persistenceContext.beginIterate();
}
loadContext = new DLoadContext(this, secondaryQueries);
loadContext.useReferences(Type.ITERATE == query.type());
}
/**
@@ -241,6 +248,23 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
}
/**
* Return the JsonReadOptions taking into account lazy loading and persistence context.
*/
@Override
public JsonReadOptions createJsonReadOptions() {
persistenceContext = persistenceContext(query, transaction);
if (query.persistenceContext() == null) {
query.setPersistenceContext(persistenceContext);
}
JsonReadOptions jsonRead = new JsonReadOptions();
jsonRead.setPersistenceContext(persistenceContext);
if (!query.isDisableLazyLoading()) {
loadContext = new DLoadContext(this, secondaryQueries);
jsonRead.setLoadContext(loadContext);
}
return jsonRead;
}
/**
* Get the TransactionContext either explicitly set on the query or
@@ -615,6 +639,17 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
Object cached = beanDescriptor.queryCacheGet(cacheKey);
if (cached != null && isAuditReads() && readAuditQueryType()) {
if (cached instanceof BeanCollection) {
// raw sql can't use L2 cache so normal queries only in here
Collection<T> actualDetails = ((BeanCollection<T>) cached).actualDetails();
List<Object> ids = new ArrayList<>(actualDetails.size());
for (T bean : actualDetails) {
ids.add(beanDescriptor.idForJson(bean));
}
beanDescriptor.readAuditMany(queryPlanKey.partialKey(), "l2-query-cache", ids);
}
}
if (Boolean.FALSE.equals(query.isReadOnly())) {
// return shallow copies if readonly is explicitly set to false
if (cached instanceof BeanCollection) {
@@ -630,6 +665,24 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return cached;
}
/**
* Return true if the query type contains bean data (not just ids etc) and hence we want to include
* it in read auditing. Return false for row count and find ids queries.
*/
private boolean readAuditQueryType() {
Type type = query.type();
switch (type) {
case BEAN:
case ITERATE:
case LIST:
case SET:
case MAP:
return true;
default:
return false;
}
}
public void putToQueryCache(Object result) {
beanDescriptor.queryCachePut(cacheKey, new QueryCacheEntry(result, dependentTables, transaction.startNanoTime()));
}
@@ -656,6 +709,15 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return (batchSize > 0) ? batchSize : server.lazyLoadBatchSize();
}
/**
* Return true if read auditing is on for this query request.
* <p>
* This means that read audit is on for this bean type and that query has not explicitly disabled it.
*/
public boolean isAuditReads() {
return beanDescriptor.isReadAuditing() && !query.isDisableReadAudit();
}
/**
* Return the base table alias for this query.
*/
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
import io.ebean.InsertOptions;
import io.ebean.ValuePair;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PreGetterCallback;
@@ -16,16 +17,21 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.persist.*;
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
import io.ebeanservice.docstore.api.DocStoreUpdate;
import io.ebeanservice.docstore.api.DocStoreUpdateContext;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.OptimisticLockException;
import jakarta.persistence.PersistenceException;
import java.io.IOException;
import java.sql.Statement;
import java.util.*;
/**
* PersistRequest for insert update or delete of a bean.
*/
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T>, PreGetterCallback, SpiProfileTransactionEvent {
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T>, DocStoreUpdate, PreGetterCallback, SpiProfileTransactionEvent {
private final BeanManager<T> beanManager;
private final BeanDescriptor<T> beanDescriptor;
@@ -39,8 +45,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private final Object parentBean;
private final boolean dirty;
private final boolean publish;
private int flags;
private boolean saveRecurse;
private DocStoreMode docStoreMode;
private final ConcurrencyMode concurrencyMode;
/**
* The unique id used for logging summary.
@@ -131,6 +139,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.parentBean = parentBean;
this.controller = beanDescriptor.persistController();
this.type = type;
this.docStoreMode = calcDocStoreMode(transaction, type);
this.flags = flags;
if (Flags.isRecurse(flags)) {
this.persistCascade = t.isPersistCascade();
@@ -146,6 +155,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
this.concurrencyMode = beanDescriptor.concurrencyMode(intercept);
this.publish = Flags.isPublish(flags);
if (isMarkDraftDirty(publish)) {
beanDescriptor.setDraftDirty(entityBean, true);
}
this.dirty = intercept.isDirty();
}
@@ -174,11 +187,29 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
profileBase(type.profileEventId, offset, beanDescriptor.name(), flushCount);
}
/**
* Return the document store event that should be used for this request.
* <p>
* Used to check if the Transaction has set the mode to IGNORE when doing large batch inserts that we
* don't want to send to the doc store.
*/
private DocStoreMode calcDocStoreMode(SpiTransaction txn, Type type) {
DocStoreMode txnMode = (txn == null) ? null : txn.docStoreMode();
return beanDescriptor.docStoreMode(type, txnMode);
}
@Override
public boolean isCascade() {
return Flags.isRecurse(flags);
}
/**
* Return true if the draftDirty property should be set to true for this request.
*/
private boolean isMarkDraftDirty(boolean publish) {
return !publish && type != Type.DELETE && beanDescriptor.isDraftable();
}
/**
* Set the transaction from prior persist request.
* Only used when hard deleting draft & associated live beans.
@@ -193,7 +224,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Init the transaction and also check for batch on cascade escalation.
*/
public void initTransIfRequiredWithBatchCascade() {
createImplicitTransIfRequired();
if (createImplicitTransIfRequired()) {
docStoreMode = calcDocStoreMode(transaction, type);
}
checkBatchEscalationOnCascade();
}
@@ -394,14 +427,21 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Set the cache notify status.
*/
private void setNotifyCache() {
this.notifyCache = beanDescriptor.isCacheNotify(type);
this.notifyCache = beanDescriptor.isCacheNotify(type, publish);
}
/**
* Return true if this change should notify persist listener or doc store (and keep the request).
*/
private boolean isNotifyListeners() {
return isNotifyPersistListener();
return isNotifyPersistListener() || isDocStoreNotify();
}
/**
* Return true if this request should update the document store.
*/
private boolean isDocStoreNotify() {
return docStoreMode != DocStoreMode.IGNORE;
}
private boolean isNotifyPersistListener() {
@@ -430,6 +470,46 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Process the persist request updating the document store.
*/
@Override
public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException {
switch (type) {
case INSERT:
beanDescriptor.docStoreInsert(idValue, this, txn);
break;
case UPDATE:
case DELETE_SOFT:
beanDescriptor.docStoreUpdate(idValue, this, txn);
break;
case DELETE:
beanDescriptor.docStoreDeleteById(idValue, txn);
break;
default:
throw new IllegalStateException("Invalid type " + type);
}
}
/**
* Add this event to the queue entries in IndexUpdates.
*/
@Override
public void addToQueue(DocStoreUpdates docStoreUpdates) {
switch (type) {
case INSERT:
case UPDATE:
case DELETE_SOFT:
docStoreUpdates.queueIndex(beanDescriptor.docStoreQueueId(), idValue);
break;
case DELETE:
docStoreUpdates.queueDelete(beanDescriptor.docStoreQueueId(), idValue);
break;
default:
throw new IllegalStateException("Invalid type " + type);
}
}
public void addToPersistMap(BeanPersistIdMap beanPersistMap) {
beanPersistMap.add(beanDescriptor, type, idValue);
}
@@ -572,6 +652,28 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return beanDescriptor.createRef(beanId(), null);
}
/**
* Return true if the bean type is a Draftable.
*/
public boolean isDraftable() {
return beanDescriptor.isDraftable();
}
/**
* Return true if this request is a hard delete of a draftable bean.
* If this is true Ebean is expected to auto-publish and delete the associated live bean.
*/
public boolean isHardDeleteDraft() {
if (type == Type.DELETE && beanDescriptor.isDraftable() && !beanDescriptor.isDraftableElement()) {
// deleting a top level draftable bean
if (beanDescriptor.isLiveInstance(entityBean)) {
throw new PersistenceException("Explicit Delete is not allowed on a 'live' bean - only draft beans");
}
return true;
}
return false;
}
/**
* Return true if this was a hard/permanent delete request (and should cascade as such).
*/
@@ -579,6 +681,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return (type == Type.DELETE && beanDescriptor.isSoftDelete());
}
/**
* Checks for @Draftable entity beans with @Draft property that the bean is a 'draft'.
* Save or Update is not allowed to execute using 'live' beans - must use publish().
*/
public void checkDraft() {
if (beanDescriptor.isDraftable() && beanDescriptor.isLiveInstance(entityBean)) {
throw new PersistenceException("Save or update is not allowed on a 'live' bean - only draft beans");
}
}
/**
* Return the parent bean for cascading save with unidirectional relationship.
*/
@@ -764,7 +876,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
setNotifyCache();
boolean isChangeLog = beanDescriptor.isChangeLog();
if (type == Type.UPDATE) {
// get the dirty properties for update notification to the doc store
// get the dirty properties for notify cache & orphanRemoval of vanilla collection detection
dirtyProperties = intercept.dirtyProperties();
}
if (isChangeLog) {
@@ -832,19 +944,20 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
private void logSummaryMessage() {
String draft = (beanDescriptor.isDraftable() && !publish) ? " draft[true]" : "";
String name = beanDescriptor.name();
switch (type) {
case INSERT:
transaction.logSummary("Inserted [{0}] [{1}]", name, (idValue == null ? "" : idValue));
transaction.logSummary("Inserted [{0}] [{1}]{2}", name, (idValue == null ? "" : idValue), draft);
break;
case UPDATE:
transaction.logSummary("Updated [{0}] [{1}]", name, idValue);
transaction.logSummary("Updated [{0}] [{1}]{2}", name, idValue , draft);
break;
case DELETE:
transaction.logSummary("Deleted [{0}] [{1}]", name, idValue);
transaction.logSummary("Deleted [{0}] [{1}]{2}", name, idValue , draft);
break;
case DELETE_SOFT:
transaction.logSummary("SoftDelete [{0}] [{1}]", name, idValue);
transaction.logSummary("SoftDelete [{0}] [{1}]{2}", name, idValue , draft);
break;
default:
break;
@@ -882,6 +995,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private void postInsert() {
// mark all properties as loaded after an insert to support immediate update
beanDescriptor.setAllLoaded(entityBean);
if (!publish) {
beanDescriptor.setDraft(entityBean);
}
if (transaction.isAutoPersistUpdates() && idValue != null) {
// with getGeneratedKeys off we will not have a idValue
beanDescriptor.contextPut(transaction.persistenceContext(), idValue, entityBean);
@@ -978,6 +1094,32 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* For requests that update document store add this event to either the list
* of queue events or list of update events.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
if (type == Type.UPDATE) {
beanDescriptor.docStoreUpdateEmbedded(this, docStoreUpdates);
}
switch (docStoreMode) {
case UPDATE: {
docStoreUpdates.addPersist(this);
return;
}
case QUEUE: {
if (type == Type.DELETE) {
docStoreUpdates.queueDelete(beanDescriptor.docStoreQueueId(), idValue);
} else {
docStoreUpdates.queueIndex(beanDescriptor.docStoreQueueId(), idValue);
}
}
break;
default:
break;
}
}
/**
* Determine if all loaded properties should be used for an update.
* <p>
@@ -1003,6 +1145,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return flags;
}
/**
* Return true if this request is a 'publish' action.
*/
public boolean isPublish() {
return publish;
}
/**
* Return the key for an update persist request.
*/
@@ -1019,14 +1168,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
key.append('v');
}
}
if (publish) {
key.append('p');
}
return key.toString();
}
/**
* Return the table to update.
* Return the table to update depending if the request is a 'publish' one or normal.
*/
public String updateTable() {
return beanDescriptor.baseTable();
return publish ? beanDescriptor.baseTable() : beanDescriptor.draftTable();
}
/**
@@ -1095,6 +1247,30 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return 0;
}
/**
* Persist to the document store now (via buffer, not post commit).
*/
public void docStorePersist() {
idValue = beanDescriptor.getId(entityBean);
if (type == Type.UPDATE) {
dirtyProperties = intercept.dirtyProperties();
}
// processing now so set IGNORE (unlike DB + DocStore processing with post-commit)
docStoreMode = DocStoreMode.IGNORE;
try {
docStoreUpdate(transaction.docStoreTransaction().obtain());
postExecute();
if (type == Type.UPDATE
&& beanDescriptor.isDocStoreEmbeddedInvalidation()
&& transaction.isPersistCascade()) {
// queue embedded/nested updates for later processing
beanDescriptor.docStoreUpdateEmbedded(this, transaction.docStoreTransaction().queue());
}
} catch (IOException e) {
throw new PersistenceException("Error persisting doc store bean", e);
}
}
/**
* Use a common 'now' value across both when created and when updated etc.
*/
@@ -1214,7 +1390,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Return the SQL used to fetch the last inserted id value.
*/
public String selectLastInsertedId() {
return beanDescriptor.selectLastInsertedId();
return beanDescriptor.selectLastInsertedId(publish);
}
/**
@@ -78,6 +78,16 @@ public interface Persister {
*/
int executeCallable(CallableSql callable, Transaction t);
/**
* Publish the draft beans matching the given query.
*/
<T> List<T> publish(Query<T> query, Transaction transaction);
/**
* Restore the draft beans back to the matching live beans.
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
/**
* Visit the metrics.
*/
@@ -5,6 +5,7 @@ import io.ebean.Version;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeanservice.docstore.api.DocQueryRequest;
import java.util.Collection;
import java.util.List;
@@ -16,7 +17,7 @@ import java.util.function.Predicate;
/**
* Defines the ORM query request api.
*/
public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T> {
public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequest<T> {
/**
* Return the query.
@@ -177,6 +178,11 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T> {
*/
void markNotQueryOnly();
/**
* Return true if this query is expected to use the doc store.
*/
boolean isUseDocStore();
/**
* Return true if delete by statement is allowed for this type given cascade rules etc.
*/
@@ -9,6 +9,8 @@ import io.ebean.event.*;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.CoreLog;
@@ -66,10 +68,14 @@ public class BootupClasses implements Predicate<Class<?>> {
private Class<? extends ChangeLogPrepare> changeLogPrepareClass;
private Class<? extends ChangeLogListener> changeLogListenerClass;
private Class<? extends ChangeLogRegister> changeLogRegisterClass;
private Class<? extends ReadAuditPrepare> readAuditPrepareClass;
private Class<? extends ReadAuditLogger> readAuditLoggerClass;
private ChangeLogPrepare changeLogPrepare;
private ChangeLogListener changeLogListener;
private ChangeLogRegister changeLogRegister;
private ReadAuditPrepare readAuditPrepare;
private ReadAuditLogger readAuditLogger;
public BootupClasses() {
}
@@ -167,11 +173,19 @@ public class BootupClasses implements Predicate<Class<?>> {
}
public void addChangeLogInstances(DatabaseBuilder.Settings config) {
readAuditPrepare = config.getReadAuditPrepare();
readAuditLogger = config.getReadAuditLogger();
changeLogPrepare = config.getChangeLogPrepare();
changeLogListener = config.getChangeLogListener();
changeLogRegister = config.getChangeLogRegister();
// if not already set create the implementations found
// via classpath scanning
if (readAuditPrepare == null && readAuditPrepareClass != null) {
readAuditPrepare = create(readAuditPrepareClass, false);
}
if (readAuditLogger == null && readAuditLoggerClass != null) {
readAuditLogger = create(readAuditLoggerClass, false);
}
if (changeLogPrepare == null && changeLogPrepareClass != null) {
changeLogPrepare = create(changeLogPrepareClass, false);
}
@@ -238,6 +252,14 @@ public class BootupClasses implements Predicate<Class<?>> {
return changeLogRegister;
}
public ReadAuditPrepare getReadAuditPrepare() {
return readAuditPrepare;
}
public ReadAuditLogger getReadAuditLogger() {
return readAuditLogger;
}
public List<IdGenerator> getIdGenerators() {
return createAdd(idGeneratorInstances, idGeneratorCandidates);
}
@@ -402,6 +424,15 @@ public class BootupClasses implements Predicate<Class<?>> {
interesting = true;
}
if (ReadAuditPrepare.class.isAssignableFrom(cls)) {
readAuditPrepareClass = (Class<? extends ReadAuditPrepare>) cls;
interesting = true;
}
if (ReadAuditLogger.class.isAssignableFrom(cls)) {
readAuditLoggerClass = (Class<? extends ReadAuditLogger>) cls;
interesting = true;
}
return interesting;
}

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