Compare commits

..
61 Commits
Author SHA1 Message Date
Rob Bygrave 367eba8685 Version 16.4.0 2026-04-10 08:03:12 +12:00
Rob BygraveandGitHub be542635a5 Bump ebean-test-containers to 8.0 (major bump to mark the port fix) (#3742)
Docker changed and that broke how ebean-test-containers detected the
currently assigned port for a container. That was fixed in 7.18 but
thinking its a good idea to mark that relatively important bug fix
with a bump of the major version to 8.0.

Everyone using ebean-test-containers should consider updating the
ebean-test-containers dependency to 8.0 or 7.18 (both have the fix).
2026-04-10 08:00:04 +12:00
Rob BygraveandGitHub 6e9d0a91e0 Fix for @Column on timestamp defined as timestamp(255) (#3740)
As per https://github.com/ebean-orm/ebean/discussions/3720

```
  @Column
  ZonedDateTime zonedDateTime2;
```
... resulting in DDL generated as `timestamp(255)`. This is
occurring when the JPA dependency is used like:
jakarta.persistence:jakarta.persistence-api:3.2.0
rather than using the transitive dependency that ebean includes.

Workaround:
Remove the jakarta.persistence:jakarta.persistence-api:3.2.0 dependency.

Fix:
The fix here is to use a timestamp type that has a maximum precision.
When a precision/length is specified greater than the maximum precision
then the fallback type is used which is `timestamp` without any precision.
2026-04-10 00:46:34 +12:00
Rob BygraveandGitHub a99ef3ebf2 Bump ebean-datasource dependency to 10.4, resets metrics on initialisation (#3739) 2026-04-09 22:23:55 +12:00
19afa845d1 FEATURE: inTuples() expressions support for natural key cache lookup (#3732)
* `inTuples()` support for natural key cache lookup

* Simplify NaturalKeyEntryBasic.addInPairs()

---------

Co-authored-by: Rob Bygrave <robin.bygrave@gmail.com>
2026-04-09 19:46:33 +12:00
Rob BygraveandGitHub 239900cd3b Bump ebean-agent to 16.4.0 with support for Java 26 (#3738) 2026-04-09 19:15:36 +12:00
robin.bygrave 73ac39971c Update test TestErrorBindLog for DB2 2026-04-08 22:55:41 +12:00
robin.bygrave 3a876252ec Update test TestQueryJoinOnFormula, restrict platforms for specific test
Restrict the new test_findCount_formulaJoin_subqueryWithOrderBy_issue3686
to H2 and Postgres for now. Not supported on Oracle and SQL Server.
2026-04-08 22:33:06 +12:00
robin.bygrave 231b52ba88 Update test TestErrorBindLog to improve failure message 2026-04-08 22:27:46 +12:00
robin.bygrave 808019cf3d Modify tests, move setRegister(false) setDefaultServer(false) before loadFromProperties() 2026-04-08 22:15:11 +12:00
Rob Bygrave 364520455f Add some logging for test BeanPersistControllerTest 2026-04-08 08:58:38 +12:00
Rob BygraveandGitHub e92621a489 Try old ebean agent, for strange CI build issue (#3737) 2026-04-08 08:51:33 +12:00
Rob BygraveandGitHub ccd1b7b8ec Bump ebean-test-containers to 7.18 (#3736) 2026-04-08 08:31:02 +12:00
thomas-lcdpandGitHub d144273307 ebean-core#3686: use parenthesis-aware ORDER BY removal in buildRowCountQuery to avoid breaking nested subqueries (#3729) 2026-03-27 23:18:15 +13:00
Rob BygraveandGitHub 41c5ebdcd7 Bump ebean-agent with ASM 9.9.1 and Java 26 support (#3731) 2026-03-27 23:15:00 +13:00
Rob BygraveandGitHub 9e711efecb Merge pull request #3730 from ebean-orm/feature/add-docs-for-graalvm-support
Add GraalVM native image support documentation
2026-03-27 22:06:00 +13:00
robin.bygrave ef7fd76f14 Add GraalVM native image support documentation 2026-03-27 22:05:21 +13:00
robin.bygrave b7e3ddbedd Add GraalVM native image badge to README.md 2026-03-27 21:55:29 +13:00
Rob Bygrave 69c932816a Version 16.3.0 2026-02-16 21:07:49 +13:00
Rob Bygrave a1facf527b Bump ebean-agent and avaje junit 2026-02-16 21:03:37 +13:00
Rob BygraveandGitHub ec42ebf66d Bump ebean-datasource to 10.3 (#3727)
This version has moved the connection initialisation that sets the
autoCommit mode to be after the clientInfo and any initial sql has
been executed
2026-02-16 20:24:58 +13:00
Rob Bygrave 5dede70801 Fix for #3722 Remove isSkipCacheExplicit() and use isSkipCache()
This change is to reduce PUTs causing cache pollution where
changes that are rolled back are PUT into the bean cache.

Effectively remove the isSkipCacheExplicit() feature and just
use the existing isSkipCache(). This means that skipCacheAfterWrite
is used and PUTs after a database write/insert/update/delete are
effectively skipped.
2026-02-16 19:49:56 +13:00
Rob BygraveandGitHub b2f02ecc1f Fix for #3723 FilterMany predicates added to incorrect join clause (#3725)
The change that moved the filterMany predicates to the join had an
issue that extra joins may be needed to support those predicates.

Prior to this change, the extra predicates where effectively just
added to the end of the joins, rather than as a "child" node in
the SqlTreeNode tree.

This fix is in SqlTreeBuilder, where the list of top level extra
joins are first tried to be added as a child to a parent node, and
only if we can't find the parent added at the end.
2026-02-15 21:47:17 +13:00
15fa7cd1c2 Bump org.assertj:assertj-core from 3.27.6 to 3.27.7 in /ebean-test (#3718)
Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.6 to 3.27.7.
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.6...assertj-build-3.27.7)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-version: 3.27.7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-15 10:02:49 +13:00
Rob BygraveandGitHub 8dced38cd5 Bump kotlin maven plugin version in build (#3724)
* Bump kotlin maven plugin version in build

* For build bump kotlin-maven-plugin version to 2.3.10
2026-02-15 09:59:16 +13:00
Rob Bygrave 7addba7c93 Version 16.2.1 2026-02-02 23:22:22 +13:00
Rob BygraveandGitHub 00b45c1642 For query usingMaster support passing a boolean (#3721)
This is to support the use case where other logic is used to determine if a query
should be "forced" to use the master data source or not, and so it's easier to
pass that as a boolean to the usingMaster() method.
2026-02-02 23:17:37 +13:00
Rob Bygrave a3d9711fe6 Version 16.2.0 2025-11-23 22:18:54 +13:00
Rob Bygrave 1239c50723 Bump ebean-agent to 16.2.0 2025-11-23 22:16:29 +13:00
Rob BygraveandGitHub d85d8449e2 #3695 Behaviour change: findSingleAttribute() to throw when multiple … (#3697)
* #3695 Behaviour change: findSingleAttribute() to throw when multiple rows returned by query

On the plus, this makes the behaviour consistent with all the other findOne() methods (ORM, DTO, SqlQuery) and this very much feels like buggy behaviour [to just get the first result and ignore any subsequent results in the ResultSet]

On the negative of merging this bug fix, any application code relying on the existing behaviour with this fix will break and throw a NonUniqueResultException at RUNTIME (not ideal). However, for these cases where the application now throws an exception, people might not be aware that they were relying on this behaviour and this exception could be useful to highlight that (a potential non-deterministic query result was being used and a potential source of bugs was being).

* Change SqlQuery + Mapper + findOne() also to throw NonUniqueResultException
2025-11-23 22:14:44 +13:00
baf71cbab3 Bump com.microsoft.sqlserver:mssql-jdbc in /ebean-test (#3694)
Bumps [com.microsoft.sqlserver:mssql-jdbc](https://github.com/Microsoft/mssql-jdbc) from 10.2.0.jre8 to 11.1.0.jre8-preview.
- [Release notes](https://github.com/Microsoft/mssql-jdbc/releases)
- [Changelog](https://github.com/microsoft/mssql-jdbc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Microsoft/mssql-jdbc/commits)

---
updated-dependencies:
- dependency-name: com.microsoft.sqlserver:mssql-jdbc
  dependency-version: 11.1.0.jre8-preview
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-23 21:01:00 +13:00
Rob BygraveandGitHub ff0ca35e54 #3701 Improve error message when missing dependency ebean-jackson-mapper (#3708) 2025-11-23 20:25:16 +13:00
0fc9a1ac2f Bug fix and change filterMany to put predicates into JOIN clause, Fix for #3626 (#3707)
* Problem with filterMany

* Missing id

* Other test

* Fix

* Update TestQueryFilterMany.java

* #3706 Bug fix and change filterMany to put predicates into JOIN clause, Fix for #3626

* Fix tests for Postgres, Oracle, Sql Server

---------

Co-authored-by: Roman Parshikov <promansew@gmail.com>
2025-11-23 20:20:13 +13:00
Rob BygraveandGitHub f7b4edb193 Merge pull request #3704 from ebean-orm/feature/bump-test-deps-bytebuddy
Bump test dependencies byte buddy and assertj
2025-11-20 09:35:09 +13:00
robin.bygrave 284c8d4d21 Bump test dependencies byte buddy and assertj 2025-11-20 09:23:39 +13:00
Rob BygraveandGitHub 59a5c5f9bd Bump dependency avaje-config to 4.2 (#3699) 2025-11-11 07:59:40 +13:00
Rob BygraveandGitHub 5b04d6eca3 #3551 Log warning for use of mapping column to Class (#3698)
I think it was a mistake for Ebean to support Class<?> from a security perspective. Instead, Ebean should just use a String <-> Varchar and leave if up to the application to take that String and convert it to a class [and then that potential Class initialisation is owned by the application code and all security considerations around that are owned by the application code].
2025-11-11 07:57:15 +13:00
d5547808a2 PGvector support (#3696)
* Add pgvector-types module.

* Add missing binder definitions.
Add missing PGbit type registration.
Few tests.

* Add cached bean test.

* Prefer final classes, minor formating only

---------

Co-authored-by: Rob Bygrave <robin.bygrave@gmail.com>
2025-11-09 16:55:51 +13:00
Rob Bygrave 5606c95de5 Version 16.1.1 2025-10-22 22:45:21 +13:00
Rob Bygrave 4c2bd408b0 Bump ebean-agent 2025-10-22 22:42:00 +13:00
Rob Bygrave db76d5c27c Test only - add Postgres JSONB raw() expression example 2025-10-22 22:03:09 +13:00
Rob Bygrave f92c2f8c14 Test only - add test for string id InsertOnConflict 2025-10-22 22:02:40 +13:00
Rob BygraveandGitHub 51c3065419 3683 - Add AggregateFormulaContext to support group_concat etc (#3691)
- Add AggregateFormulaContext with ability to override the default via DatabaseBuilder
- Add group_concat, string_agg, listagg to set of known aggregation functions
2025-10-21 22:54:55 +13:00
Rob Bygrave e46dd66292 Test only - bump to Postgres 17 for PostgresReadOnlyDatabaseTest 2025-10-21 20:55:52 +13:00
Rob Bygrave 3e1451bee7 Merge branch 'master' of github.com:ebean-orm/ebean 2025-10-21 20:54:23 +13:00
Rob BygraveandGitHub 4ac3c4ddb1 3682 @Aggregation is applied to query if entity also has a @lob column (#3689)
* Version 16.1.0

* #3682 @Aggregation is applied to query if entity also has a @Lob column

The underling issue was that Aggregation columns should have been marked as default LAZY. This impact was only hit when @Lob column are included which by default lazy fetched, and that means the default select clause determined.
2025-10-20 20:55:18 +13:00
Rob Bygrave ee8e48902b Merge branch 'master' of github.com:ebean-orm/ebean 2025-10-19 18:07:18 +13:00
Rob BygraveandGitHub c37e752ace #3681 javadoc query cache (#3687)
* Version 16.1.0

* Improve javadoc for Query setUseQueryCache() and unmodifiable

Since 16.x a query using the query cache returns unmodifiable object graphs.
2025-10-14 20:04:38 +13:00
Rob Bygrave c9a6f822ac Version 16.1.0 2025-09-19 18:48:28 +12:00
Rob BygraveandGitHub 2231ef9fd1 Bump ebean-agent to 16.1.0 with Java 25 support (#3680)
Note that the IntelliJ plugin has been updated and published. It should be public in a few days.
2025-09-19 16:25:24 +12:00
Rob Bygrave 3ff1905318 Bump ebean-test-containers to 7.15 with updated ClickHouse support 2025-09-19 16:25:00 +12:00
Rob BygraveandGitHub 9b4fecff96 Add support for AutoCommit true transactions (#3679)
* Add support for AutoCommit true transactions

* Update DocStoreTransactionManager
2025-09-19 16:22:59 +12:00
Rob BygraveandGitHub 6f356fd55e #3674 Add @Nullable to Query getId(), getForUpdateLockWait(), getForUpdateLockType() (#3675)
These methods return nullable results, add JSpecify `@Nullable` to correctly indicate this.
2025-09-06 17:22:13 +12:00
Rob BygraveandGitHub 591f260f5b Retain filterMany expression when select fetchGroup applied after filterMany (#3673) 2025-09-02 22:35:43 +12:00
Rob Bygrave b361b10571 Version 16.0.1 2025-09-02 14:09:03 +12:00
Rob BygraveandGitHub c0e4e548ee Update the kotlin-maven-plugin for tests and use EA maven profile (#3671)
* Update the kotlin-maven-plugin for tests

* Add a profile for EA that excludes kotlin-querybean-generator

* For build workflow use maven profile "default"

To ensure the kotlin-querybean-generator, test modules run

* For build workflow use maven profile "default"

To ensure the kotlin-querybean-generator, test modules run
2025-09-02 14:04:59 +12:00
Rob Bygrave 92f3245919 Bump ebean-agent to 16.0.1 2025-09-02 13:37:45 +12:00
Rob BygraveandGitHub 59c2290916 Bump ebean-migration dependency, fix for setting dbSchema on migrations (#3670)
Ref: https://github.com/ebean-orm/ebean-migration/pull/163
2025-09-02 12:52:41 +12:00
Rob BygraveandGitHub 1135eb3f99 #3665 Fix for delete() Skips Bean due to Hash Collision (#3669)
Change the underlying storage of deleting beans to be by type by id so:

  private Map<Class<?>, Set<Object>> deletingBeans;
2025-08-31 22:32:27 +12:00
Rob BygraveandGitHub 1dccdff7bf #3664 Fix / support for extra JoinColumns on ManyToOne (#3668)
* #3664 Fix / support for extra JoinColumns on ManyToOne

Allows for extra JoinColumns on ManyToOne. The extra JoinColumn(s) are
expected to be useful for the case of table partitioning where the extra
join column is used to partition the table. In the test case, the partition
column would be the org_id column and common to both tables (same partition key).

* #3664 Fix / support for extra JoinColumns on ManyToOne

Allows for extra JoinColumns on ManyToOne. The extra JoinColumn(s) are
expected to be useful for the case of table partitioning where the extra
join column is used to partition the table. In the test case, the partition
column would be the org_id column and common to both tables (same partition key).

* #3664 Extend test

* #3664 Tidy up test
2025-08-31 12:52:17 +12:00
Rob BygraveandGitHub 5f74a53e3e #3666 Fix for NPE with filterMany() containing or() (#3667)
For FilterMany with QueryBeans, it creates a queryBean to build the filterMany predicates with. This query bean needed 2 changes for this fix:

1. Needs to set it's internal "root" such that it supports chaining (required for or() etc)
2. The ExpressionFactory needs to be explicitly passed to the expression list (rather than get it from the query which is actually null in this case).
2025-08-31 10:08:07 +12:00
197 changed files with 3021 additions and 578 deletions
+1 -1
View File
@@ -40,5 +40,5 @@ jobs:
# - name: Maven single test
# run: mvn --batch-mode clean verify -Dtest="io.ebeaninternal.server.core.DefaultServer_getReferenceTest" -DfailIfNoTests=false
- name: Build with Maven
run: mvn -T 8 clean test
run: mvn -T 8 clean test -Pdefault
+1 -1
View File
@@ -37,5 +37,5 @@ jobs:
- name: Maven version
run: mvn --version
- name: Build with Maven
run: mvn -T 8 test
run: mvn test -Pea
+1
View File
@@ -3,6 +3,7 @@
[![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ebean-orm/ebean/blob/master/LICENSE)
[![Multi-JDK Build](https://github.com/ebean-orm/ebean/actions/workflows/multi-jdk-build.yml/badge.svg)](https://github.com/ebean-orm/ebean/actions/workflows/multi-jdk-build.yml)
[![GraalVM Native Image](https://img.shields.io/badge/GraalVM-Native%20Image%20Ready-darkgreen?logo=graalvm)](https://www.graalvm.org/)
##### Build with database platforms
[![H2Database](https://github.com/ebean-orm/ebean/actions/workflows/h2database.yml/badge.svg)](https://github.com/ebean-orm/ebean/actions/workflows/h2database.yml)
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-net-postgis-types</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>16.0.0</version>
<version>16.4.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>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
<name>ebean-pgvector</name>
<description>ebean-pgvector composite</description>
<artifactId>ebean-pgvector</artifactId>
<properties>
<pgvector.version>0.1.6</pgvector.version>
<postgres.jdbc.version>42.7.2</postgres.jdbc.version>
</properties>
<dependencies>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.4.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>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>16.4.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>com.pgvector</groupId>
<artifactId>pgvector</artifactId>
<version>${pgvector.version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,7 @@
package io.ebean.pgvector.assembly;
/**
* Nothing interesting here - required placeholder for javadoc.
*/
public class Assembly {
}
@@ -0,0 +1,9 @@
module io.ebean.pgvector {
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;
}
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
</parent>
<artifactId>composites</artifactId>
@@ -25,6 +25,7 @@
<module>ebean-postgres</module>
<module>ebean-postgis</module>
<module>ebean-net-postgis</module>
<module>ebean-pgvector</module>
<!-- <module>sqlanywhere</module>-->
<module>ebean-sqlite</module>
<module>ebean-sqlserver</module>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
</parent>
<name>ebean api</name>
@@ -35,7 +35,7 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-config</artifactId>
<version>4.1</version>
<version>4.2</version>
</dependency>
<dependency>
@@ -989,6 +989,14 @@ public interface DatabaseBuilder {
@Deprecated
DatabaseBuilder setNamingConvention(NamingConvention namingConvention);
/**
* Set the AggregateFormulaContext which is used to determine if a database function
* is an aggregate function (like sum, min, max, avg etc).
* <p>
* Use this to override the default known aggregation functions.
*/
DatabaseConfig aggregateFormulaContext(AggregateFormulaContext aggregateFormulaContext);
/**
* Set to true if all DB column and table names should use quoted identifiers.
* <p>
@@ -2610,6 +2618,11 @@ public interface DatabaseBuilder {
*/
NamingConvention getNamingConvention();
/**
* Return the AggregateFormulaContext.
*/
AggregateFormulaContext aggregateFormulaContext();
/**
* Return true if all DB column and table names should use quoted identifiers.
*/
+12 -1
View File
@@ -234,5 +234,16 @@ public interface DtoQuery<T> extends CancelableQuery {
* source. We use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
DtoQuery<T> usingMaster();
default DtoQuery<T> usingMaster() {
return usingMaster(true);
}
/**
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
* data source can be used if defined.
*
* @see #usingMaster()
*/
DtoQuery<T> usingMaster(boolean useMaster);
}
@@ -1,6 +1,7 @@
package io.ebean;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* Object relational query for finding a List, Set, Map or single entity bean.
@@ -310,6 +311,7 @@ public interface Query<T> extends CancelableQuery, QueryBuilder<Query<T>, T> {
/**
* Return the Id value.
*/
@Nullable
Object getId();
/**
@@ -446,11 +448,13 @@ public interface Query<T> extends CancelableQuery, QueryBuilder<Query<T>, T> {
/**
* Return the "for update" wait mode to use.
*/
@Nullable
LockWait getForUpdateLockWait();
/**
* Return the lock type (strength) to use with "for update".
*/
@Nullable
LockType getForUpdateLockType();
/**
@@ -135,7 +135,17 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
* source. We we use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
SELF usingMaster();
default SELF usingMaster() {
return usingMaster(true);
}
/**
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
* data source can be used if defined.
*
* @see #usingMaster()
*/
SELF usingMaster(boolean useMaster);
/**
* Set the base table to use for this query.
@@ -393,6 +403,12 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
* This means that the returning graph can't be mutated via setters, all the collections
* are unmodifiable collections, lazy loading is disabled and that the query uses
* {@link PersistenceContextScope#QUERY}.
* <p>
* Attempting to mutate an unmodifiable bean will throw a <code>UnmodifiableEntityException</code>.
* Attempting to load an unloaded property will throw a <code>LazyInitialisationException</code>
*
* @see LazyInitialisationException
* @see UnmodifiableEntityException
*/
SELF setUnmodifiable(boolean unmodifiable);
@@ -441,6 +457,16 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
/**
* Set the {@link CacheMode} to use the query for executing this query.
* <p>
* Since version 16.x using the query bean will set the query to use unmodifiable (see
* {@link #setUnmodifiable(boolean)}) so the returned object graph is unmodifiable
* and safe to cache by the application.
* <p>
* Attempting to mutate an unmodifiable bean will throw a <code>UnmodifiableEntityException</code>.
* Attempting to load an unloaded property will throw a <code>LazyInitialisationException</code>
*
* @see LazyInitialisationException
* @see UnmodifiableEntityException
*/
SELF setUseQueryCache(CacheMode cacheMode);
+11 -1
View File
@@ -63,7 +63,17 @@ public interface SqlQuery extends Serializable, CancelableQuery {
* source. We use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
SqlQuery usingMaster();
default SqlQuery usingMaster() {
return usingMaster(true);
}
/**
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
* data source can be used if defined.
*
* @see #usingMaster()
*/
SqlQuery usingMaster(boolean useMaster);
/**
* Execute the query returning a list.
@@ -0,0 +1,66 @@
package io.ebean.config;
import java.util.Set;
/**
* Used when parsing formulas to determine if they are aggregation formulas like
* sum, min, max, avg, count etc.
* <p>
* Ebean needs to determine if they are aggregation formulas to determine which
* properties should be included in a GROUP BY clause etc.
*/
public interface AggregateFormulaContext {
/**
* Return true if the outer function is an aggregate function (like sum, count, min, max, avg etc).
*/
boolean isAggregate(String outerFunction);
/**
* Return true if the aggregate function returns a BIGINT type.
* This is true for functions like count that return a numeric value regardless of the
* type of the property or expression inside the outer function.
*/
boolean isCount(String outerFunction);
/**
* Return true if the aggregate function returns a VARCHAR type.
* This is true for functions that return a string concatenation like group_concat etc
* regardless of the type of the property used inside the outer function.
*/
boolean isConcat(String outerFunction);
/**
* Return a builder for the AggregateFormulaContext.
*/
static Builder builder() {
return new AggregateFormulaContextBuilder();
}
/**
* A builder for the AggregateFormulaContext.
*/
interface Builder {
/**
* Override the default set of aggregation functions.
*/
Builder aggregateFunctions(Set<String> count);
/**
* Override the default set of concat functions.
*/
Builder concatFunctions(Set<String> concat);
/**
* Override the default set of count functions.
*/
Builder countFunctions(Set<String> count);
/**
* Build the AggregateFormulaContext.
*/
AggregateFormulaContext build();
}
}
@@ -0,0 +1,61 @@
package io.ebean.config;
import java.util.Set;
final class AggregateFormulaContextBuilder implements AggregateFormulaContext.Builder {
private Set<String> aggFunctions = Set.of("count", "max", "min", "avg", "sum", "group_concat", "string_agg", "listagg");
private Set<String> concat = Set.of("concat", "group_concat", "string_agg", "listagg");
private Set<String> count = Set.of("count");
@Override
public AggregateFormulaContext.Builder aggregateFunctions(Set<String> agg) {
this.aggFunctions = agg;
return this;
}
@Override
public AggregateFormulaContext.Builder concatFunctions(Set<String> concat) {
this.concat = concat;
return this;
}
@Override
public AggregateFormulaContext.Builder countFunctions(Set<String> count) {
this.count = count;
return this;
}
@Override
public AggregateFormulaContext build() {
return new FormulaContext(aggFunctions, concat, count);
}
private static final class FormulaContext implements AggregateFormulaContext {
private final Set<String> aggFunctions;
private final Set<String> concat;
private final Set<String> count;
private FormulaContext(Set<String> aggFunctions, Set<String> concat, Set<String> count) {
this.aggFunctions = aggFunctions;
this.concat = concat;
this.count = count;
}
@Override
public boolean isAggregate(String outerFunction) {
return aggFunctions.contains(outerFunction);
}
@Override
public boolean isCount(String outerFunction) {
return count.contains(outerFunction);
}
@Override
public boolean isConcat(String outerFunction) {
return concat.contains(outerFunction);
}
}
}
@@ -356,6 +356,8 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
*/
private NamingConvention namingConvention = new UnderscoreNamingConvention();
private AggregateFormulaContext aggregateFormulaContext = AggregateFormulaContext.builder().build();
/**
* Behaviour of updates in JDBC batch to by default include all properties.
*/
@@ -1279,6 +1281,17 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
return this;
}
@Override
public AggregateFormulaContext aggregateFormulaContext() {
return aggregateFormulaContext;
}
@Override
public DatabaseConfig aggregateFormulaContext(AggregateFormulaContext aggregateFormulaContext) {
this.aggregateFormulaContext = aggregateFormulaContext;
return this;
}
@Override
public boolean isAllQuotedIdentifiers() {
return platformConfig.isAllQuotedIdentifiers();
@@ -46,6 +46,18 @@ public class DbPlatformTypeMapping {
private static final DbPlatformType MULTILINESTRING = new DbPlatformType("multilinestring");
private static final DbPlatformType MULTIPOLYGON = new DbPlatformType("multipolygon");
private static final DbPlatformType VECTOR = new DbPlatformType("vector", 2000, null);
private static final DbPlatformType VECTOR_HALF = new DbPlatformType("halfvec", 4000, null);
private static final DbPlatformType VECTOR_BIT = new DbPlatformType("bit", 64000, null);
private static final DbPlatformType VECTOR_SPARSE = new DbPlatformType("sparsevec", 1000, null);
/**
* Timestamp with max precision of 15, and fallback to plain timestamp without precision defined.
*/
private static final DbPlatformType TIMESTAMP =
new DbPlatformType("timestamp", 0, 15,
new DbPlatformType("timestamp", false));
private final Map<DbType, DbPlatformType> typeMap = new EnumMap<>(DbType.class);
/**
@@ -82,7 +94,8 @@ public class DbPlatformTypeMapping {
put(DbType.ARRAY);
put(DbType.DATE);
put(DbType.TIME);
put(DbType.TIMESTAMP);
put(DbType.TIMESTAMP, TIMESTAMP);
put(DbType.LONGVARBINARY);
put(DbType.LONGVARCHAR);
// most commonly real maps to db float
@@ -93,6 +106,10 @@ public class DbPlatformTypeMapping {
put(DbType.MULTIPOINT, MULTIPOINT);
put(DbType.MULTILINESTRING, MULTILINESTRING);
put(DbType.MULTIPOLYGON, MULTIPOLYGON);
put(DbType.VECTOR, VECTOR);
put(DbType.VECTOR_HALF, VECTOR_HALF);
put(DbType.VECTOR_BIT, VECTOR_BIT);
put(DbType.VECTOR_SPARSE, VECTOR_SPARSE);
if (logicalTypes) {
// keep it logical for 2 layer DDL generation
@@ -51,7 +51,12 @@ public enum DbType {
JSONB(ExtraDbTypes.JSONB),
JSONCLOB(ExtraDbTypes.JSONClob),
JSONBLOB(ExtraDbTypes.JSONBlob),
JSONVARCHAR(ExtraDbTypes.JSONVarchar);
JSONVARCHAR(ExtraDbTypes.JSONVarchar),
VECTOR(ExtraDbTypes.VECTOR),
VECTOR_HALF(ExtraDbTypes.VECTOR_HALF),
VECTOR_BIT(ExtraDbTypes.VECTOR_BIT),
VECTOR_SPARSE(ExtraDbTypes.VECTOR_SPARSE);
private final int id;
@@ -74,4 +74,24 @@ public interface ExtraDbTypes {
*/
int MULTILINESTRING = 6007;
/**
* PGVector base type
*/
int VECTOR = 7000;
/**
* PGVector half precision float type
*/
int VECTOR_HALF = 7001;
/**
* PGVector binary type (bit)
*/
int VECTOR_BIT = 7002;
/**
* PGVector sparse type
*/
int VECTOR_SPARSE = 7003;
}
@@ -0,0 +1,65 @@
package io.ebean.config;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
class AggregateFormulaContextTest {
@Test
void defaultContext() {
var defaultContext = AggregateFormulaContext.builder().build();
for (String aggFunction : List.of("count", "max", "min", "avg", "sum", "group_concat", "string_agg", "listagg")) {
assertThat(defaultContext.isAggregate(aggFunction)).isTrue();
}
for (String c : List.of("concat", "group_concat", "string_agg", "listagg")) {
assertThat(defaultContext.isConcat(c)).isTrue();
}
for (String c : List.of("count")) {
assertThat(defaultContext.isCount(c)).isTrue();
}
assertThat(defaultContext.isConcat("junk")).isFalse();
assertThat(defaultContext.isCount("junk")).isFalse();
assertThat(defaultContext.isAggregate("junk")).isFalse();
}
@Test
void overrideAggregateFunctions() {
AggregateFormulaContext mySum = AggregateFormulaContext.builder()
.aggregateFunctions(Set.of("my_sum"))
.build();
assertThat(mySum.isAggregate("my_sum")).isTrue();
assertThat(mySum.isAggregate("avg")).isFalse();
assertThat(mySum.isCount("count")).isTrue();
assertThat(mySum.isConcat("group_concat")).isTrue();
}
@Test
void overrideConcatFunctions() {
AggregateFormulaContext myConcat = AggregateFormulaContext.builder()
.concatFunctions(Set.of("my_concat"))
.build();
assertThat(myConcat.isAggregate("avg")).isTrue();
assertThat(myConcat.isCount("count")).isTrue();
assertThat(myConcat.isConcat("group_concat")).isFalse();
assertThat(myConcat.isConcat("my_concat")).isTrue();
}
@Test
void overrideCountFunctions() {
AggregateFormulaContext myCount = AggregateFormulaContext.builder()
.countFunctions(Set.of("my_count"))
.build();
assertThat(myCount.isAggregate("avg")).isTrue();
assertThat(myCount.isCount("count")).isFalse();
assertThat(myCount.isCount("my_count")).isTrue();
assertThat(myCount.isConcat("group_concat")).isTrue();
}
}
+38 -26
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<!-- platforms -->
@@ -193,79 +193,91 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector</artifactId>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>io.ebean</groupId>
<artifactId>ebean-parent</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</parent>
<artifactId>ebean-core-json</artifactId>
<name>ebean-core-json</name>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<!-- Jackson core used internally by Ebean -->
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
+7 -7
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.0.0</version>
<version>16.4.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-json</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -52,7 +52,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -165,21 +165,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>16.0.0</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
@@ -0,0 +1,10 @@
package io.ebeaninternal.api;
import io.ebean.config.AggregateFormulaContext;
import io.ebeaninternal.server.query.STreeProperty;
public interface FormulaBuilder {
STreeProperty create(AggregateFormulaContext context, String formula, String path);
}
@@ -41,7 +41,7 @@ final class NaturalKeyEntryBasic implements NaturalKeyEntry {
* Create when query uses an IN PAIRS clause.
*/
NaturalKeyEntryBasic(BeanNaturalKey naturalKey, List<NaturalKeyEq> eqList,
String inMapProperty0, String inMapProperty1, Pairs.Entry pair) {
String inMapProperty0, String inMapProperty1, Pairs.Entry pair) {
load(eqList);
map.put(inMapProperty0, pair.getA());
map.put(inMapProperty1, pair.getB());
@@ -49,6 +49,14 @@ final class NaturalKeyEntryBasic implements NaturalKeyEntry {
this.key = calculateKey(naturalKey);
}
NaturalKeyEntryBasic(BeanNaturalKey naturalKey, List<NaturalKeyEq> eqList,
Map<String, Object> properties, Object[] naturalKeyValue) {
load(eqList);
map.putAll(properties);
this.inValue = naturalKeyValue;
this.key = calculateKey(naturalKey);
}
private void load(List<NaturalKeyEq> eqList) {
if (eqList != null) {
for (NaturalKeyEq eq : eqList) {
@@ -3,10 +3,7 @@ package io.ebeaninternal.api;
import io.ebean.Pairs;
import io.ebeaninternal.server.deploy.BeanNaturalKey;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* Collects the data for processing the natural key cache processing.
@@ -19,8 +16,9 @@ public final class NaturalKeyQueryData<T> {
*/
private boolean hasIn;
// IN Pairs clause - only one allowed
private String inProperty0, inProperty1;
private List<Pairs.Entry> inPairs;
private String[] properties;
private List<Object[]> inTuples;
// IN clause - only one allowed
private List<Object> inValues;
private String inProperty;
@@ -47,14 +45,39 @@ public final class NaturalKeyQueryData<T> {
}
if (matchProperty(property0) && matchProperty(property1)) {
this.hasIn = true;
this.inProperty0 = property0;
this.inProperty1 = property1;
this.properties = new String[]{property0, property1};
this.inPairs = new ArrayList<>(inPairs); // will be modified
return this.inPairs;
}
return null;
}
/**
* Match for In Tuples expression. We only allow one IN clause.
*/
public List<Object[]> matchInTuples(String[] properties, List<Object[]> inTuples) {
if (hasIn) {
// only 1 IN allowed (to project naturalIds)
return null;
}
boolean matchAll = true;
for (String property : properties) {
if (!matchProperty(property)) {
matchAll = false;
break;
}
}
if (matchAll) {
this.hasIn = true;
this.properties = Arrays.copyOf(properties, properties.length);
this.inTuples = new ArrayList<>(inTuples);
return this.inTuples;
}
return null;
}
/**
* Match for IN expression. We only allow one IN clause.
*/
@@ -100,6 +123,8 @@ public final class NaturalKeyQueryData<T> {
addInValues();
} else if (inPairs != null) {
addInPairs();
} else if (inTuples != null) {
addInTuples();
} else {
addEqualsKey();
}
@@ -110,7 +135,17 @@ public final class NaturalKeyQueryData<T> {
// a findList() with an IN Map clause so we project
// for every IN value a natural key combination
for (Pairs.Entry entry : inPairs) {
set.add(new NaturalKeyEntryBasic(naturalKey, eqList, inProperty0, inProperty1, entry));
set.add(new NaturalKeyEntryBasic(naturalKey, eqList, properties[0], properties[1], entry));
}
}
private void addInTuples() {
for (Object[] inTuple : inTuples) {
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < inTuple.length; i++) {
map.put(properties[i], inTuple[i]);
}
set.add(new NaturalKeyEntryBasic(naturalKey, eqList, map, inTuple));
}
}
@@ -152,11 +187,8 @@ public final class NaturalKeyQueryData<T> {
if (inProperty != null) {
exprProps.add(inProperty);
}
if (inProperty0 != null) {
exprProps.add(inProperty0);
}
if (inProperty1 != null) {
exprProps.add(inProperty1);
if (properties != null) {
exprProps.addAll(Arrays.asList(properties));
}
if (eqList != null) {
for (NaturalKeyEq eq : eqList) {
@@ -173,6 +205,7 @@ public final class NaturalKeyQueryData<T> {
int defined = (inValues == null) ? 0 : 1;
defined += (inPairs == null) ? 0 : 2;
defined += (eqList == null) ? 0 : eqList.size();
defined += (inTuples == null) ? 0 : properties.length;
return defined == naturalKey.length();
}
@@ -206,6 +239,9 @@ public final class NaturalKeyQueryData<T> {
} else if (inPairs != null) {
//noinspection SuspiciousMethodCalls
inPairs.remove(inValue);
} else if (inTuples != null) {
//noinspection SuspiciousMethodCalls
inTuples.remove(inValue);
}
}
}
@@ -14,4 +14,9 @@ public interface SpiBeanType {
* or removals from the collection.
*/
boolean isToManyDirty(EntityBean bean);
/**
* Return the FormulaBuilder for this type.
*/
FormulaBuilder formulaBuilder();
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.query.STreeProperty;
import org.jspecify.annotations.Nullable;
import io.ebean.*;
import io.ebean.bean.BeanCollectionLoader;
@@ -377,4 +378,6 @@ public interface SpiEbeanServer extends SpiServer, BeanCollectionLoader {
@Nullable
SqlRow findOne(SpiSqlQuery query);
<T> STreeProperty createFormulaProperty(SpiBeanType desc, String formula, String path);
}
@@ -15,8 +15,4 @@ public interface SpiQueryManyJoin {
*/
String fetchOrderBy();
/**
* Wrap the filter many expression with a condition allowing lEFT JOIN null matching row.
*/
String idNullOr(String filterManyExpression);
}
@@ -65,12 +65,12 @@ public interface SpiTransaction extends Transaction {
* <p>
* This is to handle bi-directional relationships where both sides Cascade.
*/
void registerDeleteBean(Integer hash);
void registerDeleteBean(Class<?> type, Object id);
/**
* Return true if this is a bean that has already been saved/deleted.
*/
boolean isRegisteredDeleteBean(Integer hash);
boolean isRegisteredDeleteBean(Class<?> type, Object id);
/**
* Unregister the persisted beans. Expected after persisting top level beans
@@ -338,11 +338,6 @@ public interface SpiTransaction extends Transaction {
*/
boolean isNestedUseSavepoint();
/**
* Return true if explicitly set to skip cache (ignores skipOnWrite).
*/
boolean isSkipCacheExplicit();
/**
* Fire pre commit processing/listeners.
*/
@@ -169,11 +169,6 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
transaction.setSkipCache(skipCache);
}
@Override
public boolean isSkipCacheExplicit() {
return transaction.isSkipCacheExplicit();
}
@Override
public boolean isSkipCache() {
return transaction.isSkipCache();
@@ -195,13 +190,13 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
}
@Override
public void registerDeleteBean(Integer hash) {
transaction.registerDeleteBean(hash);
public void registerDeleteBean(Class<?> type, Object id) {
transaction.registerDeleteBean(type, id);
}
@Override
public boolean isRegisteredDeleteBean(Integer hash) {
return transaction.isRegisteredDeleteBean(hash);
public boolean isRegisteredDeleteBean(Class<?> type, Object id) {
return transaction.isRegisteredDeleteBean(type, id);
}
@Override
@@ -113,6 +113,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final long slowQueryMicros;
private final SlowQueryListener slowQueryListener;
private final boolean disableL2Cache;
private final AggregateFormulaContext formulaContext;
private boolean shutdown;
/**
@@ -128,6 +129,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.backgroundExecutor = config.getBackgroundExecutor();
this.extraMetrics = config.getExtraMetrics();
this.serverName = this.config.getName();
this.formulaContext = config.getConfig().aggregateFormulaContext();
this.lazyLoadBatchSize = this.config.getLazyLoadBatchSize();
this.cqueryEngine = config.getCQueryEngine();
this.expressionFactory = config.getExpressionFactory();
@@ -928,6 +930,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return findId(query);
}
@Override
public <T> STreeProperty createFormulaProperty(SpiBeanType desc, String formula, String path) {
return desc.formulaBuilder().create(formulaContext, formula, path);
}
<T> SpiOrmQueryRequest<T> createQueryRequest(Type type, SpiQuery<T> query) {
SpiOrmQueryRequest<T> request = buildQueryRequest(type, query);
request.prepareQuery();
@@ -497,10 +497,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return cacheKey != null && query.queryCacheMode().isPut();
}
public boolean isBeanCachePutMany() {
return !transaction.isSkipCacheExplicit() && query.isBeanCachePut();
}
public boolean isBeanCachePut() {
return !transaction.isSkipCache() && query.isBeanCachePut();
}
@@ -54,10 +54,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* The unique id used for logging summary.
*/
private Object idValue;
/**
* Hash value used to handle cascade delete both ways in a relationship.
*/
private Integer beanHash;
private boolean statelessUpdate;
private boolean notifyCache;
/**
@@ -285,7 +281,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private void onFailedUpdateUndoGeneratedProperties() {
for (BeanProperty prop : beanDescriptor.propertiesGenUpdate()) {
Object oldVal = intercept.origValue(prop.propertyIndex());
prop.setValue(entityBean, oldVal);
if (oldVal != null) {
prop.setValue(entityBean, oldVal);
}
}
}
@@ -553,34 +551,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* The hash used to register the bean with the transaction.
* <p>
* Takes into account the class type and id value.
*/
private Integer beanHash() {
if (beanHash == null) {
Object id = beanDescriptor.getId(entityBean);
int hc = 92821 * bean.getClass().getName().hashCode();
if (id != null) {
hc += id.hashCode();
}
beanHash = hc;
}
return beanHash;
}
public void registerDeleteBean() {
Integer hash = beanHash();
transaction.registerDeleteBean(hash);
final Object id = beanDescriptor.id(entityBean);
transaction.registerDeleteBean(beanDescriptor.type(), id);
}
public boolean isRegisteredForDeleteBean() {
if (transaction == null) {
return false;
} else {
Integer hash = beanHash();
return transaction.isRegisteredDeleteBean(hash);
final Object id = beanDescriptor.id(entityBean);
return transaction.isRegisteredDeleteBean(beanDescriptor.type(), id);
}
}
@@ -809,7 +790,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public void checkRowCount(int rowCount) {
if (rowCount != 1 && rowCount != Statement.SUCCESS_NO_INFO) {
if (ConcurrencyMode.VERSION == concurrencyMode) {
onFailedUpdateUndoGeneratedProperties();
if (type == Type.UPDATE) {
onFailedUpdateUndoGeneratedProperties();
}
throw new OptimisticLockException("Data has changed. updated row count " + rowCount, null, bean);
} else if (rowCount == 0 && type == Type.UPDATE) {
throw new EntityNotFoundException("No rows updated");
@@ -2410,7 +2410,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
*/
private STreeProperty findSqlTreeFormula(String formula, String path) {
String key = formula + "-" + path;
return dynamicProperty.computeIfAbsent(key, (fullKey) -> FormulaPropertyPath.create(this, formula, path));
return dynamicProperty.computeIfAbsent(key, (fullKey) -> ebeanServer.createFormulaProperty(this, formula, path));
}
@Override
public FormulaBuilder formulaBuilder() {
return new DFormulaBuilder(this);
}
/**
@@ -36,11 +36,6 @@ public final class BeanFkeyProperty implements ElPropertyValue {
return "prefix:" + prefix + " name:" + name + " dbColumn:" + dbColumn + " ph:" + placeHolder;
}
@Override
public String idNullOr(String filterManyExpression) {
throw new UnsupportedOperationException();
}
@Override
public boolean isAggregation() {
return false;
@@ -513,11 +513,6 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return owningType.isAssignableFrom(type);
}
@Override
public String idNullOr(String filterManyExpression) {
throw new UnsupportedOperationException();
}
@Override
public void loadIgnore(DbReadContext ctx) {
ctx.dataReader().incrementPos(1);
@@ -436,14 +436,15 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
}
TableJoinColumn[] cols = join.columns();
if (!idProp.isEmbedded()) {
// simple single scalar id
if (cols.length != 1) {
CoreLog.log.log(ERROR, "No Imported Id column for {0} in table {1}", idProp, join.getTable());
return null;
} else {
BeanProperty[] idProps = {idProp};
return createImportedScalar(owner, cols[0], idProps, others);
// simple single scalar id, match on the foreign column, allow extra TableJoinColumn for #3664
String matchColumn = idProp.dbColumn();
for (TableJoinColumn col : cols) {
if (matchColumn.equals(col.getForeignDbColumn())) {
return createImportedScalar(owner, col, new BeanProperty[]{idProp}, others);
}
}
CoreLog.log.log(ERROR, "No Imported Id column for {0} in table {1}", idProp, join.getTable());
return null;
} else {
// embedded id
BeanPropertyAssocOne<?> embProp = (BeanPropertyAssocOne<?>) idProp;
@@ -634,11 +634,6 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return fetchOrderBy;
}
@Override
public String idNullOr(String filterManyExpression) {
return targetIdBinder.idNullOr(name, filterManyExpression);
}
/**
* Return the order by for use when lazy loading the associated collection.
*/
@@ -128,16 +128,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
throw new PersistenceException("Cannot find imported id for " + fullName() + " from " + targetDescriptor
+ ". If using native-image, possibly missing reflect-config for the Id property.");
}
if (importedId.isScalar()) {
// limit JoinColumn mapping to the @Id / primary key
TableJoinColumn[] columns = tableJoin.columns();
String foreignJoinColumn = columns[0].getForeignDbColumn();
String foreignIdColumn = targetDescriptor.idProperty().dbColumn();
if (!foreignJoinColumn.equalsIgnoreCase(foreignIdColumn)) {
throw new PersistenceException("Mapping limitation - @JoinColumn on " + fullName() + " needs to map to a primary key as per Issue #529 "
+ " - joining to " + foreignJoinColumn + " and not " + foreignIdColumn);
}
}
} else {
exportedProperties = createExported();
String delStmt = "delete from " + targetDescriptor.baseTable() + " where ";
@@ -0,0 +1,19 @@
package io.ebeaninternal.server.deploy;
import io.ebean.config.AggregateFormulaContext;
import io.ebeaninternal.api.FormulaBuilder;
import io.ebeaninternal.server.query.STreeProperty;
final class DFormulaBuilder implements FormulaBuilder {
private final BeanDescriptor<?> descriptor;
DFormulaBuilder(BeanDescriptor<?> descriptor) {
this.descriptor = descriptor;
}
@Override
public STreeProperty create(AggregateFormulaContext context, String formula, String path) {
return FormulaPropertyPath.create(descriptor, context, formula, path);
}
}
@@ -154,4 +154,9 @@ public interface DbSqlContext {
* as it was already added to the query.
*/
boolean joinAdded();
/**
* Include the filter many predicates if specified into the JOIN clause.
*/
void includeFilterMany();
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.deploy;
import io.ebean.core.type.ScalarType;
import io.ebean.config.AggregateFormulaContext;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.query.STreeProperty;
@@ -9,11 +10,10 @@ import java.util.Set;
final class FormulaPropertyPath {
private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg", "sum"};
private static final String DISTINCT_ = "distinct ";
private final BeanDescriptor<?> descriptor;
private final AggregateFormulaContext context;
private final String formula;
private final String outerFunction;
private final String internalExpression;
@@ -24,12 +24,13 @@ final class FormulaPropertyPath {
private String cast;
private String alias;
static STreeProperty create(BeanDescriptor<?> descriptor, String formula, String path) {
return new FormulaPropertyPath(descriptor, formula, path).build();
static STreeProperty create(BeanDescriptor<?> descriptor, AggregateFormulaContext context, String formula, String path) {
return new FormulaPropertyPath(descriptor, context, formula, path).build();
}
FormulaPropertyPath(BeanDescriptor<?> descriptor, String formula, String path) {
FormulaPropertyPath(BeanDescriptor<?> descriptor, AggregateFormulaContext context, String formula, String path) {
this.descriptor = descriptor;
this.context = context;
this.formula = formula;
int openBracket = formula.indexOf('(');
int closeBracket = formula.lastIndexOf(')');
@@ -106,10 +107,10 @@ final class FormulaPropertyPath {
}
return create(scalarType);
}
if (isCount()) {
if (context.isCount(outerFunction)) {
return create(descriptor.scalarType(Types.BIGINT));
}
if (isConcat()) {
if (context.isConcat(outerFunction)) {
return create(descriptor.scalarType(Types.VARCHAR));
}
if (firstProp == null) {
@@ -144,12 +145,7 @@ final class FormulaPropertyPath {
}
private boolean isAggregate() {
for (String aggFunction : AGG_FUNCTIONS) {
if (aggFunction.equals(outerFunction)) {
return true;
}
}
return false;
return context.isAggregate(outerFunction);
}
private String buildFormula(String parsed) {
@@ -27,11 +27,6 @@ public interface IdBinder {
*/
void initialise();
/**
* Wrap the filter many expression with a condition allowing lEFT JOIN null matching row.
*/
String idNullOr(String name, String filterManyExpression);
String idSelect();
/**
@@ -45,20 +45,6 @@ public final class IdBinderEmbedded implements IdBinder {
this.idInValueSql = idInExpandedForm ? idInExpanded() : idInCompressed();
}
@Override
public String idNullOr(String prefix, String filterManyExpression) {
StringBuilder sb = new StringBuilder(100);
sb.append("((");
for (int i = 0; i < props.length; i++) {
if (i > 0) {
sb.append(" and ");
}
sb.append("${").append(prefix).append('}').append(props[i].dbColumn()).append(" is null");
}
sb.append(") or (").append(filterManyExpression).append("))");
return sb.toString();
}
@Override
public String idSelect() {
return embIdProperty.name();
@@ -28,11 +28,6 @@ final class IdBinderEmpty implements IdBinder {
public void initialise() {
}
@Override
public String idNullOr(String name, String filterManyExpression) {
throw new UnsupportedOperationException();
}
@Override
public String idSelect() {
return "";
@@ -261,9 +261,4 @@ public final class IdBinderSimple implements IdBinder {
final Object value = idProperty.getValue(bean);
return scalarType.format(value);
}
@Override
public String idNullOr(String prefix, String filterManyExpression) {
return "(${" + prefix + "}" + idProperty.dbColumn() + " is null or (" + filterManyExpression + "))";
}
}
@@ -804,11 +804,9 @@ public class DeployBeanDescriptor<T> {
* Return the defaultSelectClause using FetchType.LAZY and FetchType.EAGER.
*/
public String getDefaultSelectClause() {
StringBuilder sb = new StringBuilder();
boolean hasLazyFetch = false;
for (DeployBeanProperty prop : propMap.values()) {
if (!prop.isTransient() && !(prop instanceof DeployBeanPropertyAssocMany<?>)) {
if (prop.isFetchEager()) {
@@ -617,6 +617,8 @@ public class DeployBeanProperty {
this.dbRead = true;
this.dbInsertable = false;
this.dbUpdateable = false;
// aggregation by default not fetchEager
this.fetchEager = false;
}
/**
@@ -286,8 +286,4 @@ public final class ElPropertyChain implements ElPropertyValue {
}
}
@Override
public String idNullOr(String filterManyExpression) {
throw new UnsupportedOperationException();
}
}
@@ -726,7 +726,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> conjunction(Query<T> query) {
return new JunctionExpression<>(Junction.Type.AND, query, query.where());
return new JunctionExpression<>(Junction.Type.AND, query, this, query.where());
}
/**
@@ -734,7 +734,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> disjunction(Query<T> query) {
return new JunctionExpression<>(Junction.Type.OR, query, query.where());
return new JunctionExpression<>(Junction.Type.OR, query, this, query.where());
}
/**
@@ -742,7 +742,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> conjunction(Query<T> query, ExpressionList<T> parent) {
return new JunctionExpression<>(Junction.Type.AND, query, parent);
return new JunctionExpression<>(Junction.Type.AND, query, this, parent);
}
/**
@@ -750,14 +750,14 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> disjunction(Query<T> query, ExpressionList<T> parent) {
return new JunctionExpression<>(Junction.Type.OR, query, parent);
return new JunctionExpression<>(Junction.Type.OR, query, this, parent);
}
/**
* Return a list of expressions that are wrapped by NOT.
*/
public <T> Junction<T> junction(Junction.Type type, Query<T> query) {
return new JunctionExpression<>(type, query, query.where());
return new JunctionExpression<>(type, query, this, query.where());
}
/**
@@ -765,6 +765,6 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> junction(Junction.Type type, Query<T> query, ExpressionList<T> parent) {
return new JunctionExpression<>(type, query, parent);
return new JunctionExpression<>(type, query, this, parent);
}
}
@@ -40,12 +40,8 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
/**
* Construct for Text root expression list - this handles implicit Bool Should, Must etc.
*/
public DefaultExpressionList(Query<T> query) {
this(query, query.getExpressionFactory(), null, new ArrayList<>(), true);
}
public DefaultExpressionList(Query<T> query, ExpressionList<T> parentExprList) {
this(query, query.getExpressionFactory(), parentExprList, new ArrayList<>());
public DefaultExpressionList(Query<T> query, boolean textRoot) {
this(query, query.getExpressionFactory(), null, new ArrayList<>(), textRoot);
}
DefaultExpressionList(Query<T> query, ExpressionFactory expr, ExpressionList<T> parentExprList, List<SpiExpression> list) {
@@ -12,7 +12,7 @@ final class InTuplesExpression extends AbstractExpression {
private final boolean not;
private final String[] properties;
private final List<Object[]> entries;
private List<Object[]> entries;
InTuplesExpression(InTuples pairs, boolean not) {
super("");
@@ -25,7 +25,15 @@ final class InTuplesExpression extends AbstractExpression {
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
return false;
if (not) {
return false;
}
List<Object[]> copy = data.matchInTuples(properties, entries);
if (copy == null) {
return false;
}
entries = copy;
return true;
}
@Override
@@ -24,9 +24,9 @@ final class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expr
DefaultExpressionList<T> exprList;
Junction.Type type;
JunctionExpression(Junction.Type type, Query<T> query, ExpressionList<T> parent) {
JunctionExpression(Junction.Type type, Query<T> query, ExpressionFactory expr, ExpressionList<T> parent) {
this.type = type;
this.exprList = new DefaultExpressionList<>(query, parent);
this.exprList = new DefaultExpressionList<>(query, expr, parent, new ArrayList<>());
}
/**
@@ -329,6 +329,13 @@ public final class Binder {
geoTypeBinder.bind(b, dataType, data);
break;
case DbPlatformType.VECTOR:
case DbPlatformType.VECTOR_HALF:
case DbPlatformType.VECTOR_BIT:
case DbPlatformType.VECTOR_SPARSE:
b.setObject(data);
break;
case java.sql.Types.OTHER:
b.setObject(data, dataType);
break;
@@ -280,7 +280,7 @@ final class CQueryBuilder {
sql = wrapSelectCount(sql);
} else if (wrap || query.isRawSql()) {
// remove order by - mssql does not accept order by in subqueries
int pos = sql.lastIndexOf(" order by ");
int pos = lastTopLevelOrderBy(sql);
if (pos != -1) {
sql = sql.substring(0, pos);
}
@@ -300,6 +300,27 @@ final class CQueryBuilder {
return request.descriptor().includesAggregation(query.detail());
}
/**
* Find the last " order by " that is not inside parentheses (i.e. not inside a subquery).
* Returns the position or -1 if not found.
*/
static int lastTopLevelOrderBy(String sql) {
String target = " order by ";
int depth = 0;
int lastFound = -1;
for (int i = 0; i < sql.length(); i++) {
char c = sql.charAt(i);
if (c == '(') {
depth++;
} else if (c == ')') {
depth--;
} else if (depth == 0 && c == ' ' && sql.regionMatches(true, i, target, 0, target.length())) {
lastFound = i;
}
}
return lastFound;
}
private String wrapSelectCount(String sql) {
sql = "select count(*) from ( " + sql + ")";
if (selectCountWithAlias) {
@@ -715,7 +736,7 @@ final class CQueryBuilder {
appendHistoryAsOfPredicate();
appendFindId();
appendToWhere(predicates.dbWhere());
appendToWhere(predicates.dbFilterMany());
appendToWhere(predicates.dbFilterManyWhere());
if (!query.isIncludeSoftDeletes()) {
appendSoftDelete();
}
@@ -39,6 +39,7 @@ public final class CQueryPredicates {
private final Object idValue;
private final BindParams bindParams;
private DefaultExpressionRequest filterMany;
private boolean filterManyJoin;
/**
* Bind values from the where expressions.
*/
@@ -106,6 +107,9 @@ public final class CQueryPredicates {
dataBind.append(", ");
}
}
if (filterManyJoin) {
filterMany.bind(dataBind);
}
if (idValue != null) {
// this is a find by id type query...
request.descriptor().bindId(dataBind, idValue);
@@ -119,7 +123,7 @@ public final class CQueryPredicates {
if (where != null) {
where.bind(dataBind);
}
if (filterMany != null) {
if (!filterManyJoin && filterMany != null) {
filterMany.bind(dataBind);
}
if (having != null) {
@@ -189,9 +193,10 @@ public final class CQueryPredicates {
if (chunk != null) {
SpiExpressionList<?> filterManyExpr = chunk.getFilterMany();
if (filterManyExpr != null) {
this.filterMany = new DefaultExpressionRequest(request, deployParser, binder, filterManyExpr);
filterManyJoin = chunk.isFilterManyJoin();
filterMany = new DefaultExpressionRequest(request, deployParser, binder, filterManyExpr);
if (buildSql) {
dbFilterMany = manyProperty.idNullOr(filterMany.buildSql());
dbFilterMany = filterMany.buildSql();
}
}
}
@@ -339,10 +344,17 @@ public final class CQueryPredicates {
}
/**
* Return a db filter for filtering many fetch joins.
* Return a db filter to be included in the WHERE.
*/
String dbFilterMany() {
return dbFilterMany;
String dbFilterManyWhere() {
return filterManyJoin ? null : dbFilterMany;
}
/**
* Return a db filter to be included in the JOIN.
*/
String dbFilterManyJoin() {
return filterManyJoin ? dbFilterMany : null;
}
/**
@@ -15,8 +15,8 @@ final class DFetchGroup<T> implements SpiFetchGroup<T> {
}
@Override
public OrmQueryDetail detail() {
return detail.copy();
public OrmQueryDetail detail(OrmQueryDetail existing) {
return detail.copy(existing);
}
@Override
@@ -22,6 +22,7 @@ final class DefaultDbSqlContext implements DbSqlContext {
private final ArrayStack<String> joinStack = new ArrayStack<>();
private final ArrayStack<String> prefixStack = new ArrayStack<>();
private final String fromForUpdate;
private final String dbFilterManyJoin;
private boolean useColumnAlias;
private int columnIndex;
private int asOfTableCount;
@@ -41,7 +42,7 @@ final class DefaultDbSqlContext implements DbSqlContext {
private boolean joinSuppressed;
DefaultDbSqlContext(SqlTreeAlias alias, String columnAliasPrefix, CQueryHistorySupport historySupport,
CQueryDraftSupport draftSupport, String fromForUpdate) {
CQueryDraftSupport draftSupport, String fromForUpdate, String dbFilterManyJoin) {
this.alias = alias;
this.columnAliasPrefix = columnAliasPrefix;
this.useColumnAlias = columnAliasPrefix != null;
@@ -49,6 +50,14 @@ final class DefaultDbSqlContext implements DbSqlContext {
this.historySupport = historySupport;
this.historyQuery = (historySupport != null);
this.fromForUpdate = fromForUpdate;
this.dbFilterManyJoin = dbFilterManyJoin;
}
@Override
public void includeFilterMany() {
if (dbFilterManyJoin != null) {
sb.append(" and ").append(dbFilterManyJoin);
}
}
@Override
@@ -52,7 +52,7 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
@SuppressWarnings("rawtypes")
@Override
public Query<T> select(FetchGroup fetchGroup) {
this.detail = ((SpiFetchGroup) fetchGroup).detail();
this.detail = ((SpiFetchGroup) fetchGroup).detail(detail);
return this;
}
@@ -235,7 +235,7 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
}
@Override
public Query<T> usingMaster() {
public Query<T> usingMaster(boolean useMaster) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@@ -129,7 +129,7 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
result = finder.postProcessMany(request, result);
}
if (result != null && request.isBeanCachePutMany()) {
if (result != null && request.isBeanCachePut()) {
// load the individual beans into the bean cache
request.descriptor().cacheBeanPutAll(result.actualDetails());
}
@@ -14,6 +14,7 @@ import io.ebeaninternal.server.core.RelationalQueryRequest;
import io.ebeaninternal.server.core.RowReader;
import io.ebeaninternal.server.persist.Binder;
import jakarta.persistence.NonUniqueResultException;
import jakarta.persistence.PersistenceException;
import java.sql.SQLException;
@@ -115,9 +116,14 @@ public final class DefaultRelationalQueryEngine implements RelationalQueryEngine
try {
request.executeSql(binder, SpiQuery.Type.BEAN);
T value = request.mapOne(mapper);
if (request.next()) {
throw new NonUniqueResultException("Got more than 1 result for findOne");
}
request.logSummary();
return value;
} catch (NonUniqueResultException e) {
throw e;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
@@ -160,13 +166,17 @@ public final class DefaultRelationalQueryEngine implements RelationalQueryEngine
T value = null;
if (dataReader.next()) {
value = scalarType.read(dataReader);
if (dataReader.next()) {
throw new NonUniqueResultException("Got more than 1 result for findSingleAttribute");
}
}
request.logSummary();
return value;
} catch (NonUniqueResultException e) {
throw e;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
@@ -107,7 +107,7 @@ public final class SqlTreeBuilder {
CQueryHistorySupport historySupport = builder.historySupport(query);
CQueryDraftSupport draftSupport = builder.draftSupport(query);
String colAlias = subQuery || rootNode.isSingleProperty() ? null : columnAliasPrefix;
this.ctx = new DefaultDbSqlContext(alias, colAlias, historySupport, draftSupport, fromForUpdate);
this.ctx = new DefaultDbSqlContext(alias, colAlias, historySupport, draftSupport, fromForUpdate, predicates.dbFilterManyJoin());
}
/**
@@ -371,10 +371,11 @@ public final class SqlTreeBuilder {
IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes, manyWhereJoins, temporalMode);
Collection<SqlTreeNodeExtraJoin> extraJoins = extraJoinDistill.getExtraJoinRootNodes();
if (!extraJoins.isEmpty()) {
// add extra joins required to support predicates
// and/or order by clause
// add extra joins required to support predicates and/or order by clause
for (SqlTreeNodeExtraJoin extraJoin : extraJoins) {
myList.add(extraJoin);
if (!addToParent(extraJoin, myList)) {
myList.add(extraJoin);
}
if (extraJoin.isManyJoin()) {
// as we are now going to join to the many then we need
// to add the distinct to the sql query to stop duplicate
@@ -385,6 +386,20 @@ public final class SqlTreeBuilder {
}
}
/**
* Return true if the extra join was added as a child to one of the nodes.
*/
private boolean addToParent(SqlTreeNodeExtraJoin extraJoin, List<SqlTreeNode> myList) {
String parentPath = SplitName.split(extraJoin.prefix())[0];
for (SqlTreeNode maybeParent : myList) {
if (maybeParent.prefix().equals(parentPath)) {
maybeParent.addChild(extraJoin);
return true;
}
}
return false;
}
/**
* A subQuery has slightly different rules in that it just generates SQL (into
* the where clause) and its properties are not required to read the resultSet
@@ -641,7 +656,7 @@ public final class SqlTreeBuilder {
SqlTreeNodeExtraJoin root = findExtraJoinRoot(includeProp, extraJoin);
// register the root because these are the only ones we
// return back.
rootRegister.put(root.name(), root);
rootRegister.put(root.prefix(), root);
}
}
@@ -84,4 +84,12 @@ interface SqlTreeNode {
*/
default void unselectLobsForPlatform() {
}
default String prefix() {
return ""; // not matched
}
default void addChild(SqlTreeNode extraJoin) {
throw new UnsupportedOperationException();
}
}
@@ -99,6 +99,16 @@ class SqlTreeNodeBean implements SqlTreeNode {
this.pathMap = createPathMap(prefix, desc);
}
@Override
public String prefix() {
return prefix;
}
@Override
public void addChild(SqlTreeNode extraJoin) {
children.add(extraJoin);
}
@Override
public SqlTreeLoad createLoad() {
return new SqlTreeLoadBean(this);
@@ -87,7 +87,8 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode {
return manyJoin;
}
public String name() {
@Override
public String prefix() {
return prefix;
}
@@ -45,5 +45,6 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
super.appendFrom(ctx, joinType.autoToOuter());
ctx.includeFilterMany();
}
}
@@ -95,8 +95,8 @@ public final class DefaultDtoQuery<T> extends AbstractQuery implements SpiDtoQue
}
@Override
public DtoQuery<T> usingMaster() {
this.useMaster = true;
public DtoQuery<T> usingMaster(boolean useMaster) {
this.useMaster = useMaster;
return this;
}
@@ -769,7 +769,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
copy.useQueryCache = useQueryCache;
copy.unmodifiable = unmodifiable;
if (detail != null) {
copy.detail = detail.copy();
copy.detail = detail.copy(null);
}
copy.temporalMode = temporalMode;
copy.firstRow = firstRow;
@@ -1387,7 +1387,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
@Override
public final Query<T> select(FetchGroup<T> fetchGroup) {
if (fetchGroup != null) {
this.detail = ((SpiFetchGroup<T>) fetchGroup).detail();
this.detail = ((SpiFetchGroup<T>) fetchGroup).detail(detail);
}
return this;
}
@@ -1475,8 +1475,8 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
}
@Override
public Query<T> usingMaster() {
this.useMaster = true;
public Query<T> usingMaster(boolean useMaster) {
this.useMaster = useMaster;
return this;
}
@@ -1895,7 +1895,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
public final ExpressionList<T> text() {
if (textExpressions == null) {
useDocStore = true;
textExpressions = new DefaultExpressionList<>(this);
textExpressions = new DefaultExpressionList<>(this, true);
}
return textExpressions;
}
@@ -1903,7 +1903,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
@Override
public final ExpressionList<T> where() {
if (whereExpressions == null) {
whereExpressions = new DefaultExpressionList<>(this, null);
whereExpressions = new DefaultExpressionList<>(this, false);
}
return whereExpressions;
}
@@ -1924,7 +1924,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
@Override
public final ExpressionList<T> having() {
if (havingExpressions == null) {
havingExpressions = new DefaultExpressionList<>(this, null);
havingExpressions = new DefaultExpressionList<>(this, false);
}
return havingExpressions;
}
@@ -60,8 +60,8 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq
}
@Override
public SqlQuery usingMaster() {
this.useMaster = true;
public SqlQuery usingMaster(boolean useMaster) {
this.useMaster = useMaster;
return this;
}
@@ -39,12 +39,21 @@ public final class OrmQueryDetail implements Serializable {
/**
* Return a deep copy of the OrmQueryDetail.
*/
public OrmQueryDetail copy() {
public OrmQueryDetail copy(OrmQueryDetail existing) {
OrmQueryDetail copy = new OrmQueryDetail();
copy.baseProps = baseProps.copy();
for (Map.Entry<String, OrmQueryProperties> entry : fetchPaths.entrySet()) {
copy.fetchPaths.put(entry.getKey(), entry.getValue().copy());
}
if (existing != null) {
// transfer any existing filterMany expressions
for (Map.Entry<String, OrmQueryProperties> entry : existing.fetchPaths.entrySet()) {
var filterMany = entry.getValue().getFilterMany();
if (filterMany != null) {
copy.getChunk(entry.getKey(), true).setFilterMany(filterMany);
}
}
}
return copy;
}
@@ -171,8 +171,9 @@ public final class OrmQueryProperties implements Serializable {
if (filterMany == null) {
FilterExprPath exprPath = new FilterExprPath(path);
SpiExpressionFactory queryEf = (SpiExpressionFactory) rootQuery.getExpressionFactory();
ExpressionFactory filterEf = queryEf.createExpressionFactory();// exprPath);
ExpressionFactory filterEf = queryEf.createExpressionFactory();
filterMany = new FilterExpressionList(exprPath, filterEf, rootQuery);
// assuming conditions supported in JOIN, not setting markForQueryJoin = true
}
return filterMany;
}
@@ -187,6 +188,13 @@ public final class OrmQueryProperties implements Serializable {
return filterMany.trimPath(trimPath);
}
/**
* Return true if there is a filterMany and it should be included in the JOIN.
*/
public boolean isFilterManyJoin() {
return filterMany != null && !markForQueryJoin;
}
/**
* Adjust filterMany expressions for inclusion in main query.
*/
@@ -10,7 +10,7 @@ public interface SpiFetchGroup<T> extends FetchGroup<T> {
/**
* Return the detail to use for query execution.
*/
OrmQueryDetail detail();
OrmQueryDetail detail(OrmQueryDetail existing);
/**
* Return the underlying detail for copy purposes.
@@ -20,7 +20,7 @@ public final class DocStoreTransactionManager extends TransactionManager {
@Override
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
return createTransaction(explicit, null);
return new DocStoreOnlyTransaction(explicit, this);
}
@Override
@@ -28,8 +28,4 @@ public final class DocStoreTransactionManager extends TransactionManager {
return new DocStoreOnlyTransaction(false, this);
}
@Override
protected SpiTransaction createTransaction(boolean explicit, Connection c) {
return new DocStoreOnlyTransaction(explicit, this);
}
}
@@ -150,11 +150,6 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
return false;
}
@Override
public boolean isSkipCacheExplicit() {
return false;
}
@Override
public void setSkipCache(boolean skipCache) {
}
@@ -200,7 +195,7 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
}
@Override
public void registerDeleteBean(Integer persistingBean) {
public void registerDeleteBean(Class<?> type, Object id) {
throw new IllegalStateException(notExpectedMessage);
}
@@ -208,7 +203,7 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
* Return true if this is a bean that has already been saved/deleted.
*/
@Override
public boolean isRegisteredDeleteBean(Integer persistingBean) {
public boolean isRegisteredDeleteBean(Class<?> type, Object id) {
return false;
}
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.transaction;
import org.jspecify.annotations.Nullable;
import java.sql.Connection;
/**
* Ebean transaction used for AutoCommit true connections.
*/
final class JdbcAutoCommitTransaction extends JdbcTransaction {
JdbcAutoCommitTransaction(boolean explicit, Connection connection, @Nullable TransactionManager manager) {
super(true, explicit, connection, manager);
}
@Override
void performRollback() {
long offset = profileOffset();
if (profileStream != null) {
profileStream.addEvent(EVT_ROLLBACK, offset);
}
}
@Override
void performCommit() {
long offset = profileOffset();
if (profileStream != null) {
profileStream.addEvent(EVT_COMMIT, offset);
}
}
}
@@ -68,7 +68,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
private int depth;
private boolean autoCommit;
private IdentityHashMap<Object, Object> persistingBeans;
private HashSet<Integer> deletingBeansHash;
private Map<Class<?>, Set<Object>> deletingBeans;
private HashMap<String, String> m2mIntersectionSave;
private Map<String, Object> userObjects;
private List<TransactionCallback> callbackList;
@@ -90,13 +90,17 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
*/
private final boolean skipCacheAfterWrite;
DocStoreTransaction docStoreTxn;
private ProfileStream profileStream;
protected ProfileStream profileStream;
private ProfileLocation profileLocation;
private final Instant startTime = Instant.now();
private final long startNanos;
private boolean autoPersistUpdates;
JdbcTransaction(boolean explicit, Connection connection, TransactionManager manager) {
this(false, explicit, connection, manager);
}
JdbcTransaction(boolean autoCommit, boolean explicit, Connection connection, TransactionManager manager) {
try {
this.active = true;
this.explicit = explicit;
@@ -125,7 +129,9 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.onQueryOnlyCommit = true;
}
checkAutoCommit(connection);
if (!autoCommit) {
checkAutoCommit(connection);
}
} catch (Exception e) {
throw new PersistenceException(e);
@@ -203,11 +209,6 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
return autoPersistUpdates;
}
@Override
public final boolean isSkipCacheExplicit() {
return (skipCache != null && !skipCache);
}
@Override
public final boolean isSkipCache() {
if (skipCache != null) return skipCache;
@@ -328,40 +329,28 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
deferredList.add(derived);
}
/**
* Add a bean to the registed list.
* <p>
* This is to handle bi-directional relationships where both sides Cascade.
* </p>
*/
@Override
public final void registerDeleteBean(Integer persistingBean) {
if (deletingBeansHash == null) {
deletingBeansHash = new HashSet<>();
public final void registerDeleteBean(Class<?> type, Object id) {
deleteBeanIds(type).add(id);
}
@Override
public final boolean isRegisteredDeleteBean(Class<?> type, Object id) {
return deleteBeanIds(type).contains(id);
}
private Set<Object> deleteBeanIds(Class<?> type) {
if (deletingBeans == null) {
deletingBeans = new HashMap<>();
}
deletingBeansHash.add(persistingBean);
return deletingBeans.computeIfAbsent(type, k -> new HashSet<>());
}
/**
* Return true if this is a bean that has already been saved/deleted.
*/
@Override
public final boolean isRegisteredDeleteBean(Integer persistingBean) {
return deletingBeansHash != null && deletingBeansHash.contains(persistingBean);
}
/**
* Unregister the persisted beans (when persisting at the top level).
*/
@Override
public final void unregisterBeans() {
persistingBeans.clear();
}
/**
* Return true if this is a bean that has already been saved. This will
* register the bean if it is not already.
*/
@Override
public final boolean isRegisteredBean(Object bean) {
if (persistingBeans == null) {
@@ -370,10 +359,6 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
return (persistingBeans.put(bean, PLACEHOLDER) != null);
}
/**
* Return true if the m2m intersection save is allowed from a given bean direction.
* This is to stop m2m intersection management via both directions of a m2m.
*/
@Override
public final boolean isSaveAssocManyIntersection(String intersectionTable, String beanName) {
if (m2mIntersectionSave == null) {
@@ -154,12 +154,12 @@ final class NoTransaction implements SpiTransaction {
}
@Override
public void registerDeleteBean(Integer hash) {
public void registerDeleteBean(Class<?> type, Object id) {
}
@Override
public boolean isRegisteredDeleteBean(Integer hash) {
public boolean isRegisteredDeleteBean(Class<?> type, Object id) {
return false;
}
@@ -235,11 +235,6 @@ final class NoTransaction implements SpiTransaction {
public void setSkipCache(boolean skipCache) {
}
@Override
public boolean isSkipCacheExplicit() {
return false;
}
@Override
public boolean isSkipCache() {
return false;
@@ -13,9 +13,18 @@ import java.sql.SQLException;
abstract class TransactionFactory {
final TransactionManager manager;
private final boolean autoCommitMode;
TransactionFactory(TransactionManager manager) {
this.manager = manager;
this.autoCommitMode = manager.isAutoCommitMode();
}
/**
* Return a new transaction.
*/
SpiTransaction createTransaction(boolean explicit, Connection connection) {
return autoCommitMode ? new JdbcAutoCommitTransaction(explicit, connection, manager) : new JdbcTransaction(explicit, connection, manager);
}
/**
@@ -39,7 +39,7 @@ class TransactionFactoryBasic extends TransactionFactory {
Connection connection = null;
try {
connection = dataSource.getConnection();
SpiTransaction t = manager.createTransaction(explicit, connection);
SpiTransaction t = createTransaction(explicit, connection);
return setIsolationLevel(t, isolationLevel);
} catch (PersistenceException ex) {
JdbcClose.close(connection);
@@ -48,7 +48,7 @@ class TransactionFactoryTenant extends TransactionFactory {
try {
Object tenantId = tenantProvider.currentId();
connection = dataSourceSupplier.connection(tenantId);
SpiTransaction transaction = manager.createTransaction(explicit, connection);
SpiTransaction transaction = createTransaction(explicit, connection);
transaction.setTenantId(tenantId);
return setIsolationLevel(transaction, isolationLevel);
@@ -5,12 +5,12 @@ import io.ebean.BackgroundExecutor;
import io.ebean.ProfileLocation;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.annotation.TxType;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.datasource.DataSourcePool;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeSet;
@@ -106,6 +106,7 @@ public class TransactionManager implements SpiTransactionManager {
private final ServerCacheNotify cacheNotify;
private final boolean supportsSavepointId;
private final ConcurrentHashMap<String, ProfileLocation> profileLocations = new ConcurrentHashMap<>();
private final boolean autoCommitMode;
/**
* Create the TransactionManager
@@ -115,6 +116,7 @@ public class TransactionManager implements SpiTransactionManager {
this.logManager = options.logManager;
this.databasePlatform = options.config.getDatabasePlatform();
this.supportsSavepointId = databasePlatform.supportsSavepointId();
this.autoCommitMode = databasePlatform.platform() == Platform.CLICKHOUSE;
this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
this.notifyL2CacheInForeground = options.notifyL2CacheInForeground;
this.autoPersistUpdates = options.config.isAutoPersistUpdates();
@@ -206,6 +208,10 @@ public class TransactionManager implements SpiTransactionManager {
}
}
final boolean isAutoCommitMode() {
return autoCommitMode;
}
/**
* Return true if the DB platform supports SavepointId().
*/
@@ -298,13 +304,6 @@ public class TransactionManager implements SpiTransactionManager {
return transactionFactory.createReadOnlyTransaction(tenantId, useMaster);
}
/**
* Create a new transaction.
*/
SpiTransaction createTransaction(boolean explicit, Connection c) {
return new JdbcTransaction(explicit, c, this);
}
/**
* Process a local rolled back transaction.
*/
@@ -216,6 +216,9 @@ public final class DefaultTypeManager implements TypeManager {
}
found = checkInheritedTypes(type);
}
if (found instanceof ScalarTypeClass) {
log.log(WARNING, "@Column mapping for type Class is deprecated. Please refer to https://ebean.io/docs/deprecated#class-mapping");
}
return found != ScalarTypeNotFound.INSTANCE ? found : null; // Do not return ScalarTypeNotFound, otherwise checks will fail
}
@@ -371,7 +374,7 @@ public final class DefaultTypeManager implements TypeManager {
private ScalarType<?> createJsonObjectMapperType(DeployBeanProperty prop, int dbType, DocPropertyType docType) {
if (jsonMapper == null) {
throw new IllegalArgumentException("Unsupported @DbJson mapping - Jackson ObjectMapper not present for " + prop);
throw new IllegalArgumentException("Unsupported @DbJson mapping - Missing dependency ebean-jackson-mapper? Jackson ObjectMapper not present for " + prop);
}
if (MutationDetection.DEFAULT == prop.getMutationDetection()) {
prop.setMutationDetection(jsonManager.mutationDetection());

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