Compare commits

..
25 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
85 changed files with 760 additions and 321 deletions
+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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-net-postgis-types</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<artifactId>composites</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<name>ebean api</name>
@@ -51,6 +51,13 @@ public class DbPlatformTypeMapping {
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);
/**
@@ -87,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
+28 -28
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</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.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<!-- platforms -->
@@ -193,91 +193,91 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>16.2.1</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.2.1</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.2.1</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.2.1</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.2.1</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.2.1</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.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-json</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -52,7 +52,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -165,21 +165,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
@@ -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);
}
}
}
@@ -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();
@@ -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();
}
@@ -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
@@ -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) {
@@ -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());
}
@@ -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;
}
@@ -150,11 +150,6 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
return false;
}
@Override
public boolean isSkipCacheExplicit() {
return false;
}
@Override
public void setSkipCache(boolean skipCache) {
}
@@ -209,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;
@@ -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;
@@ -55,7 +55,7 @@ class BasicProfileLocationTest {
DProfileLocation loc = new DTimedProfileLocation("foo", MetricFactory.get().createTimedMetric("junk"));
assertThat(loc.obtain()).isTrue();
assertThat(loc.fullLocation()).endsWith("org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:728)");
assertThat(loc.fullLocation()).endsWith("org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:786)");
assertThat(loc.location()).isEqualTo("org.junit.platform.commons.util.ReflectionUtils.invokeMethod");
assertThat(loc.label()).isEqualTo("ReflectionUtils.invokeMethod");
}
@@ -0,0 +1,118 @@
package io.ebeaninternal.server.query;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class CQueryBuilderTest {
/**
* Simulates what buildRowCountQuery does: strip the top-level order by, then wrap with count.
* This is the logic that must use lastTopLevelOrderBy instead of lastIndexOf.
*/
private static String simulateCountWrap(String sql) {
int pos = CQueryBuilder.lastTopLevelOrderBy(sql);
if (pos != -1) {
sql = sql.substring(0, pos);
}
return "select count(*) from ( " + sql + ") as c";
}
private static int countChar(String s, char c) {
int count = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == c) count++;
}
return count;
}
@Test
void lastTopLevelOrderBy_simple() {
String sql = "select t0.id from ad t0 order by t0.id";
int pos = CQueryBuilder.lastTopLevelOrderBy(sql);
assertThat(pos).isEqualTo(sql.indexOf(" order by "));
}
@Test
void lastTopLevelOrderBy_noOrderBy() {
String sql = "select t0.id from ad t0";
int pos = CQueryBuilder.lastTopLevelOrderBy(sql);
assertThat(pos).isEqualTo(-1);
}
@Test
void lastTopLevelOrderBy_insideSubquery() {
// order by is only inside a subquery - should not be found at top level
String sql = "select t0.id from ad t0 where t0.id in (select t0.id from ad t0 order by t0.rebate)";
int pos = CQueryBuilder.lastTopLevelOrderBy(sql);
assertThat(pos).isEqualTo(-1);
}
@Test
void lastTopLevelOrderBy_bothLevels() {
// order by inside subquery AND at top level - should find only the top-level one
String sql = "select t0.id from ad t0 where t0.id in (select t0.id from ad t0 order by t0.rebate) order by t0.id";
int pos = CQueryBuilder.lastTopLevelOrderBy(sql);
assertThat(sql.substring(pos)).isEqualTo(" order by t0.id");
}
@Test
void lastTopLevelOrderBy_nestedSubqueries() {
// deeply nested order by should not be found
String sql = "select t0.id from ad t0 where t0.id in (select t0.id from ad t0 where t0.x in (select id from foo order by bar))";
int pos = CQueryBuilder.lastTopLevelOrderBy(sql);
assertThat(pos).isEqualTo(-1);
}
/**
* Reproduces the exact scenario from https://github.com/ebean-orm/ebean/issues/3686
*
* With lastIndexOf(" order by "), the inner subquery's order by is matched,
* stripping its closing parenthesis and producing unbalanced SQL.
*/
@Test
void countWrap_formulaJoinWithSubqueryOrderBy_issue3686() {
// This is the SQL that buildRowCountQuery would produce before wrapping,
// matching the bug report: @Formula with JOIN + IN subquery with distinctOn + orderBy
String innerSql = "select t0.id from ad t0"
+ " LEFT JOIN price_range ON price_range.ad_id = t0.id"
+ " where t0.id in (select distinct on (t0.rebate) t0.id from ad t0 order by t0.rebate)";
String countSql = simulateCountWrap(innerSql);
// The subquery's closing ) must be preserved
assertThat(countSql).contains("order by t0.rebate)");
// Parentheses must be balanced
assertThat(countChar(countSql, '(')).isEqualTo(countChar(countSql, ')'));
// Should end with ") as c" - the outer count wrapper's closing paren
assertThat(countSql).endsWith(") as c");
}
@Test
void countWrap_topLevelOrderByIsStripped() {
// When there IS a top-level order by, it should be stripped
String innerSql = "select t0.id from ad t0"
+ " LEFT JOIN price_range ON price_range.ad_id = t0.id"
+ " where t0.id in (select distinct on (t0.rebate) t0.id from ad t0 order by t0.rebate)"
+ " order by price_range.discounted_price";
String countSql = simulateCountWrap(innerSql);
// Top-level order by should be removed
assertThat(countSql).doesNotContain("discounted_price");
// But inner subquery order by must remain intact
assertThat(countSql).contains("order by t0.rebate)");
// Parentheses must be balanced
assertThat(countChar(countSql, '(')).isEqualTo(countChar(countSql, ')'));
}
@Test
void countWrap_simpleOrderByIsStripped() {
// Simple case: top-level order by with no subquery
String innerSql = "select t0.id from ad t0 order by t0.id";
String countSql = simulateCountWrap(innerSql);
assertThat(countSql).isEqualTo("select count(*) from ( select t0.id from ad t0) as c");
}
}
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<name>ebean ddl generation</name>
@@ -28,14 +28,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
@@ -65,7 +65,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -15,7 +15,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<name>ebean net postgis types</name>
@@ -19,14 +19,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
@@ -54,7 +54,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<name>ebean pgvector types</name>
@@ -19,14 +19,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
@@ -54,7 +54,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<name>ebean postgis types</name>
@@ -19,14 +19,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
@@ -62,7 +62,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<name>ebean querybean</name>
@@ -17,7 +17,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
@@ -59,14 +59,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
@@ -80,7 +80,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<artifactId>ebean-redis</artifactId>
@@ -29,35 +29,35 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
@@ -1,6 +1,8 @@
package org.integration;
import io.ebean.DB;
import io.ebean.InTuples;
import io.ebean.Pairs;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheStatistics;
import org.domain.*;
@@ -177,6 +179,76 @@ class IntegrationTest {
.findOne();
}
@Test
void naturalKey_inPairs() throws InterruptedException {
DB.save(new OtherOne("ip_A", "ip_1", "ip_A1"));
DB.save(new OtherOne("ip_A", "ip_2", "ip_A2"));
DB.save(new OtherOne("ip_B", "ip_1", "ip_B1"));
ServerCache nkeyCache = DB.cacheManager().naturalKeyCache(OtherOne.class);
nkeyCache.clear();
Pairs pairs = new Pairs("one", "two")
.add("ip_A", "ip_1")
.add("ip_A", "ip_2")
.add("ip_B", "ip_1");
// first fetch — miss, populates natural key + bean cache
List<OtherOne> list0 = DB.find(OtherOne.class)
.where()
.inPairs(pairs)
.setUseCache(true)
.findList();
assertThat(list0).hasSize(3);
nkeyCache.statistics(true); // reset stats
Thread.sleep(5);
// second fetch — all three should hit the natural key cache
List<OtherOne> list1 = DB.find(OtherOne.class)
.where()
.inPairs(pairs)
.setUseCache(true)
.findList();
assertThat(list1).hasSize(3);
assertThat(nkeyCache.statistics(true).getHitCount()).isEqualTo(3);
}
@Test
void naturalKey_inTuples() throws InterruptedException {
DB.save(new OtherOne("it_A", "it_1", "it_A1"));
DB.save(new OtherOne("it_A", "it_2", "it_A2"));
DB.save(new OtherOne("it_B", "it_1", "it_B1"));
ServerCache nkeyCache = DB.cacheManager().naturalKeyCache(OtherOne.class);
nkeyCache.clear();
InTuples tuples = InTuples.of("one", "two")
.add("it_A", "it_1")
.add("it_A", "it_2")
.add("it_B", "it_1");
// first fetch — miss, populates natural key + bean cache
List<OtherOne> list0 = DB.find(OtherOne.class)
.where()
.inTuples(tuples)
.setUseCache(true)
.findList();
assertThat(list0).hasSize(3);
nkeyCache.statistics(true); // reset stats
Thread.sleep(5);
// second fetch — all three should hit the natural key cache
List<OtherOne> list1 = DB.find(OtherOne.class)
.where()
.inTuples(tuples)
.setUseCache(true)
.findList();
assertThat(list1).hasSize(3);
assertThat(nkeyCache.statistics(true).getHitCount()).isEqualTo(3);
}
@Test
void test() throws InterruptedException {
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<artifactId>ebean-spring-txn</artifactId>
@@ -28,7 +28,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
@@ -77,7 +77,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
+7 -7
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<name>ebean test</name>
@@ -13,7 +13,7 @@
<properties>
<bytebuddy.version>1.18.1</bytebuddy.version>
<assertj.version>3.27.6</assertj.version>
<assertj.version>3.27.7</assertj.version>
</properties>
<dependencies>
@@ -33,20 +33,20 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -149,14 +149,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
@@ -2,16 +2,17 @@ package io.ebean.xtest.config.dbplatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbPlatformTypeMapping;
import io.ebean.config.dbplatform.DbType;
import org.junit.jupiter.api.Test;
import java.sql.Types;
import static org.assertj.core.api.Assertions.assertThat;
public class DbPlatformTypeMappingTest {
class DbPlatformTypeMappingTest {
@Test
public void logicalBoolean_renderType_expect_noLength() {
void logicalBoolean_renderType_expect_noLength() {
DbPlatformTypeMapping logicalMapping = DbPlatformTypeMapping.logicalTypes();
@@ -19,4 +20,11 @@ public class DbPlatformTypeMappingTest {
String colDefinition = type.renderType(1, 1, false);
assertThat(colDefinition).isEqualTo("boolean");
}
@Test
void timestamp_with_255_expectNoPrecision() {
DbPlatformType timestampType = new DbPlatformTypeMapping().get(DbType.TIMESTAMP);
String colDefinition = timestampType.renderType(255, 0, true);
assertThat(colDefinition).isEqualTo("timestamp");
}
}
@@ -30,13 +30,13 @@ public class BeanFindControllerTest extends BaseTestCase {
var config = new DatabaseConfig();
config.setName("h2otherfind");
config.setRegister(false);
config.setDefaultServer(false);
config.loadFromProperties();
config.setDdlGenerate(true);
config.setDdlRun(true);
config.setDdlExtra(false);
config.setRegister(false);
config.setDefaultServer(false);
config.add(new ModUuidGenerator());
config.addClass(EBasic.class);
config.addClass(ECustomId.class);
@@ -173,13 +173,13 @@ public class BeanFindControllerTest extends BaseTestCase {
DatabaseConfig config = new DatabaseConfig();
config.setName("h2otherfind");
config.setRegister(false);
config.setDefaultServer(false);
config.loadFromProperties();
config.setDdlGenerate(true);
config.setDdlRun(true);
config.setDdlExtra(false);
config.setRegister(false);
config.setDefaultServer(false);
config.add(new ModUuidGenerator());
config.addClass(FindControllerMain.class);
config.addClass(SoftRefA.class);
@@ -10,6 +10,8 @@ import io.ebean.event.BeanDeleteIdRequest;
import io.ebean.event.BeanPersistAdapter;
import io.ebean.event.BeanPersistRequest;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tests.model.basic.EBasicVer;
import org.tests.model.basic.UTDetail;
import org.tests.model.basic.UTMaster;
@@ -22,13 +24,15 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanPersistControllerTest {
private static final Logger log = LoggerFactory.getLogger(BeanPersistControllerTest.class);
private final PersistAdapter continuePersistingAdapter = new PersistAdapter(true);
private final PersistAdapter stopPersistingAdapter = new PersistAdapter(false);
@Test
public void issued1() {
Database db = getDatabase(continuePersistingAdapter);
Database db = createDatabase(continuePersistingAdapter);
UTMaster bean0 = new UTMaster("m0");
bean0.setJournal(new UTMaster.Journal());
@@ -47,11 +51,12 @@ public class BeanPersistControllerTest {
assertThat(journal.getEntries()).hasSize(2);
db.shutdown();
log.info("done issued1");
}
@Test
public void issue_1341() {
Database db = getDatabase(continuePersistingAdapter);
Database db = createDatabase(continuePersistingAdapter);
UTMaster bean0 = new UTMaster("one0");
UTDetail detail0 = new UTDetail("detail0", 12, 23D);
@@ -77,12 +82,13 @@ public class BeanPersistControllerTest {
}
db.shutdown();
log.info("done issue_1341");
}
@Test
public void testInsertUpdateDelete_given_continuePersistingAdapter() {
Database db = getDatabase(continuePersistingAdapter);
Database db = createDatabase(continuePersistingAdapter);
EBasicVer bean = new EBasicVer("testController");
@@ -102,12 +108,13 @@ public class BeanPersistControllerTest {
assertThat(continuePersistingAdapter.methodsCalled).containsExactly("preDelete", "postDelete");
db.shutdown();
log.info("done testInsertUpdateDelete_given_continuePersistingAdapter");
}
@Test
public void testInsertUpdateDelete_given_stopPersistingAdapter() {
Database db = getDatabase(stopPersistingAdapter);
Database db = createDatabase(stopPersistingAdapter);
EBasicVer bean = new EBasicVer("testController");
@@ -138,18 +145,19 @@ public class BeanPersistControllerTest {
stopPersistingAdapter.methodsCalled.clear();
db.shutdown();
log.info("done testInsertUpdateDelete_given_stopPersistingAdapter");
}
private Database getDatabase(PersistAdapter persistAdapter) {
private Database createDatabase(PersistAdapter persistAdapter) {
DatabaseBuilder config = new DatabaseConfig();
config.setName("h2ebasicver");
config.setRegister(false);
config.setDefaultServer(false);
config.loadFromProperties();
config.setDdlGenerate(true);
config.setDdlRun(true);
config.setDdlExtra(false);
config.setRegister(false);
config.setDefaultServer(false);
config.addClass(EBasicVer.class);
config.addClass(UTMaster.class);
config.addClass(UTDetail.class);
@@ -52,13 +52,13 @@ public class BeanPostLoadTest extends BaseTestCase {
DatabaseBuilder config = new DatabaseConfig();
config.setName("h2ebasicver");
config.setRegister(false);
config.setDefaultServer(false);
config.loadFromProperties();
config.setDdlGenerate(true);
config.setDdlRun(true);
config.setDdlExtra(false);
config.setRegister(false);
config.setDefaultServer(false);
config.addClass(EBasicVer.class);
config.add(postLoad);
@@ -7,23 +7,20 @@ import org.tests.model.basic.Order;
import jakarta.persistence.PersistenceException;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.assertj.core.api.Assertions.assertThat;
public class TestErrorBindLog extends BaseTestCase {
class TestErrorBindLog extends BaseTestCase {
@Test
public void test() {
void test() {
try {
DB.find(Order.class).where().gt("id", "JUNK").findList();
} catch (PersistenceException e) {
String msg = e.getMessage();
if (isHana()) {
assertTrue(msg.contains("Error with property[1] dt[12]data[JUNK]"));
}
else {
assertTrue(msg.contains("Bind values:"));
if (isHana() || isDb2()) {
assertThat(msg).contains("Error with property");
} else {
assertThat(msg).contains("Bind values:");
}
}
}
@@ -124,7 +124,7 @@ public class TestBeanCache extends BaseTestCase {
ServerCacheStatistics statistics = beanCache.statistics(true);
assertThat(statistics.getHitCount()).isEqualTo(0);
assertThat(statistics.getMissCount()).isEqualTo(0);
assertThat(statistics.getPutCount()).isEqualTo(2);
assertThat(statistics.getPutCount()).isEqualTo(0);
}
@Test
@@ -445,4 +445,21 @@ public class TestQueryFilterMany extends BaseTestCase {
assertSql(sql.get(0)).contains(" from o_customer t0 left join contact t1 on t1.customer_id = t0.id and (t1.first_name is not null and lower(t1.email) like ? escape'|') order by t0.id; --bind(rob%)");
}
}
@Test
void testFilterManyWithNestedPathExpression() {
ResetBasicData.reset();
LoggedSql.start();
DB.find(Customer.class)
.select("id, name")
.fetch("contacts", "firstName, email")
.filterMany("contacts").eq("group.name", "DoesNotExist").isNotNull("cretime")
.findList();
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertSql(sql.get(0)).contains(" from o_customer t0 left join contact t1 on t1.customer_id = t0.id left join contact_group t2 on t2.id = t1.group_id and t2.name = ? and t1.cretime is not null order by t0.id");
}
}
@@ -43,4 +43,19 @@ public class TestQueryFilterManySimple extends BaseTestCase {
assertThat(sql.get(1)).contains("from contact t0 where (t0.customer_id) in (?) and t0.first_name is not null;");
}
}
@Test
void testNestedFilterMany() {
ResetBasicData.reset();
LoggedSql.start();
DB.find(Customer.class)
.fetch("orders")
.filterMany("orders").eq("customer.status", Customer.Status.NEW)
.findList();
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id and t2.status = ? order by t0.id");
}
}
@@ -4,6 +4,7 @@ import io.ebean.DB;
import io.ebean.Query;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.BaseTestCase;
import io.ebean.xtest.ForPlatform;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Order;
@@ -16,6 +17,8 @@ import org.tests.model.family.ParentPerson;
import java.sql.Date;
import java.util.List;
import static io.ebean.annotation.Platform.H2;
import static io.ebean.annotation.Platform.POSTGRES;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -333,6 +336,52 @@ public class TestQueryJoinOnFormula extends BaseTestCase {
assertThat(loggedSql.get(0)).contains("where coalesce(f2.child_age, 0) = ?");
}
/**
* Test for https://github.com/ebean-orm/ebean/issues/3686
* findCount with Formula join + subquery containing ORDER BY
* should produce valid SQL with properly balanced parentheses.
*
* Before the fix, lastIndexOf(" order by ") matched the ORDER BY inside the
* IN subquery, stripping its closing parenthesis and producing invalid SQL.
*/
@ForPlatform({H2, POSTGRES})
@Test
public void test_findCount_formulaJoin_subqueryWithOrderBy_issue3686() {
LoggedSql.start();
// Subquery with orderBy — the order by inside the subquery triggers the bug.
Query<Order> subQuery = DB.find(Order.class)
.select("id")
.orderBy("id");
// Outer query: formula join (totalItems) via where clause + findCount
int count = DB.find(Order.class)
.where()
.in("id", subQuery)
.eq("totalItems", 3)
.findCount();
assertThat(count).isEqualTo(2);
List<String> sql = LoggedSql.stop();
assertEquals(1, sql.size());
String countSql = sql.get(0);
// The count query must wrap with select count(*) from ( ... )
assertThat(countSql).contains("select count(*) from (");
// The subquery's ORDER BY and closing ) must be preserved
assertThat(countSql).contains("order by t0.id)");
// Parentheses must be balanced in the generated SQL
int open = 0, close = 0;
for (char c : countSql.toCharArray()) {
if (c == '(') open++;
if (c == ')') close++;
}
assertThat(open).as("parentheses must be balanced in: " + countSql).isEqualTo(close);
}
@Test
public void test_softRef() {
@@ -75,16 +75,18 @@ public class TestOrderByWithDistinct extends BaseTestCase {
assertSql(query).startsWith("select distinct on (t0.id, t2.name, t1.id) t0.id, t0.parent_id, t0.ref_id, t1.id, t1.parent_id, t1.ref_id, t2.name "
+ "from e_basic_tree t0 "
+ "left join e_basic_tree t1 on t1.parent_id = t0.id "
+ "left join e_basic t2 on t2.id = t1.ref_id "
+ "join e_basic_tree u1 on u1.parent_id = t0.id "
+ "join e_basic u2 on u2.id = u1.ref_id left "
+ "join e_basic t2 on t2.id = t1.ref_id where u2.status = ? order by t0.id, t2.name");
+ "join e_basic u2 on u2.id = u1.ref_id "
+ "where u2.status = ? order by t0.id, t2.name");
} else {
assertSql(query).startsWith("select distinct t0.id, t0.parent_id, t0.ref_id, t1.id, t1.parent_id, t1.ref_id, t2.name "
+ "from e_basic_tree t0 "
+ "left join e_basic_tree t1 on t1.parent_id = t0.id "
+ "left join e_basic t2 on t2.id = t1.ref_id "
+ "join e_basic_tree u1 on u1.parent_id = t0.id "
+ "join e_basic u2 on u2.id = u1.ref_id left "
+ "join e_basic t2 on t2.id = t1.ref_id where u2.status = ? order by t0.id, t2.name");
+ "join e_basic u2 on u2.id = u1.ref_id "
+ "where u2.status = ? order by t0.id, t2.name");
}
}
+66
View File
@@ -0,0 +1,66 @@
# GraalVM Native Image
Ebean ORM supports GraalVM native image compilation. No additional dependencies or
native-image configuration files are required beyond what Ebean already provides.
## How it works
Ebean uses **bytecode enhancement at compile time** (via the ebean-maven-plugin or
ebean-gradle-plugin). The enhanced entity classes are compiled directly into the native
image. Because the enhancement happens ahead-of-time, Ebean does not rely on runtime
reflection for normal ORM operations — queries, inserts, updates, deletes, and
associations all work without additional configuration.
Entity classes do require reflection registration for native image, but this is handled
**automatically by the querybean-generator** annotation processor at compile time.
Projects using the querybean-generator (which is the standard and expected setup) have
nothing extra to do for `@Entity` classes.
## Setup
Ensure the ebean enhancement plugin runs during your build, as it normally would for
a standard JVM application. No extra steps are required for native image.
**Maven:**
```xml
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>${ebean.version}</version>
<extensions>true</extensions>
</plugin>
```
**Gradle:**
```kotlin
plugins {
id("io.ebean") version "${ebeanVersion}"
}
```
## DtoQuery and reflection
[`DtoQuery`](https://ebean.io/docs/query/dto) maps SQL results onto plain classes using
their public constructors and setter methods. This mapping uses reflection at runtime, so
DTO classes must be explicitly registered for reflection in your native-image configuration.
Create `src/main/resources/META-INF/native-image/<group-id>/<artifact-id>/reflect-config.json`
and add an entry for each DTO class:
```json
[
{
"name": "com.example.CustomerDto",
"allDeclaredConstructors": true,
"allPublicMethods": true
},
{
"name": "com.example.OrderSummaryDto",
"allDeclaredConstructors": true,
"allPublicMethods": true
}
]
```
> **Note:** `@Entity` classes are registered automatically by the querybean-generator —
> no manual entries are needed for them.
+2 -2
View File
@@ -15,8 +15,8 @@ mvn -T 4 clean package
mvn -T 4 deploy -pl '!composites,!platforms' -Pcentral -DskipTests
## git commit, git tag, git push --tags
git commit -am 'Version 16.2.1'
git tag 16.2.1
git commit -am 'Version 16.4.0'
git tag 16.4.0
git push --tags
## convert to javax
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<name>kotlin querybean generator</name>
@@ -21,7 +21,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
@@ -35,7 +35,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
@@ -56,14 +56,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
@@ -83,7 +83,7 @@
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>2.2.10</version>
<version>2.3.10</version>
<executions>
<execution>
<id>test-compile</id>
+14 -14
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,67 +16,67 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hsqldb</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlanywhere</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<!-- Provided scope so that the H2HistoryTrigger can live in Ebean core
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<artifactId>platforms</artifactId>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
</dependencies>
+8 -8
View File
@@ -4,12 +4,12 @@
<parent>
<groupId>org.avaje</groupId>
<artifactId>java11-oss</artifactId>
<version>5.1</version>
<version>5.2</version>
</parent>
<groupId>io.ebean</groupId>
<artifactId>ebean-parent</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<packaging>pom</packaging>
<name>ebean parent</name>
@@ -48,10 +48,10 @@
<ebean-ddl-runner.version>2.3</ebean-ddl-runner.version>
<ebean-migration-auto.version>1.2</ebean-migration-auto.version>
<ebean-migration.version>14.3.0</ebean-migration.version>
<ebean-test-containers.version>7.17</ebean-test-containers.version>
<ebean-datasource.version>10.2</ebean-datasource.version>
<ebean-agent.version>16.2.1</ebean-agent.version>
<ebean-maven-plugin.version>16.2.1</ebean-maven-plugin.version>
<ebean-test-containers.version>8.0</ebean-test-containers.version>
<ebean-datasource.version>10.4</ebean-datasource.version>
<ebean-agent.version>16.4.0</ebean-agent.version>
<ebean-maven-plugin.version>16.4.0</ebean-maven-plugin.version>
<surefire.useModulePath>false</surefire.useModulePath>
</properties>
@@ -60,14 +60,14 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>junit</artifactId>
<version>1.5</version>
<version>1.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.13</version>
<version>1.5.31</version>
<scope>test</scope>
</dependency>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<name>querybean generator</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<artifactId>tests</artifactId>
+4 -4
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>tests</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<artifactId>test-java16</artifactId>
@@ -18,7 +18,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -30,14 +30,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>provided</scope>
</dependency>
+4 -4
View File
@@ -6,7 +6,7 @@
<parent>
<artifactId>tests</artifactId>
<groupId>io.ebean</groupId>
<version>16.2.1</version>
<version>16.4.0</version>
</parent>
<artifactId>test-kotlin</artifactId>
@@ -40,7 +40,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
</dependency>
<dependency>
@@ -53,7 +53,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.2.1</version>
<version>16.4.0</version>
<scope>test</scope>
</dependency>
@@ -73,7 +73,7 @@
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>2.2.10</version>
<version>2.3.10</version>
<configuration>
<jvmTarget>${maven.compiler.target}</jvmTarget>
</configuration>